/*
* 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.security.PermissionDeniedException;
import org.exist.security.Subject;
import org.exist.security.xacml.AccessContext;
import org.exist.security.xacml.NullAccessContextException;
import org.exist.source.DBSource;
import org.exist.source.FileSource;
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.value.AnyURIValue;
import org.exist.xquery.value.Sequence;
import org.w3c.dom.Node;
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;
import org.exist.dom.BinaryDocument;
import org.exist.dom.DefaultDocumentSet;
import org.exist.dom.DocumentImpl;
import org.exist.dom.ExtArrayNodeSet;
import org.exist.dom.MutableDocumentSet;
import org.exist.dom.NodeProxy;
import org.exist.dom.NodeSet;
import org.exist.security.Permission;
import org.exist.xquery.CompiledXQuery;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQuery;
import org.exist.xquery.XQueryContext;
public class LocalXPathQueryService implements XPathQueryServiceImpl, XQueryService {
private final static Logger LOG = Logger.getLogger(LocalXPathQueryService.class);
protected BrokerPool brokerPool;
protected LocalCollection collection;
protected Subject user;
protected TreeMap<String, String> namespaceDecls = new TreeMap<String, String>();
protected TreeMap<String, Object> variableDecls = new TreeMap<String, Object>();
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;
@SuppressWarnings("unused")
private LocalXPathQueryService() {}
public LocalXPathQueryService(
Subject 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 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 {
final 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 {
final Node n = ((LocalXMLResource) res).root;
if (n != null && n instanceof org.exist.memtree.NodeImpl) {
final XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(res.getParentCollection().getName()) };
return doQuery(query, docs, (org.exist.memtree.NodeImpl)n, sortBy);
}
final NodeProxy node = ((LocalXMLResource) res).getNode();
if (node == null) {
// resource is a document
//TODO : use dedicated function in XmldbURI
final XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(res.getParentCollection().getName()).append(res.getDocumentId()) };
return doQuery(query, docs, null, sortBy);
} else {
final NodeSet set = new ExtArrayNodeSet(1);
set.add(node);
final 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 {
final NodeProxy node = ((LocalXMLResource) res).getNode();
if (node == null) {
// resource is a document
final XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(res.getParentCollection().getName()).append(res.getDocumentId()) };
return execute(docs, null, expression, null);
} else {
final NodeSet set = new ExtArrayNodeSet(1);
set.add(node);
final XmldbURI[] docs = new XmldbURI[] { node.getDocument().getURI() };
return execute(docs, set, expression, null);
}
}
public ResourceSet execute(Source source)
throws XMLDBException {
final long start = System.currentTimeMillis();
final Subject preserveSubject = brokerPool.getSubject();
DBBroker broker = null;
Sequence result;
try {
broker = brokerPool.get(user);
// DocumentSet docs = collection.getCollection().allDocs(broker, new DocumentSet(), true, true);
final XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(collection.getName()) };
final XQuery xquery = broker.getXQueryService();
final 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")) {
context.declareVariable(Debuggee.SESSION, variableDecls.get(Debuggee.PREFIX+":session"));
variableDecls.remove(Debuggee.PREFIX+":session");
}
setupContext(source, context);
if(compiled == null)
{compiled = xquery.compile(context, source);}
try {
result = xquery.execute(compiled, null, properties);
} finally {
pool.returnCompiledXQuery(source, compiled);
}
} catch (final EXistException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (final XPathException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (final IOException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (final PermissionDeniedException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} finally {
brokerPool.release(broker);
brokerPool.setSubject(preserveSubject);
}
LOG.debug("query took " + (System.currentTimeMillis() - start) + " ms.");
if(result != null)
{return new LocalResourceSet(user, brokerPool, collection, properties, result, null);}
else
{return null;}
}
@Override
public ResourceSet executeStoredQuery(final String uri) throws XMLDBException {
final Subject preserveSubject = brokerPool.getSubject();
DBBroker broker = null;
final Sequence result;
try {
broker = brokerPool.get(user);
final DocumentImpl resource = broker.getResource(new XmldbURI(uri), Permission.READ | Permission.EXECUTE);
if(resource == null) {
throw new XMLDBException(ErrorCodes.INVALID_URI, "No stored XQuery exists at: " + uri);
}
final Source xquerySource = new DBSource(broker, (BinaryDocument)resource, false);
return execute(xquerySource);
} catch(final EXistException ee) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, ee.getMessage(), ee);
} catch(final PermissionDeniedException pde) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, pde.getMessage(), pde);
} finally {
if(broker != null) {
brokerPool.release(broker);
}
brokerPool.setSubject(preserveSubject);
}
}
public CompiledExpression compile(String query) throws XMLDBException {
try {
return compileAndCheck(query);
} catch (final XPathException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
}
}
public CompiledExpression compileAndCheck(String query) throws XMLDBException, XPathException {
final Subject preserveSubject = brokerPool.getSubject();
DBBroker broker = null;
try {
final long start = System.currentTimeMillis();
broker = brokerPool.get(user);
final XQuery xquery = broker.getXQueryService();
final XQueryContext context = xquery.newContext(accessCtx);
setupContext(null, context);
final CompiledXQuery expr = xquery.compile(context, query);
// checkPragmas(context);
LOG.debug("compilation took " + (System.currentTimeMillis() - start));
return expr;
} catch (final EXistException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (final IllegalArgumentException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (final PermissionDeniedException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} finally {
brokerPool.release(broker);
brokerPool.setSubject(preserveSubject);
}
}
public ResourceSet queryResource(String resource, String query)
throws XMLDBException {
final LocalXMLResource res = (LocalXMLResource) collection.getResource(resource);
if (res == null)
{throw new XMLDBException(
ErrorCodes.INVALID_RESOURCE,
"resource '" + resource + "' not found");}
final XmldbURI[] docs = new XmldbURI[] { XmldbURI.create(res.getParentCollection().getName()).append(res.getDocumentId()) };
return doQuery(query, docs, null, null);
}
protected void setupContext(Source source, XQueryContext context) throws XMLDBException, XPathException {
try {
context.setBaseURI(new AnyURIValue(properties.getProperty("base-uri", collection.getPath())));
} catch(final XPathException e) {
throw new XMLDBException(ErrorCodes.INVALID_URI,"Invalid base uri",e);
}
if(moduleLoadPath != null)
{context.setModuleLoadPath(moduleLoadPath);}
else if (source != null) {
// context.setModuleLoadPath(XmldbURI.EMBEDDED_SERVER_URI_PREFIX + collection.getPath());
String modulePath = null;
if (source instanceof DBSource) {
modulePath = ((DBSource) source).getDocumentPath().removeLastSegment().toString();
} else if (source instanceof FileSource) {
modulePath = ((FileSource) source).getFile().getParent();
}
if (modulePath != null)
{context.setModuleLoadPath(modulePath);}
}
// declare namespace/prefix mappings
for (final Map.Entry<String, String> entry : namespaceDecls.entrySet()) {
context.declareNamespace(
entry.getKey(),
entry.getValue());
}
// declare static variables
for (final Map.Entry<String, Object> entry : variableDecls.entrySet()) {
context.declareVariable(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,
Sequence contextSet,
String sortExpr)
throws XMLDBException {
final 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 {
final org.exist.collections.Collection coll = collection.getCollection();
lockedDocuments = new LockedDocumentMap();
docs = new DefaultDocumentSet();
coll.allDocs(reservedBroker, docs, true, lockedDocuments, Lock.WRITE_LOCK);
} catch (final LockException e) {
LOG.debug("Deadlock detected. Starting over again. Docs: " + docs.getDocumentCount() + "; locked: " +
lockedDocuments.size());
lockedDocuments.unlock();
brokerPool.release(reservedBroker);
deadlockCaught = true;
} catch (final PermissionDeniedException e) {
throw new XMLDBException(ErrorCodes.PERMISSION_DENIED,
"Permission denied on document");
}
} while (deadlockCaught);
} catch (final 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 (final Iterator<String> i = namespaceDecls.values().iterator(); i.hasNext();) {
if (i.next().equals(ns)) {
i.remove();
}
}
}
private ResourceSet execute(XmldbURI[] docs,
Sequence contextSet, CompiledExpression expression, String sortExpr)
throws XMLDBException {
final long start = System.currentTimeMillis();
final CompiledXQuery expr = (CompiledXQuery)expression;
final Subject preserveSubject = brokerPool.getSubject();
DBBroker broker = null;
Sequence result;
final XQueryContext context = expr.getContext();
try {
broker = brokerPool.get(user);
//context.setBackwardsCompatibility(xpathCompatible);
context.setStaticallyKnownDocuments(docs);
if (lockedDocuments != null)
{context.setProtectedDocs(lockedDocuments);}
setupContext(null, context);
// checkPragmas(context);
final XQuery xquery = broker.getXQueryService();
result = xquery.execute(expr, contextSet, properties);
} catch (final EXistException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (final XPathException e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
} catch (final 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);
brokerPool.setSubject(preserveSubject);
}
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 {
final CompiledXQuery expr = (CompiledXQuery)expression;
expr.dump(writer);
}
}