/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-06 Wolfgang M. Meier
* wolfgang@exist-db.org
* http://exist.sourceforge.net
*
* This program 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
* of the License, or (at your option) any later version.
*
* This program 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 program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.xmldb;
import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.debuggee.Debuggee;
import org.exist.dom.*;
import org.exist.security.User;
import org.exist.security.xacml.AccessContext;
import org.exist.security.xacml.NullAccessContextException;
import org.exist.source.Source;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.XQueryPool;
import org.exist.storage.lock.Lock;
import org.exist.storage.lock.LockedDocumentMap;
import org.exist.util.LockException;
import org.exist.xquery.*;
import org.exist.xquery.value.AnyURIValue;
import org.exist.xquery.value.Sequence;
import org.xmldb.api.base.*;
import org.xmldb.api.modules.XMLResource;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
public class LocalXPathQueryService implements XPathQueryServiceImpl, XQueryService {
private final static Logger LOG = Logger.getLogger(LocalXPathQueryService.class);
protected BrokerPool brokerPool;
protected LocalCollection collection;
protected User user;
protected TreeMap namespaceDecls = new TreeMap();
protected TreeMap variableDecls = new TreeMap();
protected boolean xpathCompatible = true;
protected String moduleLoadPath = null;
protected Properties properties = null;
protected boolean lockDocuments = false;
protected LockedDocumentMap lockedDocuments = null;
protected DBBroker reservedBroker = null;
protected AccessContext accessCtx;
private LocalXPathQueryService() {}
public LocalXPathQueryService(
User user,
BrokerPool pool,
LocalCollection collection,
AccessContext accessCtx) {
if(accessCtx == null)
throw new NullAccessContextException();
this.accessCtx = accessCtx;
this.user = user;
this.collection = collection;
this.brokerPool = pool;
this.properties = new Properties(collection.properties);
}
public void clearNamespaces() throws XMLDBException {
namespaceDecls.clear();
}
public String getName() throws XMLDBException {
return "XPathQueryService";
}
public String getNamespace(String prefix) throws XMLDBException {
return (String)namespaceDecls.get(prefix);
}
public String getProperty(String property) throws XMLDBException {
return properties.getProperty(property);
}
public String getVersion() throws XMLDBException {
return "1.0";
}
public ResourceSet query(String query) throws XMLDBException {
return query(query, null);
}
public ResourceSet query(XMLResource res, String query) throws XMLDBException {
return query(res, query, null);
}
public ResourceSet query(String query, String sortBy) throws XMLDBException {
XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(collection.getName()) };
return doQuery(query, docs, null, sortBy);
}
public ResourceSet query(XMLResource res, String query, String sortBy)
throws XMLDBException {
NodeProxy node = ((LocalXMLResource) res).getNode();
if (node == null) {
// resource is a document
//TODO : use dedicated function in XmldbURI
XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(res.getParentCollection().getName()).append(res.getDocumentId()) };
return doQuery(query, docs, null, sortBy);
} else {
NodeSet set = new ExtArrayNodeSet(1);
set.add(node);
XmldbURI[] docs = new XmldbURI[] { node.getDocument().getURI() };
return doQuery(query, docs, set, sortBy);
}
}
public ResourceSet execute(CompiledExpression expression) throws XMLDBException {
return execute(null, null, expression, null);
}
public ResourceSet execute(XMLResource res, CompiledExpression expression)
throws XMLDBException {
NodeProxy node = ((LocalXMLResource) res).getNode();
if (node == null) {
// resource is a document
XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(res.getParentCollection().getName()).append(res.getDocumentId()) };
return execute(docs, null, expression, null);
} else {
NodeSet set = new ExtArrayNodeSet(1);
set.add(node);
XmldbURI[] docs = new XmldbURI[] { node.getDocument().getURI() };
return execute(docs, set, expression, null);
}
}
public ResourceSet execute(Source source)
throws XMLDBException {
long start = System.currentTimeMillis();
DBBroker broker = null;
Sequence result;
try {
broker = brokerPool.get(user);
// DocumentSet docs = collection.getCollection().allDocs(broker, new DocumentSet(), true, true);
XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(collection.getName()) };
XQuery xquery = broker.getXQueryService();
XQueryPool pool = xquery.getXQueryPool();
XQueryContext context;
CompiledXQuery compiled = pool.borrowCompiledXQuery(broker, source);
if(compiled == null)
context = xquery.newContext(accessCtx);
else
context = compiled.getContext();
//context.setBackwardsCompatibility(xpathCompatible);
context.setStaticallyKnownDocuments(docs);
if (variableDecls.containsKey(Debuggee.PREFIX+":session")) {
variableDecls.remove(Debuggee.PREFIX+":session");
context.setDebugMode(true);
}
setupContext(context);
if(compiled == null)
compiled = xquery.compile(context, source);
try {
result = xquery.execute(compiled, null, properties);
} finally {
pool.returnCompiledXQuery(source, compiled);
}
} catch (EXistException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (XPathException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (IOException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} finally {
brokerPool.release(broker);
}
LOG.debug("query took " + (System.currentTimeMillis() - start) + " ms.");
if(result != null)
return new LocalResourceSet(user, brokerPool, collection, properties, result, null);
else
return null;
}
public CompiledExpression compile(String query) throws XMLDBException {
try {
return compileAndCheck(query);
} catch (XPathException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
}
}
public CompiledExpression compileAndCheck(String query) throws XMLDBException, XPathException {
DBBroker broker = null;
try {
long start = System.currentTimeMillis();
broker = brokerPool.get(user);
XQuery xquery = broker.getXQueryService();
XQueryContext context = xquery.newContext(accessCtx);
setupContext(context);
CompiledXQuery expr = xquery.compile(context, query);
// checkPragmas(context);
LOG.debug("compilation took " + (System.currentTimeMillis() - start));
return expr;
} catch (EXistException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (IllegalArgumentException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} finally {
brokerPool.release(broker);
}
}
public ResourceSet queryResource(String resource, String query)
throws XMLDBException {
LocalXMLResource res = (LocalXMLResource) collection.getResource(resource);
if (res == null)
throw new XMLDBException(
ErrorCodes.INVALID_RESOURCE,
"resource '" + resource + "' not found");
XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(res.getParentCollection().getName()).append(res.getDocumentId()) };
return doQuery(query, docs, null, null);
}
protected void setupContext(XQueryContext context) throws XMLDBException, XPathException {
try {
context.setBaseURI(new AnyURIValue(properties.getProperty("base-uri", collection.getPath())));
} catch(XPathException e) {
throw new XMLDBException(ErrorCodes.INVALID_URI,"Invalid base uri",e);
}
if(moduleLoadPath != null)
context.setModuleLoadPath(moduleLoadPath);
Map.Entry entry;
// declare namespace/prefix mappings
for (Iterator i = namespaceDecls.entrySet().iterator(); i.hasNext();) {
entry = (Map.Entry) i.next();
context.declareNamespace(
(String) entry.getKey(),
(String) entry.getValue());
}
// declare static variables
for (Iterator i = variableDecls.entrySet().iterator(); i.hasNext();) {
entry = (Map.Entry) i.next();
context.declareVariable((String) entry.getKey(), entry.getValue());
}
//context.setBackwardsCompatibility(xpathCompatible);
}
/**
* Check if the XQuery contains pragmas that define serialization settings.
* If yes, copy the corresponding settings to the current set of output properties.
*
* @param context
*/
// private void checkPragmas(XQueryContext context) throws XPathException {
// Option pragma = context.getOption(Option.SERIALIZE_QNAME);
// if(pragma == null)
// return;
// String[] contents = pragma.tokenizeContents();
// for(int i = 0; i < contents.length; i++) {
// String[] pair = Option.parseKeyValuePair(contents[i]);
// if(pair == null)
// throw new XPathException("Unknown parameter found in " + pragma.getQName().getStringValue() +
// ": '" + contents[i] + "'");
// LOG.debug("Setting serialization property from pragma: " + pair[0] + " = " + pair[1]);
// properties.setProperty(pair[0], pair[1]);
// }
// }
private ResourceSet doQuery(
String query,
XmldbURI[] docs,
NodeSet contextSet,
String sortExpr)
throws XMLDBException {
CompiledExpression expr = compile(query);
return execute(docs, contextSet, expr, sortExpr);
}
/**
* Execute all following queries in a protected environment.
* Protected means: it is guaranteed that documents referenced by the
* query or the result set are not modified by other threads
* until {@link #endProtected} is called.
*/
public void beginProtected() throws XMLDBException {
// lockDocuments = true;
// if (reservedBroker != null)
// if a previous broker was not properly released, do it now (just to be sure)
// brokerPool.release(reservedBroker);
try {
boolean deadlockCaught;
do {
reservedBroker = brokerPool.get(user);
deadlockCaught = false;
MutableDocumentSet docs = null;
try {
org.exist.collections.Collection coll = collection.getCollection();
lockedDocuments = new LockedDocumentMap();
docs = new DefaultDocumentSet();
coll.allDocs(reservedBroker, docs, true, lockedDocuments, Lock.WRITE_LOCK);
} catch (LockException e) {
LOG.debug("Deadlock detected. Starting over again. Docs: " + docs.getDocumentCount() + "; locked: " +
lockedDocuments.size());
lockedDocuments.unlock();
brokerPool.release(reservedBroker);
deadlockCaught = true;
}
} while (deadlockCaught);
} catch (EXistException e) {
brokerPool.release(reservedBroker);
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage());
}
}
/**
* Close the protected environment. All locks held
* by the current thread are released. The result set
* is no longer guaranteed to be stable.
*/
public void endProtected() {
lockDocuments = false;
if(lockedDocuments != null) {
lockedDocuments.unlock();
}
lockedDocuments = null;
if (reservedBroker != null)
brokerPool.release(reservedBroker);
reservedBroker = null;
}
public void removeNamespace(String ns) throws XMLDBException {
for (Iterator i = namespaceDecls.values().iterator(); i.hasNext();) {
if (((String) i.next()).equals(ns)) {
i.remove();
}
}
}
private ResourceSet execute(XmldbURI[] docs,
NodeSet contextSet, CompiledExpression expression, String sortExpr)
throws XMLDBException {
long start = System.currentTimeMillis();
CompiledXQuery expr = (CompiledXQuery)expression;
DBBroker broker = null;
Sequence result;
XQueryContext context = expr.getContext();
try {
broker = brokerPool.get(user);
//context.setBackwardsCompatibility(xpathCompatible);
context.setStaticallyKnownDocuments(docs);
if (lockedDocuments != null)
context.setProtectedDocs(lockedDocuments);
setupContext(context);
// checkPragmas(context);
XQuery xquery = broker.getXQueryService();
result = xquery.execute(expr, contextSet, properties);
} catch (EXistException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (XPathException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (Exception e) {
// need to catch all runtime exceptions here to be able to release locked documents
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} finally {
// if (keepLocks)
// reservedBroker = broker;
// else
brokerPool.release(broker);
}
LOG.debug("query took " + (System.currentTimeMillis() - start) + " ms.");
if(result != null)
return new LocalResourceSet(user, brokerPool, collection, properties, result, sortExpr);
else
return null;
}
public void setCollection(Collection col) throws XMLDBException {
}
public void setNamespace(String prefix, String namespace) throws XMLDBException {
namespaceDecls.put(prefix, namespace);
}
public void setProperty(String property, String value) throws XMLDBException {
properties.setProperty(property, value);
}
/* (non-Javadoc)
* @see org.exist.xmldb.XPathQueryServiceImpl#declareVariable(java.lang.String, java.lang.Object)
*/
public void declareVariable(String qname, Object initialValue)
throws XMLDBException {
variableDecls.put(qname, initialValue);
}
/* (non-Javadoc)
* @see org.exist.xmldb.XQueryService#setXPathCompatibility(boolean)
*/
public void setXPathCompatibility(boolean backwardsCompatible) {
this.xpathCompatible = backwardsCompatible;
}
/* (non-Javadoc)
* @see org.exist.xmldb.XQueryService#setModuleLoadPath(java.lang.String)
*/
public void setModuleLoadPath(String path) {
moduleLoadPath = path;
}
/* (non-Javadoc)
* @see org.exist.xmldb.XQueryService#dump(org.exist.xmldb.CompiledExpression, java.io.Writer)
*/
public void dump(CompiledExpression expression, Writer writer) throws XMLDBException {
CompiledXQuery expr = (CompiledXQuery)expression;
expr.dump(writer);
}
}