org.exist.xupdate.Modification Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of exist-core Show documentation
Show all versions of exist-core Show documentation
eXist-db NoSQL Database Core
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* [email protected]
* http://www.exist-db.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.xupdate;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.collections.ManagedLocks;
import org.exist.collections.triggers.DocumentTrigger;
import org.exist.collections.triggers.DocumentTriggers;
import org.exist.collections.triggers.TriggerException;
import org.exist.dom.persistent.DefaultDocumentSet;
import org.exist.dom.persistent.DocumentImpl;
import org.exist.dom.persistent.DocumentSet;
import org.exist.dom.persistent.MutableDocumentSet;
import org.exist.dom.persistent.NodeSet;
import org.exist.dom.persistent.StoredNode;
import org.exist.security.PermissionDeniedException;
import org.exist.source.Source;
import org.exist.source.StringSource;
import org.exist.storage.DBBroker;
import org.exist.storage.XQueryPool;
import org.exist.storage.lock.ManagedDocumentLock;
import org.exist.storage.txn.Txn;
import org.exist.util.LockException;
import org.exist.xquery.CompiledXQuery;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQuery;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.annotation.Nullable;
/**
* Base class for all XUpdate modifications.
*
* @author Wolfgang Meier
*/
public abstract class Modification {
protected final static Logger LOG = LogManager.getLogger(Modification.class);
/** select Statement in the current XUpdate definition;
* defines the set of nodes to which this XUpdate might apply. */
protected String selectStmt = null;
/**
* NodeList to keep track of created document fragments within
* the currently processed XUpdate modification.
* see {@link XUpdateProcessor#contents}
*/
protected NodeList content = null;
protected DBBroker broker;
/** Documents concerned by this XUpdate modification,
* i.e. the set of documents to which this XUpdate might apply. */
protected DocumentSet docs;
protected Map namespaces;
protected Map variables;
protected ManagedLocks lockedDocumentsLocks = null;
protected MutableDocumentSet modifiedDocuments = new DefaultDocumentSet();
protected Int2ObjectMap triggers;
@SuppressWarnings("unused")
private Modification() {}
/**
* Constructor for Modification.
*
* @param broker the database broker
* @param docs the document set
* @param selectStmt the select statement
* @param namespaces the namespace bindings
* @param variables the variable bindings
*/
public Modification(DBBroker broker, DocumentSet docs, String selectStmt,
Map namespaces, Map variables) {
this.selectStmt = selectStmt;
this.broker = broker;
this.docs = docs;
this.namespaces = new HashMap<>(namespaces);
this.variables = new TreeMap<>(variables);
this.triggers = new Int2ObjectOpenHashMap<>();
// DESIGN_QUESTION : wouldn't that be nice to apply selectStmt right here ?
}
/**
* Process the modification. This is the main method that has to be implemented
* by all subclasses.
*
* @param transaction the database transaction
* @return long the number of updates processed
*
* @throws PermissionDeniedException if the caller has insufficient priviledges
* @throws LockException if a lock error occurs
* @throws EXistException if the database raises an error
* @throws XPathException if the XPath raises an error
* @throws TriggerException if a trigger raises an error
*/
public abstract long process(Txn transaction) throws PermissionDeniedException, LockException,
EXistException, XPathException, TriggerException;
public abstract String getName();
public void setContent(NodeList nodes) {
content = nodes;
}
/**
* Evaluate the select expression.
*
* @param docs the documents to evaludate the expression over
*
* @return The selected nodes.
*
* @throws PermissionDeniedException if the caller has insufficient priviledges
* @throws EXistException if the database raises an error
* @throws XPathException if the XPath raises an error
*/
protected NodeList select(DocumentSet docs)
throws PermissionDeniedException, EXistException, XPathException {
final XQuery xquery = broker.getBrokerPool().getXQueryService();
final XQueryPool pool = broker.getBrokerPool().getXQueryPool();
final Source source = new StringSource(selectStmt);
CompiledXQuery compiled = pool.borrowCompiledXQuery(broker, source);
XQueryContext context;
if(compiled == null) {
context = new XQueryContext(broker.getBrokerPool());
} else {
context = compiled.getContext();
context.prepareForReuse();
}
context.setStaticallyKnownDocuments(docs);
declareNamespaces(context);
declareVariables(context);
if(compiled == null)
try {
compiled = xquery.compile(context, source);
} catch (final IOException e) {
throw new EXistException("An exception occurred while compiling the query: " + e.getMessage());
}
Sequence resultSeq = null;
try {
resultSeq = xquery.execute(broker, compiled, null);
} finally {
context.runCleanupTasks();
pool.returnCompiledXQuery(source, compiled);
}
if (!(resultSeq.isEmpty() || Type.subTypeOf(resultSeq.getItemType(), Type.NODE)))
{throw new EXistException("select expression should evaluate to a node-set; got " +
Type.getTypeName(resultSeq.getItemType()));}
if (LOG.isDebugEnabled())
{
LOG.debug("found {} for select: {}", resultSeq.getItemCount(), selectStmt);}
return resultSeq.toNodeSet();
}
/**
* @param context the xquery context
* @throws XPathException if an error occurs whilst declaring the variables
*/
protected void declareVariables(XQueryContext context) throws XPathException {
for (final Map.Entry entry : variables.entrySet()) {
context.declareVariable(entry.getKey(), entry.getValue());
}
}
/**
* @param context the xquery context
* @throws XPathException if an error occurs whilst declaring the namespaces
*/
protected void declareNamespaces(XQueryContext context) throws XPathException {
for (Map.Entry entry : namespaces.entrySet()) {
context.declareNamespace(entry.getKey(), entry.getValue());
}
}
/**
* Acquire a lock on all documents processed by this modification. We have
* to avoid that node positions change during the operation.
* feature trigger_update :
* At the same time we leverage on the fact that it's called before
* database modification to call the eventual triggers.
*
* @param transaction the database transaction.
*
* @return The selected document nodes.
*
* @throws LockException if a lock error occurs
* @throws PermissionDeniedException if the caller has insufficient priviledges
* @throws EXistException if the database raises an error
* @throws XPathException if the XPath raises an error
* @throws TriggerException if a trigger raises an error
*/
protected final StoredNode[] selectAndLock(Txn transaction)
throws LockException, PermissionDeniedException, EXistException,
XPathException, TriggerException {
final java.util.concurrent.locks.Lock globalLock = broker.getBrokerPool().getGlobalUpdateLock();
globalLock.lock();
try {
final NodeList nl = select(docs);
final DocumentSet lockedDocuments = ((NodeSet)nl).getDocumentSet();
// acquire a lock on all documents
// we have to avoid that node positions change
// during the modification
lockedDocumentsLocks = lockedDocuments.lock(broker, true);
final StoredNode ql[] = new StoredNode[nl.getLength()];
for (int i = 0; i < ql.length; i++) {
ql[i] = (StoredNode)nl.item(i);
final DocumentImpl doc = ql[i].getOwnerDocument();
// call the eventual triggers
// TODO -jmv separate loop on docs and not on nodes
//prepare Trigger
prepareTrigger(transaction, doc);
}
return ql;
} finally {
globalLock.unlock();
}
}
/**
* Release all acquired document locks;
* feature trigger_update :
* at the same time we leverage on the fact that it's called after
* database modification to call the eventual triggers
*
* @param transaction the database transaction.
*
* @throws TriggerException if a trigger raises an error
*/
protected final void unlockDocuments(final Txn transaction) throws TriggerException
{
if(lockedDocumentsLocks == null) {
return;
}
try {
//finish Trigger
final Iterator iterator = modifiedDocuments.getDocumentIterator();
while (iterator.hasNext()) {
finishTrigger(transaction, iterator.next());
}
} finally {
triggers.clear();
modifiedDocuments.clear();
//unlock documents
lockedDocumentsLocks.close();
lockedDocumentsLocks = null;
}
}
/**
* Check if any of the modified documents needs defragmentation.
*
* Defragmentation will take place if the number of split pages in the
* document exceeds the limit defined in the configuration file.
*
* @param transaction the database transaction.
* @param docs the documents
*
* @throws EXistException if an error occurs
*/
protected void checkFragmentation(Txn transaction, DocumentSet docs) throws EXistException {
int fragmentationLimit = -1;
final Object property = broker.getBrokerPool().getConfiguration().getProperty(DBBroker.PROPERTY_XUPDATE_FRAGMENTATION_FACTOR);
if (property != null)
{fragmentationLimit = (Integer) property;}
for(final Iterator i = docs.getDocumentIterator(); i.hasNext(); ) {
final DocumentImpl next = i.next();
if(next.getSplitCount() > fragmentationLimit)
{broker.defragXMLResource(transaction, next);}
broker.checkXMLResourceConsistency(next);
}
}
/**
* Fires the prepare function for the UPDATE_DOCUMENT_EVENT trigger for the Document doc
*
* @param transaction The database transaction
* @param doc The document to trigger for
*
* @throws TriggerException if a trigger raises an error
*/
private void prepareTrigger(Txn transaction, DocumentImpl doc) throws TriggerException {
final Collection col = doc.getCollection();
final DocumentTrigger trigger = new DocumentTriggers(broker, transaction, col);
trigger.beforeUpdateDocument(broker, transaction, doc);
triggers.put(doc.getDocId(), trigger);
}
/**
* Fires the finish function for UPDATE_DOCUMENT_EVENT for the documents trigger
*
* @param transaction The transaction
* @param doc The document to trigger for
*
* @throws TriggerException if a trigger raises an error
*/
private void finishTrigger(Txn transaction, DocumentImpl doc) throws TriggerException {
final DocumentTrigger trigger = triggers.get(doc.getDocId());
if(trigger != null)
{trigger.afterUpdateDocument(broker, transaction, doc);}
}
public String toString() {
// buf.append(XMLUtil.dump(content));
return "" + " ";
}
/**
* Get's the parent of the node.
*
* @param node The node of which to retrieve the parent.
*
* @return the parent node, or null if not available
*/
protected @Nullable Node getParent(@Nullable final Node node) {
if (node == null) {
return null;
} else if (node instanceof Attr) {
return ((Attr) node).getOwnerElement();
} else {
return node.getParentNode();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy