/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id: XUpdateImpl.java 511426 2007-02-25 03:25:02Z vgritsenko $
*/
package org.apache.xindice.core.xupdate;
import org.apache.xindice.core.Collection;
import org.apache.xindice.core.data.NodeSet;
import org.apache.xindice.xml.NamespaceMap;
import org.apache.xindice.xml.NodeSource;
import org.apache.xindice.xml.dom.CompressedNode;
import org.apache.xindice.xml.dom.DBNode;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import org.xmldb.xupdate.lexus.XUpdateQueryImpl;
import org.xmldb.xupdate.lexus.commands.CommandConstants;
import org.xmldb.xupdate.lexus.commands.CommandObject;
import org.xmldb.xupdate.lexus.commands.DefaultCommand;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
/**
* Provides Collection and document based XUpdate capabilities.
*
* For more detail about XUpdate look at the
* <a href="http://xmldb-org.sourceforge.net/xupdate/xupdate-wd.html">XUpdate Working Draft</a>.
*
* @version $Revision: 511426 $, $Date: 2007-02-24 22:25:02 -0500 (Sat, 24 Feb 2007) $
*/
public class XUpdateImpl extends XUpdateQueryImpl {
/**
* If set to true, then namespaces set explicitly via an API call will take precendence.
* If set to false, then namespaces set implicitly within query string will take precedence.
*/
private static final boolean API_NS_PRECEDENCE = true;
protected int nodesModified;
protected NamespaceMap nsMap;
/**
* Set the namespace map to be used when resolving queries
*/
public void setNamespaceMap(NamespaceMap nsMap) {
if (nsMap == null) {
return;
}
if (this.nsMap == null) {
this.nsMap = nsMap;
} else {
this.nsMap.includeNamespaces(nsMap, API_NS_PRECEDENCE);
}
}
/**
* Sets the query string to be used when executing update
*/
public void setQString(String query) throws SAXException {
super.setQString(query);
if (nsMap == null) {
nsMap = new NamespaceMap();
}
nsMap.includeNamespaces(super.namespaces, !API_NS_PRECEDENCE);
}
/**
* Execute the XUpdate commands against a document.
*/
public void execute(Node contextNode) throws Exception {
CommandObject currentCommand = new DefaultCommand(contextNode);
Enumeration commands = super.query[0].elements();
Enumeration attributes = super.query[1].elements();
Enumeration characters = super.query[2].elements();
Node origNode = contextNode;
CommandObject.getXPath().setNamespace(nsMap.getContextNode());
while (commands.hasMoreElements()) {
int id = ((Integer) commands.nextElement()).intValue();
if (id == CommandConstants.ATTRIBUTES) {
currentCommand.submitAttributes((Hashtable) attributes.nextElement());
} else if (id == CommandConstants.CHARACTERS) {
currentCommand.submitCharacters((String) characters.nextElement());
} else if (id > 0) {
if (!currentCommand.submitInstruction(id)) {
super.commandConstants.setContextNode(contextNode);
currentCommand = super.commandConstants.commandForID(id);
if (currentCommand == null) {
throw new Exception("Operation can not have any XUpdate-instruction!");
}
currentCommand.reset();
}
} else {
if (!currentCommand.executeInstruction()) {
try {
contextNode = currentCommand.execute();
} catch (Exception e) {
// While not ideal, CommandObject.execute throws
// Exception("no nodes selected !") if nothing is
// selected for modification we trap that case
// and ignore allowing continued processing
// of remaining xupdate instructions that may be present
if (!"no nodes selected !".equals(e.getMessage())) {
throw e;
}
}
// Default do-nothing command will soak up anything
// (characters, attributes, etc.) encountered until we
// come across the next xupdate instruction
// (e.g. remove, append, insert, etc.)
currentCommand = new DefaultCommand(contextNode);
}
}
}
if (origNode instanceof CompressedNode) {
CompressedNode cn = (CompressedNode) origNode;
if (cn.isDirty()) {
nodesModified++;
}
}
}
/**
* Execute the set of XUpdate commands against a collection.
*
* @param col The collection against which the command will be executed
* @exception Exception Description of Exception
*/
public void execute(Collection col) throws Exception {
int attribIndex = 0;
// TODO: Don't cache all the documents in memory.
// Need to keep updated documents in memory so that can
// 'rollback' all the changes in case of failure.
// Won't need this in case underlying collection supports
// transaction.
HashMap docsUpdated = new HashMap();
for (int i = 0; i < super.query[0].size(); i++) {
int cmdID = ((Integer) super.query[0].elementAt(i)).intValue();
if (cmdID == CommandConstants.ATTRIBUTES) {
Hashtable attribs = (Hashtable) super.query[1].elementAt(attribIndex);
String selector = (String) attribs.get("select");
attribIndex++;
// If we found an XPath selector we need to execute the commands,
// but we can not execute xupdate variables again.
// all variables start with a '$'
if (selector != null && !selector.startsWith("$")) {
NodeSet ns = col.queryCollection("XPath", selector, nsMap);
while (ns != null && ns.hasMoreNodes()) {
DBNode node = (DBNode) ns.getNextNode();
Document doc = node.getOwnerDocument();
NodeSource source = node.getSource();
if (docsUpdated.containsKey(source.getKey())) {
continue; // We only have to process it once
} else {
docsUpdated.put(source.getKey(), doc);
}
execute(doc.getDocumentElement());
}
}
}
}
// Update all documents at once
// this way we don't get any half run xupdate commands.
Iterator i = docsUpdated.entrySet().iterator();
while (i.hasNext()) {
Map.Entry set = (Map.Entry) i.next();
col.setDocument(set.getKey(), (Document) set.getValue());
}
}
public int getModifiedCount() {
return nodesModified;
}
}