/* * 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.xquery; import antlr.RecognitionException; import antlr.TokenStreamException; import antlr.collections.AST; import org.apache.log4j.Logger; import org.exist.EXistException; import org.exist.Namespaces; import org.exist.collections.Collection; import org.exist.collections.CollectionConfiguration; import org.exist.collections.CollectionConfigurationException; import org.exist.collections.triggers.DocumentTrigger; import org.exist.collections.triggers.Trigger; import org.exist.collections.triggers.TriggerStatePerThread; import org.exist.debuggee.DebuggeeJoint; import org.exist.dom.*; import org.exist.http.servlets.SessionWrapper; import org.exist.memtree.DocBuilder; import org.exist.memtree.InMemoryXMLStreamReader; import org.exist.memtree.MemTreeBuilder; import org.exist.memtree.NodeImpl; import org.exist.numbering.NodeId; import org.exist.security.Permission; import org.exist.security.PermissionDeniedException; import org.exist.security.User; import org.exist.security.xacml.AccessContext; import org.exist.security.xacml.ExistPDP; import org.exist.security.xacml.NullAccessContextException; import org.exist.security.xacml.XACMLSource; import org.exist.source.DBSource; import org.exist.source.Source; import org.exist.source.SourceFactory; import org.exist.stax.ExtendedXMLStreamReader; import org.exist.storage.DBBroker; import org.exist.storage.UpdateListener; import org.exist.storage.lock.Lock; import org.exist.storage.lock.LockedDocumentMap; import org.exist.storage.txn.TransactionException; import org.exist.storage.txn.TransactionManager; import org.exist.storage.txn.Txn; import org.exist.util.Collations; import org.exist.util.Configuration; import org.exist.util.DatabaseConfigurationException; import org.exist.util.LockException; import org.exist.util.hashtable.NamePool; import org.exist.xmldb.XmldbURI; import org.exist.xquery.functions.session.SessionModule; import org.exist.xquery.parser.XQueryLexer; import org.exist.xquery.parser.XQueryParser; import org.exist.xquery.parser.XQueryTreeParser; import org.exist.xquery.pragmas.*; import org.exist.xquery.value.*; import org.exist.xquery.update.Modification; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.stream.XMLStreamException; import java.io.IOException; import java.io.Reader; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.text.Collator; import java.util.ArrayList; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.SimpleTimeZone; import java.util.Stack; import java.util.TimeZone; import java.util.TreeMap; /** * The current XQuery execution context. Contains the static as well * as the dynamic XQuery context components. * * @author Wolfgang Meier (wolfgang@exist-db.org) */ public class XQueryContext { public static final String CONFIGURATION_ELEMENT_NAME = "xquery"; public static final String CONFIGURATION_MODULES_ELEMENT_NAME = "builtin-modules"; public static final String ENABLE_QUERY_REWRITING_ATTRIBUTE = "enable-query-rewriting"; public static final String XQUERY_BACKWARD_COMPATIBLE_ATTRIBUTE = "backwardCompatible"; public static final String XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_ATTRIBUTE = "raise-error-on-failed-retrieval"; //TODO : move elsewhere ? public static final String CONFIGURATION_MODULE_ELEMENT_NAME = "module"; public static final String BUILT_IN_MODULE_URI_ATTRIBUTE = "uri"; public static final String BUILT_IN_MODULE_CLASS_ATTRIBUTE = "class"; public static final String BUILT_IN_MODULE_SOURCE_ATTRIBUTE = "src"; public static final String PROPERTY_XQUERY_BACKWARD_COMPATIBLE = "xquery.backwardCompatible"; public static final String PROPERTY_ENABLE_QUERY_REWRITING = "xquery.enable-query-rewriting"; public static final String PROPERTY_XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL = "xquery.raise-error-on-failed-retrieval"; public static final boolean XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_DEFAULT = false; //TODO : move elsewhere ? public static final String PROPERTY_BUILT_IN_MODULES = "xquery.modules"; public static final String PROPERTY_STATIC_MODULE_MAP = "xquery.modules.static"; private static final String JAVA_URI_START = "java:"; //private static final String XMLDB_URI_START = "xmldb:exist://"; protected final static Logger LOG = Logger.getLogger(XQueryContext.class); private static final String TEMP_STORE_ERROR = "Error occurred while storing temporary data"; // Static namespace/prefix mappings protected HashMap staticNamespaces = new HashMap(); // Static prefix/namespace mappings protected HashMap staticPrefixes = new HashMap(); // Local in-scope namespace/prefix mappings in the current context protected HashMap inScopeNamespaces = new HashMap(); // Local prefix/namespace mappings in the current context protected HashMap inScopePrefixes = new HashMap(); // Inherited in-scope namespace/prefix mappings in the current context protected HashMap inheritedInScopeNamespaces = new HashMap(); // Inherited prefix/namespace mappings in the current context protected HashMap inheritedInScopePrefixes = new HashMap(); protected HashMap mappedModules = new HashMap(); private boolean preserveNamespaces = true; private boolean inheritNamespaces = true; // Local namespace stack protected Stack namespaceStack = new Stack(); // Known user defined functions in the local module protected TreeMap declaredFunctions = new TreeMap(); // Globally declared variables protected Map<QName, Variable> globalVariables = new TreeMap<QName, Variable>(); // The last element in the linked list of local in-scope variables protected LocalVariable lastVar = null; protected Stack<LocalVariable> contextStack = new Stack<LocalVariable>(); protected Stack callStack = new Stack(); // The current size of the variable stack protected int variableStackSize = 0; // Unresolved references to user defined functions protected Stack forwardReferences = new Stack(); // List of options declared for this query at compile time - i.e. declare option protected List staticOptions = null; // List of options declared for this query at run time - i.e. util:declare-option() protected List dynamicOptions = null; //The Calendar for this context : may be changed by some options XMLGregorianCalendar calendar = null; TimeZone implicitTimeZone = null; /** * the watchdog object assigned to this query */ protected XQueryWatchDog watchdog; /** * Loaded modules. */ protected HashMap<String,Module> modules = new HashMap<String,Module>(); /** * Loaded modules, including ones bubbled up from imported modules. */ protected HashMap<String,Module> allModules = new HashMap<String,Module>(); /** * Whether some modules were rebound to new instances since the last time this context's * query was analyzed. (This assumes that each context is attached to at most one query.) */ private boolean modulesChanged = true; /** * The set of statically known documents specified as * an array of paths to documents and collections. */ protected XmldbURI[] staticDocumentPaths = null; /** * The actual set of statically known documents. This * will be generated on demand from staticDocumentPaths. */ protected DocumentSet staticDocuments = null; /** * The set of statically known documents specified as * an array of paths to documents and collections. */ protected XmldbURI[] staticCollections = null; /** * A set of documents which were modified during the query, * usually through an XQuery update extension. The documents * will be checked after the query completed to see if a * defragmentation run is needed. */ protected MutableDocumentSet modifiedDocuments = null; /** * The main database broker object providing access * to storage and indexes. Every XQuery has its own * DBBroker object. */ protected DBBroker broker; /** * A general-purpose map to set attributes in the current * query context. */ protected Map attributes = new HashMap(); protected AnyURIValue baseURI = AnyURIValue.EMPTY_URI; protected boolean baseURISetInProlog = false; protected String moduleLoadPath = "."; protected String defaultFunctionNamespace = Function.BUILTIN_FUNCTION_NS; protected AnyURIValue defaultElementNamespace = AnyURIValue.EMPTY_URI; protected AnyURIValue defaultElementNamespaceSchema = AnyURIValue.EMPTY_URI; /** * The default collation URI */ private String defaultCollation = Collations.CODEPOINT; /** * Default Collator. Will be null for the default unicode codepoint collation. */ private Collator defaultCollator = null; /** * Set to true to enable XPath 1.0 * backwards compatibility. */ private boolean backwardsCompatible = false; /** * Should whitespace inside node constructors be stripped? */ private boolean stripWhitespace = true; /** * Should empty order greatest or least? */ private boolean orderEmptyGreatest = true; /** * The position of the currently processed item in the context * sequence. This field has to be set on demand, for example, * before calling the fn:position() function. */ private int contextPosition = 0; /** * The builder used for creating in-memory document * fragments */ private MemTreeBuilder builder = null; /** * Shared name pool used by all in-memory documents constructed in * this query context. */ private NamePool sharedNamePool = null; /** * Stack for temporary document fragments */ private Stack fragmentStack = new Stack(); /** * The root of the expression tree */ private Expression rootExpression; /** * An incremental counter to count the expressions in * the current XQuery. Used during compilation to assign * a unique ID to every expression. */ private int expressionCounter = 0; /** * Should all documents loaded by the query be locked? * If set to true, it is the responsibility of the calling client * code to unlock documents after the query has completed. */ // private boolean lockDocumentsOnLoad = false; /** * Documents locked during the query. */ // private LockedDocumentMap lockedDocuments = null; private LockedDocumentMap protectedDocuments = null; /** * The profiler instance used by this context. */ private Profiler profiler; //For holding XQuery Context variables for general storage in the XQuery Context HashMap XQueryContextVars = new HashMap(); public static final String XQUERY_CONTEXTVAR_XQUERY_UPDATE_ERROR = "_eXist_xquery_update_error"; public static final String HTTP_SESSIONVAR_XMLDB_USER = "_eXist_xmldb_user"; //Transaction for batched xquery updates private Txn batchTransaction = null; private MutableDocumentSet batchTransactionTriggers = new DefaultDocumentSet(); private AccessContext accessCtx; private ContextUpdateListener updateListener = null; private boolean enableOptimizer = true; private boolean raiseErrorOnFailedRetrieval = XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_DEFAULT; private boolean isShared = false; private XACMLSource source = null; private boolean analyzed = false; private XQueryContext() {} protected XQueryContext(AccessContext accessCtx) { if(accessCtx == null) throw new NullAccessContextException(); this.accessCtx = accessCtx; builder = new MemTreeBuilder(this); builder.startDocument(); profiler = new Profiler(null); } public XQueryContext(DBBroker broker, AccessContext accessCtx) { this(accessCtx); this.broker = broker; loadDefaults(broker.getConfiguration()); this.profiler = new Profiler(broker.getBrokerPool()); } public XQueryContext(XQueryContext copyFrom) { this(copyFrom.getAccessContext()); this.broker = copyFrom.broker; loadDefaultNS(); Iterator prefixes = copyFrom.staticNamespaces.keySet().iterator(); while (prefixes.hasNext()) { String prefix = (String)prefixes.next(); if (prefix.equals("xml") || prefix.equals("xmlns")) { continue; } try { declareNamespace(prefix,(String)copyFrom.staticNamespaces.get(prefix)); } catch (XPathException ex) { ex.printStackTrace(); } } this.profiler = copyFrom.profiler; } /** * Returns true if this context has a parent context * (means it is a module context). * * @return False. */ public boolean hasParent() { return false; } public XQueryContext getRootContext() { return this; } public XQueryContext copyContext() { XQueryContext ctx = new XQueryContext(this); copyFields(ctx); return ctx; } /** * Update the current dynamic context using the properties * of another context. This is needed by * {@link org.exist.xquery.functions.util.Eval}. * * @param from */ public void updateContext(XQueryContext from) { this.watchdog = from.watchdog; this.lastVar = from.lastVar; this.variableStackSize = from.getCurrentStackSize(); this.contextStack = from.contextStack; this.inScopeNamespaces = from.inScopeNamespaces; this.inScopePrefixes = from.inScopePrefixes; this.inheritedInScopeNamespaces = from.inheritedInScopeNamespaces; this.inheritedInScopePrefixes = from.inheritedInScopePrefixes; this.variableStackSize = from.variableStackSize; this.attributes = from.attributes; this.updateListener = from.updateListener; this.modules = from.modules; this.allModules = from.allModules; this.mappedModules = from.mappedModules; } protected void copyFields(XQueryContext ctx) { ctx.calendar = this.calendar; ctx.implicitTimeZone = this.implicitTimeZone; ctx.baseURI = this.baseURI; ctx.baseURISetInProlog = this.baseURISetInProlog; ctx.staticDocumentPaths = this.staticDocumentPaths; ctx.staticDocuments = this.staticDocuments; ctx.moduleLoadPath = this.moduleLoadPath; ctx.defaultFunctionNamespace = this.defaultFunctionNamespace; ctx.defaultElementNamespace = this.defaultElementNamespace; ctx.defaultCollation = this.defaultCollation; ctx.defaultCollator = this.defaultCollator; ctx.backwardsCompatible = this.backwardsCompatible; ctx.enableOptimizer = this.enableOptimizer; ctx.stripWhitespace = this.stripWhitespace; ctx.preserveNamespaces = this.preserveNamespaces; ctx.inheritNamespaces = this.inheritNamespaces; ctx.orderEmptyGreatest = this.orderEmptyGreatest; ctx.declaredFunctions = new TreeMap(this.declaredFunctions); ctx.globalVariables = new TreeMap<QName, Variable>(this.globalVariables); // make imported modules available in the new context ctx.modules = new HashMap(); for (Iterator i = this.modules.values().iterator(); i.hasNext(); ) { try { Module module = (Module) i.next(); ctx.modules.put(module.getNamespaceURI(), module); String prefix = (String) this.staticPrefixes.get(module.getNamespaceURI()); ctx.declareNamespace(prefix, module.getNamespaceURI()); } catch (XPathException e) { // ignore } } ctx.allModules = new HashMap(); for (Iterator i = this.allModules.values().iterator(); i.hasNext(); ) { Module module = (Module) i.next(); if (module != null) ctx.allModules.put(module.getNamespaceURI(), module); } ctx.watchdog = this.watchdog; ctx.lastVar = this.lastVar; ctx.variableStackSize = getCurrentStackSize(); ctx.contextStack = this.contextStack; ctx.mappedModules = new HashMap(this.mappedModules); ctx.staticNamespaces = new HashMap(this.staticNamespaces); ctx.staticPrefixes = new HashMap(this.staticPrefixes); } /** * Prepares the current context before xquery execution */ public void prepare() { //if there is an existing user in the current http session //then set the DBBroker user User user = getUserFromHttpSession(); if(user != null) { broker.setUser(user); } //Reset current context position setContextPosition(0); //Note that, for some reasons, an XQueryContext might be used without calling this method } public AccessContext getAccessContext() { return accessCtx; } /** * @return true if profiling is enabled for this context. */ public boolean isProfilingEnabled() { return profiler.isEnabled(); } public boolean isProfilingEnabled(int verbosity) { return profiler.isEnabled() && profiler.verbosity() >= verbosity; } /** * Returns the {@link Profiler} instance of this context * if profiling is enabled. * * @return the profiler instance. */ public Profiler getProfiler() { return profiler; } /** * Called from the XQuery compiler to set the root expression * for this context. * * @param expr */ public void setRootExpression(Expression expr) { this.rootExpression = expr; } /** * Returns the root expression of the XQuery associated with * this context. * * @return root expression */ public Expression getRootExpression() { return rootExpression; } /** * Returns the next unique expression id. Every expression * in the XQuery is identified by a unique id. During compilation, * expressions are assigned their id by calling this method. * * @return The next unique expression id. */ protected int nextExpressionId() { return expressionCounter++; } /** * Returns the number of expression objects in the internal * representation of the query. Used to estimate the size * of the query. * * @return number of expression objects */ public int getExpressionCount() { return expressionCounter; } public void setSource(XACMLSource source) { this.source = source; } public XACMLSource getSource() { return source; } /** * Returns the Source Key of the XQuery associated with * this context. * * @return source key */ public String getSourceKey() { return source.getKey(); } /** * Returns the Source Type of the XQuery associated with * this context. * * @return source type */ public String getSourceType() { return source.getType(); } /** * Declare a user-defined static prefix/namespace mapping. * * eXist internally keeps a table containing all prefix/namespace * mappings it found in documents, which have been previously * stored into the database. These default mappings need not to be * declared explicitely. * * @param prefix * @param uri */ public void declareNamespace(String prefix, String uri) throws XPathException { if (prefix == null) prefix = ""; if(uri == null) uri = ""; if (prefix.equals("xml") || prefix.equals("xmlns")) throw new XPathException("err:XQST0070: Namespace predefined prefix '" + prefix + "' can not be bound"); if (uri.equals(Namespaces.XML_NS)) throw new XPathException("err:XQST0070: Namespace URI '" + uri + "' must be bound to the 'xml' prefix"); final String prevURI = (String)staticNamespaces.get(prefix); //This prefix was not bound if(prevURI == null ) { //Bind it if (uri.length() > 0) { staticNamespaces.put(prefix, uri); staticPrefixes.put(uri, prefix); return; } //Nothing to bind else { //TODO : check the specs : unbinding an NS which is not already bound may be disallowed. LOG.warn("Unbinding unbound prefix '" + prefix + "'"); } } else //This prefix was bound { //Unbind it if (uri.length() == 0) { // if an empty namespace is specified, // remove any existing mapping for this namespace //TODO : improve, since XML_NS can't be unbound staticPrefixes.remove(uri); staticNamespaces.remove(prefix); return; } //those prefixes can be rebound to different URIs if ((prefix.equals("xs") && Namespaces.SCHEMA_NS.equals(prevURI)) || (prefix.equals("xsi") && Namespaces.SCHEMA_INSTANCE_NS.equals(prevURI)) || (prefix.equals("xdt") && Namespaces.XPATH_DATATYPES_NS.equals(prevURI))|| (prefix.equals("fn") && Namespaces.XPATH_FUNCTIONS_NS.equals(prevURI)) || (prefix.equals("local") && Namespaces.XQUERY_LOCAL_NS.equals(prevURI))) { staticPrefixes.remove(prevURI); staticNamespaces.remove(prefix); if (uri.length() > 0) { staticNamespaces.put(prefix, uri); staticPrefixes.put(uri, prefix); return; } //Nothing to bind (not sure if it should raise an error though) else { //TODO : check the specs : unbinding an NS which is not already bound may be disallowed. LOG.warn("Unbinding unbound prefix '" + prefix + "'"); } } else { //Forbids rebinding the *same* prefix in a *different* namespace in this *same* context if (!uri.equals(prevURI)) throw new XPathException("err:XQST0033: Namespace prefix '" + prefix + "' is already bound to a different uri '" + prevURI + "'"); } } } public void declareNamespaces(Map namespaceMap) { Map.Entry entry; String prefix, uri; for(Iterator i = namespaceMap.entrySet().iterator(); i.hasNext(); ) { entry = (Map.Entry)i.next(); prefix = (String)entry.getKey(); uri = (String) entry.getValue(); if(prefix == null) prefix = ""; if(uri == null) uri = ""; staticNamespaces.put(prefix, uri); staticPrefixes.put(uri, prefix); } } /** * Removes the namespace URI from the prefix/namespace * mappings table. * * @param uri */ public void removeNamespace(String uri) { staticPrefixes.remove(uri); for (Iterator i = staticNamespaces.values().iterator(); i.hasNext();) { if (((String) i.next()).equals(uri)) { i.remove(); return; } } inScopePrefixes.remove(uri); if (inScopeNamespaces != null) { for (Iterator i = inScopeNamespaces.values().iterator(); i.hasNext();) { if (((String) i.next()).equals(uri)) { i.remove(); return; } } } //TODO : is this relevant ? inheritedInScopePrefixes.remove(uri); if (inheritedInScopeNamespaces != null) { for (Iterator i = inheritedInScopeNamespaces.values().iterator(); i.hasNext();) { if (((String) i.next()).equals(uri)) { i.remove(); return; } } } } /** * Declare an in-scope namespace. This is called during query execution. * * @param prefix * @param uri */ public void declareInScopeNamespace(String prefix, String uri) { if (prefix == null || uri == null) throw new IllegalArgumentException("null argument passed to declareNamespace"); //Activate the namespace by removing it from the inherited namespaces if (inheritedInScopePrefixes.get(getURIForPrefix(prefix)) != null) inheritedInScopePrefixes.remove(uri); if (inheritedInScopeNamespaces.get(prefix) != null) inheritedInScopeNamespaces.remove(prefix); inScopePrefixes.put(uri, prefix); inScopeNamespaces.put(prefix, uri); } public String getInScopeNamespace(String prefix) { return inScopeNamespaces == null ? null : (String) inScopeNamespaces.get(prefix); } public String getInScopePrefix(String uri) { return inScopePrefixes == null ? null : (String) inScopePrefixes.get(uri); } public String getInheritedNamespace(String prefix) { return inheritedInScopeNamespaces == null ? null : (String) inheritedInScopeNamespaces.get(prefix); } public String getInheritedPrefix(String uri) { return inheritedInScopePrefixes == null ? null : (String) inheritedInScopePrefixes.get(uri); } /** * Return the namespace URI mapped to the registered prefix * or null if the prefix is not registered. * * @param prefix * @return namespace */ public String getURIForPrefix(String prefix) { // try in-scope namespace declarations String uri = inScopeNamespaces == null ? null : (String) inScopeNamespaces.get(prefix); if (uri != null) return uri; if (inheritNamespaces) { uri = inheritedInScopeNamespaces == null ? null : (String) inheritedInScopeNamespaces.get(prefix); if (uri != null) return uri; } return (String)staticNamespaces.get(prefix); /* old code checked namespaces first String ns = (String) namespaces.get(prefix); if (ns == null) // try in-scope namespace declarations return inScopeNamespaces == null ? null : (String) inScopeNamespaces.get(prefix); else return ns; */ } /** * @param uri * @return the prefix mapped to the registered URI or null if the URI * is not registered. */ public String getPrefixForURI(String uri) { String prefix = inScopePrefixes == null ? null : (String) inScopePrefixes.get(uri); if (prefix != null) return prefix; if (inheritNamespaces) { prefix = inheritedInScopePrefixes == null ? null : (String) inheritedInScopePrefixes.get(uri); if (prefix != null) return prefix; } return (String) staticPrefixes.get(uri); } /** * Clear all user-defined prefix/namespace mappings. */ // TODO: remove since never used? // public void clearNamespaces() { // staticNamespaces.clear(); // staticPrefixes.clear(); // if (inScopeNamespaces != null) { // inScopeNamespaces.clear(); // inScopePrefixes.clear(); // } // //TODO : it this relevant ? // if (inheritedInScopeNamespaces != null) { // inheritedInScopeNamespaces.clear(); // inheritedInScopePrefixes.clear(); // } // loadDefaults(broker.getConfiguration()); // } /** * Returns the current default function namespace. * * @return current default function namespace */ public String getDefaultFunctionNamespace() { return defaultFunctionNamespace; } /** * Set the default function namespace. By default, this * points to the namespace for XPath built-in functions. * * @param uri */ public void setDefaultFunctionNamespace(String uri) throws XPathException { //Not sure for the 2nd clause : eXist forces the function NS as default. if (defaultFunctionNamespace != null && !defaultFunctionNamespace.equals(Function.BUILTIN_FUNCTION_NS) && !defaultFunctionNamespace.equals(uri)) throw new XPathException("err:XQST0066: default function namespace is already set to: '" + defaultFunctionNamespace + "'"); defaultFunctionNamespace = uri; } /** * Returns the current default element namespace. * * @return current default element namespace schema */ public String getDefaultElementNamespaceSchema() throws XPathException { return defaultElementNamespaceSchema.getStringValue(); } /** * Set the default element namespace. By default, this * points to the empty uri. * * @param uri */ public void setDefaultElementNamespaceSchema(String uri) throws XPathException { // eXist forces the empty element NS as default. if (!defaultElementNamespaceSchema.equals(AnyURIValue.EMPTY_URI)) throw new XPathException("err:XQST0066: default function namespace schema is already set to: '" + defaultElementNamespaceSchema.getStringValue() + "'"); defaultElementNamespaceSchema = new AnyURIValue(uri); } /** * Returns the current default element namespace. * * @return current default element namespace */ public String getDefaultElementNamespace() throws XPathException { return defaultElementNamespace.getStringValue(); } /** * Set the default element namespace. By default, this * points to the empty uri. * * @param uri a <code>String</code> value * @param schema a <code>String</code> value * @exception XPathException if an error occurs */ public void setDefaultElementNamespace(String uri, String schema) throws XPathException { // eXist forces the empty element NS as default. if (!defaultElementNamespace.equals(AnyURIValue.EMPTY_URI)) throw new XPathException("err:XQST0066: default element namespace is already set to: '" + defaultElementNamespace.getStringValue() + "'"); defaultElementNamespace = new AnyURIValue(uri); if (schema != null) { defaultElementNamespaceSchema = new AnyURIValue(schema); } } /** * Set the default collation to be used by all operators and functions on strings. * Throws an exception if the collation is unknown or cannot be instantiated. * * @param uri * @throws XPathException */ public void setDefaultCollation(String uri) throws XPathException { if(uri.equals(Collations.CODEPOINT) || uri.equals(Collations.CODEPOINT_SHORT)) { defaultCollation = Collations.CODEPOINT; defaultCollator = null; } URI uriTest; try { uriTest = new URI(uri); } catch(URISyntaxException e) { throw new XPathException("err:XQST0038: Unknown collation : '" + uri + "'"); } if (uri.startsWith(Collations.EXIST_COLLATION_URI) || uri.startsWith("?") || uriTest.isAbsolute()) { defaultCollator = Collations.getCollationFromURI(this, uri); defaultCollation = uri; } else { String absUri = getBaseURI().getStringValue() + uri; defaultCollator = Collations.getCollationFromURI(this, absUri); defaultCollation = absUri; } } public String getDefaultCollation() { return defaultCollation; } public Collator getCollator(String uri) throws XPathException { if(uri == null) return defaultCollator; return Collations.getCollationFromURI(this, uri); } public Collator getDefaultCollator() { return defaultCollator; } /** * Set the set of statically known documents for the current * execution context. These documents will be processed if * no explicit document set has been set for the current expression * with fn:doc() or fn:collection(). * * @param docs */ public void setStaticallyKnownDocuments(XmldbURI[] docs) { staticDocumentPaths = docs; } public void setStaticallyKnownDocuments(DocumentSet set) { staticDocuments = set; } //TODO : not sure how these 2 options might/have to be related public void setCalendar(XMLGregorianCalendar newCalendar) { this.calendar = (XMLGregorianCalendar)newCalendar.clone(); } public void setTimeZone(TimeZone newTimeZone) { this.implicitTimeZone = newTimeZone; } public XMLGregorianCalendar getCalendar() { //TODO : we might prefer to return null if (calendar == null) { try { //Initialize to current dateTime calendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar()); } catch (DatatypeConfigurationException e) { LOG.error(e.getMessage(), e); } } //That's how we ensure stability of that static context function return calendar; } public TimeZone getImplicitTimeZone() { if (implicitTimeZone == null) { implicitTimeZone = TimeZone.getDefault(); if (implicitTimeZone.inDaylightTime(new Date())) implicitTimeZone.setRawOffset(implicitTimeZone.getRawOffset() + implicitTimeZone.getDSTSavings()); } //That's how we ensure stability of that static context function return this.implicitTimeZone; } /** * @return set of statically known documents. */ public DocumentSet getStaticallyKnownDocuments() throws XPathException { if(staticDocuments != null) // the document set has already been built, return it return staticDocuments; if (protectedDocuments != null) { staticDocuments = protectedDocuments.toDocumentSet(); return staticDocuments; } MutableDocumentSet ndocs = new DefaultDocumentSet(1031); if(staticDocumentPaths == null) // no path defined: return all documents in the db broker.getAllXMLResources(ndocs); else { DocumentImpl doc; Collection collection; for(int i = 0; i < staticDocumentPaths.length; i++) { try { collection = broker.getCollection(staticDocumentPaths[i]); if (collection != null) { collection.allDocs(broker, ndocs, true, true); } else { doc = broker.getXMLResource(staticDocumentPaths[i], Lock.READ_LOCK); if(doc != null) { if(doc.getPermissions().validate(broker.getUser(), Permission.READ)) { ndocs.add(doc); } doc.getUpdateLock().release(Lock.READ_LOCK); } } } catch(PermissionDeniedException e) { LOG.warn("Permission denied to read resource " + staticDocumentPaths[i] + ". Skipping it."); } } } staticDocuments = ndocs; return staticDocuments; } public ExtendedXMLStreamReader getXMLStreamReader(NodeValue nv) throws XMLStreamException, IOException { ExtendedXMLStreamReader reader; if (nv.getImplementationType() == NodeValue.IN_MEMORY_NODE) { NodeImpl node = (NodeImpl) nv; reader = new InMemoryXMLStreamReader(node.getDocument(), node.getDocument()); } else { NodeProxy proxy = (NodeProxy) nv; reader = getBroker().newXMLStreamReader(new NodeProxy(proxy.getDocument(), NodeId.DOCUMENT_NODE, proxy.getDocument().getFirstChildAddress()), false); } return reader; } public void setProtectedDocs(LockedDocumentMap map) { this.protectedDocuments = map; } public LockedDocumentMap getProtectedDocs() { return this.protectedDocuments; } public boolean inProtectedMode() { return protectedDocuments != null; } /** * Should loaded documents be locked? * * see #setLockDocumentsOnLoad(boolean) * */ public boolean lockDocumentsOnLoad() { return false; } // /** // * If lock is true, all documents loaded during query execution // * will be locked. This way, we avoid that query results become // * invalid before the entire result has been processed by the client // * code. All attempts to modify nodes which are part of the result // * set will be blocked. // * // * However, it is the client's responsibility to proper unlock // * all documents once processing is completed. // * // * @param lock // */ // public void setLockDocumentsOnLoad(boolean lock) { // lockDocumentsOnLoad = lock; // if(lock) // lockedDocuments = new LockedDocumentMap(); // } public void addLockedDocument(DocumentImpl doc) { // if (lockedDocuments != null) // lockedDocuments.add(doc); } // /** // * Release all locks on documents that have been locked // * during query execution. // * // *@see #setLockDocumentsOnLoad(boolean) // */ // public void releaseLockedDocuments() { // if(lockedDocuments != null) // lockedDocuments.unlock(); // lockDocumentsOnLoad = false; // lockedDocuments = null; // } // /** // * Release all locks on documents not being referenced by the sequence. // * This is called after query execution has completed. Only locks on those // * documents contained in the final result set will be preserved. All other // * locks are released as they are no longer needed. // * // * @param seq // * @throws XPathException // */ // public LockedDocumentMap releaseUnusedDocuments(Sequence seq) throws XPathException { // if(lockedDocuments == null) // return null; // // determine the set of documents referenced by nodes in the sequence // DocumentSet usedDocs = new DocumentSet(); // for(SequenceIterator i = seq.iterate(); i.hasNext(); ) { // Item next = i.nextItem(); // if(Type.subTypeOf(next.getType(), Type.NODE)) { // NodeValue node = (NodeValue) next; // if(node.getImplementationType() == NodeValue.PERSISTENT_NODE) { // DocumentImpl doc = ((NodeProxy)node).getDocument(); // if(!usedDocs.contains(doc.getDocId())) // usedDocs.add(doc, false); // } // } // } // LockedDocumentMap remaining = lockedDocuments.unlockSome(usedDocs); // lockDocumentsOnLoad = false; // lockedDocuments = null; // return remaining; // } public void setShared(boolean shared) { isShared = shared; } public boolean isShared() { return isShared; } public void addModifiedDoc(DocumentImpl document) { if (modifiedDocuments == null) modifiedDocuments = new DefaultDocumentSet(); modifiedDocuments.add(document); } public void reset() { reset(false); } /** * Prepare this XQueryContext to be reused. This should be * called when adding an XQuery to the cache. */ public void reset(boolean keepGlobals) { if (modifiedDocuments != null) { try { Modification.checkFragmentation(this, modifiedDocuments); } catch (EXistException e) { LOG.warn("Error while checking modified documents: " + e.getMessage(), e); } modifiedDocuments = null; } calendar = null; implicitTimeZone = null; builder = new MemTreeBuilder(this); builder.startDocument(); if(!keepGlobals) { // do not reset the statically known documents staticDocumentPaths = null; staticDocuments = null; } if(!isShared) { lastVar = null; } fragmentStack = new Stack(); callStack.clear(); protectedDocuments = null; if(!keepGlobals) { globalVariables.clear(); } if(dynamicOptions != null) { dynamicOptions.clear(); //clear any dynamic options } if(!isShared) { watchdog.reset(); } for(Iterator i = modules.values().iterator(); i.hasNext();) { Module module = (Module) i.next(); if (module instanceof ExternalModule && ((ModuleContext)((ExternalModule)module).getContext()).getParentContext() != this) { continue; } module.reset(this); } if(!keepGlobals) { mappedModules.clear(); } //remove the context-vars, subsequent execution of the query //may generate different values for the vars based on the //content of the db XQueryContextVars.clear(); clearUpdateListeners(); profiler.reset(); analyzed = false; } /** * Returns true if whitespace between constructed element nodes * should be stripped by default. */ public boolean stripWhitespace() { return stripWhitespace; } public void setStripWhitespace(boolean strip) { this.stripWhitespace = strip; } /** * Returns true if namespaces for constructed element and document nodes * should be preserved on copy by default. */ public boolean preserveNamespaces() { return preserveNamespaces; } /** * The method <code>setPreserveNamespaces</code> * * @param preserve a <code>boolean</code> value */ public void setPreserveNamespaces(final boolean preserve) { this.preserveNamespaces = preserve; } /** * Returns true if namespaces for constructed element and document nodes * should be inherited on copy by default. */ public boolean inheritNamespaces() { return inheritNamespaces; } /** * The method <code>setInheritNamespaces</code> * * @param inherit a <code>boolean</code> value */ public void setInheritNamespaces(final boolean inherit) { this.inheritNamespaces = inherit; } /** * Returns true if order empty is set to gretest, otherwise false * for order empty is least. */ public boolean orderEmptyGreatest() { return orderEmptyGreatest; } /** * The method <code>setOrderEmptyGreatest</code> * * @param order a <code>boolean</code> value */ public void setOrderEmptyGreatest(final boolean order) { this.orderEmptyGreatest = order; } /** * @return iterator over all modules imported into this context */ public Iterator getModules() { return modules.values().iterator(); } /** * @return iterator over all modules registered in the entire context tree */ public Iterator getRootModules() { return getAllModules(); } public Iterator getAllModules() { return allModules.values().iterator(); } /** * Get the built-in module registered for the given namespace * URI. * * @param namespaceURI * @return built-in module */ public Module getModule(String namespaceURI) { return (Module) modules.get(namespaceURI); } public Module getRootModule(String namespaceURI) { return (Module) allModules.get(namespaceURI); } public void setModule(String namespaceURI, Module module) { if (module == null) { modules.remove(namespaceURI); // unbind the module } else { modules.put(namespaceURI, module); if( !module.isInternalModule() && module.isReady() ) { ( (ModuleContext)( (ExternalModule)module ).getContext() ).setParentContext( this ); } } setRootModule(namespaceURI, module); } protected void setRootModule(String namespaceURI, Module module) { if (module == null) { allModules.remove(namespaceURI); // unbind the module return; } if (allModules.get(namespaceURI) != module) setModulesChanged(); allModules.put(namespaceURI, module); } void setModulesChanged() { this.modulesChanged = true; } /** * For compiled expressions: check if the source of any * module imported by the current query has changed since * compilation. */ public boolean checkModulesValid() { for(Iterator i = modules.values().iterator(); i.hasNext(); ) { Module module = (Module)i.next(); if(!module.isInternalModule()) { if(!((ExternalModule)module).moduleIsValid(getBroker())) { LOG.debug("Module with URI " + module.getNamespaceURI() + " has changed and needs to be reloaded"); return false; } } } return true; } public void analyzeAndOptimizeIfModulesChanged(Expression expr) throws XPathException { if (analyzed) return; analyzed = true; for (Module module : expr.getContext().modules.values()) { if( !module.isInternalModule() ) { Expression root = ((ExternalModule)module).getRootExpression(); ((ExternalModule)module).getContext().analyzeAndOptimizeIfModulesChanged(root); } } expr.analyze(new AnalyzeContextInfo()); if (optimizationsEnabled()) { Optimizer optimizer = new Optimizer(this); expr.accept(optimizer); if (optimizer.hasOptimized()) { reset(true); expr.resetState(true); expr.analyze(new AnalyzeContextInfo()); } } modulesChanged = false; } /** * Load a built-in module from the given class name and assign it to the * namespace URI. The specified class should be a subclass of * {@link Module}. The method will try to instantiate the class. If the * class is not found or an exception is thrown, the method will silently * fail. The namespace URI has to be equal to the namespace URI declared * by the module class. Otherwise, the module is not loaded. * * @param namespaceURI * @param moduleClass */ public Module loadBuiltInModule(String namespaceURI, String moduleClass) { Module module = getModule(namespaceURI); if (module != null) { // LOG.debug("module " + namespaceURI + " is already present"); return module; } return initBuiltInModule(namespaceURI, moduleClass); } protected Module initBuiltInModule(String namespaceURI, String moduleClass) { Module module = null; try { // lookup the class Class mClass = Class.forName(moduleClass); if (!(Module.class.isAssignableFrom(mClass))) { LOG.info( "failed to load module. " + moduleClass + " is not an instance of org.exist.xquery.Module."); return null; } instantiateModule(namespaceURI, mClass); //LOG.debug("module " + module.getNamespaceURI() + " loaded successfully."); } catch (ClassNotFoundException e) { LOG.warn("module class " + moduleClass + " not found. Skipping..."); } return module; } protected Module instantiateModule(String namespaceURI, Class mClass) { Module module = null; try { module = (Module) mClass.newInstance(); if (!module.getNamespaceURI().equals(namespaceURI)) { LOG.warn("the module declares a different namespace URI. Expected: " + namespaceURI + " found: " + module.getNamespaceURI()); return null; } if (getPrefixForURI(module.getNamespaceURI()) == null && module.getDefaultPrefix().length() > 0) declareNamespace(module.getDefaultPrefix(), module.getNamespaceURI()); modules.put(module.getNamespaceURI(), module); allModules.put(module.getNamespaceURI(), module); return module; } catch (InstantiationException e) { LOG.warn("error while instantiating module class " + mClass.getName(), e); } catch (IllegalAccessException e) { LOG.warn("error while instantiating module class " + mClass.getName(), e); } catch (XPathException e) { LOG.warn("error while instantiating module class " + mClass.getName(), e); } return null; } /** * Convenience method that returns the XACML Policy Decision Point for * this database instance. If XACML has not been enabled, this returns * null. * * @return the PDP for this database instance, or null if XACML is disabled */ public ExistPDP getPDP() { return broker.getBrokerPool().getSecurityManager().getPDP(); } /** * Declare a user-defined function. All user-defined functions are kept * in a single hash map. * * @param function * @throws XPathException */ public void declareFunction(UserDefinedFunction function) throws XPathException { // TODO: redeclaring functions should be forbidden. however, throwing an // exception will currently break util:eval. if (Namespaces.XML_NS.equals(function.getSignature().getName().getNamespaceURI())) throw new XPathException("XQST0045: function is in the forbidden namespace '" + Namespaces.XML_NS + "'"); if (Namespaces.SCHEMA_NS.equals(function.getSignature().getName().getNamespaceURI())) throw new XPathException("XQST0045: function is in the forbidden namespace '" + Namespaces.SCHEMA_NS + "'"); if (Namespaces.SCHEMA_INSTANCE_NS.equals(function.getSignature().getName().getNamespaceURI())) throw new XPathException("XQST0045: function is in the forbidden namespace '" + Namespaces.SCHEMA_INSTANCE_NS + "'"); if (Namespaces.XPATH_FUNCTIONS_NS.equals(function.getSignature().getName().getNamespaceURI())) throw new XPathException("XQST0045: function is in the forbidden namespace '" + Namespaces.XPATH_FUNCTIONS_NS + "'"); declaredFunctions.put(function.getSignature().getFunctionId(), function); // if (declaredFunctions.get(function.getSignature().getFunctionId()) == null) // declaredFunctions.put(function.getSignature().getFunctionId(), function); // else // throw new XPathException("XQST0034: function " + function.getName() + " is already defined with the same arity"); } /** * Resolve a user-defined function. * * @param name * @return user-defined function * @throws XPathException */ public UserDefinedFunction resolveFunction(QName name, int argCount) throws XPathException { FunctionId id = new FunctionId(name, argCount); UserDefinedFunction func = (UserDefinedFunction) declaredFunctions.get(id); return func; } public Iterator getSignaturesForFunction(QName name) { ArrayList signatures = new ArrayList(2); for (Iterator i = declaredFunctions.values().iterator(); i.hasNext(); ) { UserDefinedFunction func = (UserDefinedFunction) i.next(); if (func.getName().equals(name)) signatures.add(func.getSignature()); } return signatures.iterator(); } public Iterator localFunctions() { return declaredFunctions.values().iterator(); } /** * Declare a local variable. This is called by variable binding expressions like * "let" and "for". * * @param var * @throws XPathException */ public LocalVariable declareVariableBinding(LocalVariable var) throws XPathException { if(lastVar == null) lastVar = var; else { lastVar.addAfter(var); lastVar = var; } var.setStackPosition(getCurrentStackSize()); return var; } /** * Declare a global variable as by "declare variable". * * @param var * @throws XPathException */ public Variable declareGlobalVariable(Variable var) throws XPathException { globalVariables.put(var.getQName(), var); var.setStackPosition(getCurrentStackSize()); return var; } public void undeclareGlobalVariable( QName name ) { globalVariables.remove(name); } /** * Declare a user-defined variable. * * The value argument is converted into an XPath value * (@see XPathUtil#javaObjectToXPath(Object)). * * @param qname the qualified name of the new variable. Any namespaces should * have been declared before. * @param value a Java object, representing the fixed value of the variable * @return the created Variable object * @throws XPathException if the value cannot be converted into a known XPath value * or the variable QName references an unknown namespace-prefix. */ public Variable declareVariable(String qname, Object value) throws XPathException { return declareVariable(QName.parse(this, qname, null), value); } public Variable declareVariable(QName qn, Object value) throws XPathException { Variable var; Module module = getModule(qn.getNamespaceURI()); if(module != null) { var = module.declareVariable(qn, value); return var; } Sequence val = XPathUtil.javaObjectToXPath(value, this); var = globalVariables.get(qn); if(var == null) { var = new Variable(qn); globalVariables.put(qn, var); } if (var.getSequenceType() != null) { int actualCardinality; if (val.isEmpty()) actualCardinality = Cardinality.EMPTY; else if (val.hasMany()) actualCardinality = Cardinality.MANY; else actualCardinality = Cardinality.ONE; //Type.EMPTY is *not* a subtype of other types ; checking cardinality first if (!Cardinality.checkCardinality(var.getSequenceType().getCardinality(), actualCardinality)) throw new XPathException("XPTY0004: Invalid cardinality for variable $" + var.getQName() + ". Expected " + Cardinality.getDescription(var.getSequenceType().getCardinality()) + ", got " + Cardinality.getDescription(actualCardinality)); //TODO : ignore nodes right now ; they are returned as xs:untypedAtomicType if (!Type.subTypeOf(var.getSequenceType().getPrimaryType(), Type.NODE)) { if (!val.isEmpty() && !Type.subTypeOf(val.getItemType(), var.getSequenceType().getPrimaryType())) throw new XPathException("XPTY0004: Invalid type for variable $" + var.getQName() + ". Expected " + Type.getTypeName(var.getSequenceType().getPrimaryType()) + ", got " +Type.getTypeName(val.getItemType())); //Here is an attempt to process the nodes correctly } else { //Same as above : we probably may factorize if (!val.isEmpty() && !Type.subTypeOf(val.getItemType(), var.getSequenceType().getPrimaryType())) throw new XPathException("XPTY0004: Invalid type for variable $" + var.getQName() + ". Expected " + Type.getTypeName(var.getSequenceType().getPrimaryType()) + ", got " +Type.getTypeName(val.getItemType())); } } //TODO : should we allow global variable *re*declaration ? var.setValue(val); return var; } /** * Try to resolve a variable. * * @param name the qualified name of the variable as string * @return the declared Variable object * @throws XPathException if the variable is unknown */ public Variable resolveVariable(String name) throws XPathException { QName qn = QName.parse(this, name, null); return resolveVariable(qn); } /** * Try to resolve a variable. * * @param qname the qualified name of the variable * @return the declared Variable object * @throws XPathException if the variable is unknown */ public Variable resolveVariable(QName qname) throws XPathException { Variable var; // check if the variable is declared local var = resolveLocalVariable(qname); // check if the variable is declared in a module if (var == null){ Module module = getModule(qname.getNamespaceURI()); if(module != null) { var = module.resolveVariable(qname); } } // check if the variable is declared global if (var == null) var = (Variable) globalVariables.get(qname); //if (var == null) // throw new XPathException("variable $" + qname + " is not bound"); return var; } protected Variable resolveLocalVariable(QName qname) throws XPathException { LocalVariable end = contextStack.isEmpty() ? null : contextStack.peek(); for(LocalVariable var = lastVar; var != null; var = var.before) { if (var == end) return null; if(qname.equals(var.getQName())) return var; } return null; } public boolean isVarDeclared(QName qname) { Module module = getModule(qname.getNamespaceURI()); if(module != null) { if (module.isVarDeclared(qname)) return true; } return globalVariables.get(qname) != null; } public Map<QName, Variable> getVariables() { Map<QName, Variable> variables = new HashMap<QName, Variable>(); variables.putAll(globalVariables); LocalVariable end = contextStack.isEmpty() ? null : (LocalVariable) contextStack.peek(); for(LocalVariable var = lastVar; var != null; var = var.before) { if (var == end) break; variables.put(var.getQName(), var); } return variables; } public Map<QName, Variable> getLocalVariables() { Map<QName, Variable> variables = new HashMap<QName, Variable>(); LocalVariable end = contextStack.isEmpty() ? null : (LocalVariable)contextStack.peek(); for( LocalVariable var = lastVar; var != null; var = var.before ) { if( var == end ) { break; } variables.put( var.getQName(), var ); } return( variables ); } public Map<QName, Variable> getGlobalVariables() { Map<QName, Variable> variables = new HashMap<QName, Variable>(); variables.putAll( globalVariables ); return( variables ); } /** * Turn on/off XPath 1.0 backwards compatibility. * * If turned on, comparison expressions will behave like * in XPath 1.0, i.e. if any one of the operands is a number, * the other operand will be cast to a double. * * @param backwardsCompatible */ public void setBackwardsCompatibility(boolean backwardsCompatible) { this.backwardsCompatible = backwardsCompatible; } /** * XPath 1.0 backwards compatibility turned on? * * In XPath 1.0 compatible mode, additional conversions * will be applied to values if a numeric value is expected. * */ public boolean isBackwardsCompatible() { return this.backwardsCompatible; } public boolean isRaiseErrorOnFailedRetrieval() { return raiseErrorOnFailedRetrieval; } /** * Get the DBBroker instance used for the current query. * * The DBBroker is the main database access object, providing * access to all internal database functions. * * @return DBBroker instance */ public DBBroker getBroker() { return broker; } public void setBroker(DBBroker broker) { this.broker = broker; } /** * Get the user which executes the current query. * * @return user */ public User getUser() { return getBroker().getUser(); } /** * If there is a HTTP Session, and a User has been stored in the session then this will * return the user object from the session * * @return The user or null if there is no session or no user */ private User getUserFromHttpSession() { SessionModule myModule = (SessionModule)getModule(SessionModule.NAMESPACE_URI); //Sanity check : one may *not* want to bind the module ! if (myModule == null) { return null; } Variable var = null; try { var = myModule.resolveVariable(SessionModule.SESSION_VAR); } catch(XPathException xpe) { return null; } if(var != null && var.getValue() != null) { if(var.getValue().getItemType() == Type.JAVA_OBJECT) { JavaObjectValue session = (JavaObjectValue) var.getValue().itemAt(0); if(session.getObject() instanceof SessionWrapper) { try { return (User)((SessionWrapper)session.getObject()).getAttribute(HTTP_SESSIONVAR_XMLDB_USER); } catch (IllegalStateException e) { // session is invalid return null; } } } } return null; } /** * Get the document builder currently used for creating * temporary document fragments. A new document builder * will be created on demand. * * @return document builder */ public MemTreeBuilder getDocumentBuilder() { if (builder == null) { builder = new MemTreeBuilder(this); builder.startDocument(); } return builder; } public MemTreeBuilder getDocumentBuilder(boolean explicitCreation) { if (builder == null) { builder = new MemTreeBuilder(this); builder.startDocument(explicitCreation); } return builder; } /** * Utility method to create a new in-memory document using the * provided DocBuilder interface. * * @param builder a supplied {@link DocBuilder} * @return the document element of the created document */ public NodeValue createDocument(DocBuilder builder) { pushDocumentContext(); try { MemTreeBuilder treeBuilder = getDocumentBuilder(); builder.build(treeBuilder); treeBuilder.endDocument(); NodeValue node = (NodeValue) treeBuilder.getDocument().getFirstChild(); if (node == null) node = treeBuilder.getDocument().getAttribute(0); return node; } finally { popDocumentContext(); } } /** * Returns the shared name pool used by all in-memory * documents which are created within this query context. * Create a name pool for every document would be a waste of * memory, especially since it is likely that the documents * contain elements or attributes with similar names. * * @return the shared name pool */ public NamePool getSharedNamePool() { if (sharedNamePool == null) sharedNamePool = new NamePool(); return sharedNamePool; } /* DebuggeeJoint methods */ public XQueryContext getContext() { return null; } public void expressionStart(Expression expr) throws TerminatedException { if (debuggeeJoint != null) { debuggeeJoint.expressionStart(expr); } } public void expressionEnd(Expression expr) { if (debuggeeJoint != null) { debuggeeJoint.expressionEnd(expr); } } public void stackEnter(Expression expr) throws TerminatedException { if (debuggeeJoint != null) { debuggeeJoint.stackEnter(expr); } } public void stackLeave(Expression expr) { if (debuggeeJoint != null) { debuggeeJoint.stackLeave(expr); } } /* Methods delegated to the watchdog */ public void proceed() throws TerminatedException { getWatchDog().proceed(null); } public void proceed(Expression expr) throws TerminatedException { getWatchDog().proceed(expr); } public void proceed(Expression expr, MemTreeBuilder builder) throws TerminatedException { getWatchDog().proceed(expr, builder); } public XQueryWatchDog getWatchDog() { return watchdog; } /** * Push any document fragment created within the current * execution context on the stack. */ public void pushDocumentContext() { fragmentStack.push(builder); builder = null; } public void popDocumentContext() { if (!fragmentStack.isEmpty()) { builder = (MemTreeBuilder) fragmentStack.pop(); } } /** * Set the base URI for the evaluation context. * * This is the URI returned by the fn:base-uri() * function. * * @param uri */ public void setBaseURI(AnyURIValue uri) { setBaseURI(uri, false); } /** * Set the base URI for the evaluation context. * * A base URI specified via the base-uri directive in the * XQuery prolog overwrites any other setting. * * @param uri * @param setInProlog */ public void setBaseURI(AnyURIValue uri, boolean setInProlog) { if (baseURISetInProlog) return; if (uri == null) baseURI = AnyURIValue.EMPTY_URI; baseURI = uri; baseURISetInProlog = setInProlog; } /** * Set the path to a base directory where modules should * be loaded from. Relative module paths will be resolved against * this directory. The property is usually set by the XQueryServlet or * XQueryGenerator, but can also be specified manually. * * @param path */ public void setModuleLoadPath(String path) { this.moduleLoadPath = path; } public String getModuleLoadPath() { return moduleLoadPath; } /** * The method <code>isBaseURIDeclared</code> * * @return a <code>boolean</code> value */ public boolean isBaseURIDeclared() { if (baseURI == null || baseURI.equals(AnyURIValue.EMPTY_URI)) { return false; } else { return true; } } /** * Get the base URI of the evaluation context. * * This is the URI returned by the fn:base-uri() function. * * @return base URI of the evaluation context * @exception XPathException if an error occurs */ public AnyURIValue getBaseURI() throws XPathException { // the base URI in the static context is established according to the // principles outlined in [RFC3986] Section 5.1—that is, it defaults // first to the base URI of the encapsulating entity, then to the URI // used to retrieve the entity, and finally to an implementation-defined // default. If the URILiteral in the base URI declaration is a relative // URI, then it is made absolute by resolving it with respect to this // same hierarchy. // It is not intrinsically an error if this process fails to establish // an absolute base URI; however, the base URI in the static context // is then undefined, and any attempt to use its value may result in // an error [err:XPST0001]. if (baseURI == null || baseURI.equals(AnyURIValue.EMPTY_URI)) { //throw new XPathException("err:XPST0001: base URI of the static context has not been assigned a value."); // We catch and resolve this to the XmlDbURI.ROOT_COLLECTION_URI // at least in DocumentImpl so maybe we should do it here./ljo } return baseURI; } /** * Set the current context position, i.e. the position * of the currently processed item in the context sequence. * This value is required by some expressions, e.g. fn:position(). * * @param pos */ public void setContextPosition(int pos) { contextPosition = pos; } /** * Get the current context position, i.e. the position of * the currently processed item in the context sequence. * * @return current context position */ public int getContextPosition() { return contextPosition; } public void pushInScopeNamespaces() { pushInScopeNamespaces(true); } /** * Push all in-scope namespace declarations onto the stack. */ public void pushInScopeNamespaces(boolean inherit) { //TODO : push into an inheritedInScopeNamespaces HashMap... and return an empty HashMap HashMap m = (HashMap) inScopeNamespaces.clone(); HashMap p = (HashMap) inScopePrefixes.clone(); namespaceStack.push(inheritedInScopeNamespaces); namespaceStack.push(inheritedInScopePrefixes); namespaceStack.push(inScopeNamespaces); namespaceStack.push(inScopePrefixes); //Current namespaces now become inherited just like the previous inherited ones if (inherit) { inheritedInScopeNamespaces = (HashMap)inheritedInScopeNamespaces.clone(); inheritedInScopeNamespaces.putAll(m); inheritedInScopePrefixes = (HashMap)inheritedInScopePrefixes.clone(); inheritedInScopePrefixes.putAll(p); } else { inheritedInScopeNamespaces = new HashMap(); inheritedInScopePrefixes = new HashMap(); } //TODO : consider dynamic instanciation inScopeNamespaces = new HashMap(); inScopePrefixes = new HashMap(); } public void popInScopeNamespaces() { inScopePrefixes = (HashMap) namespaceStack.pop(); inScopeNamespaces = (HashMap) namespaceStack.pop(); inheritedInScopePrefixes = (HashMap) namespaceStack.pop(); inheritedInScopeNamespaces = (HashMap) namespaceStack.pop(); } public void pushNamespaceContext() { HashMap m = (HashMap) staticNamespaces.clone(); HashMap p = (HashMap) staticPrefixes.clone(); namespaceStack.push(staticNamespaces); namespaceStack.push(staticPrefixes); staticNamespaces = m; staticPrefixes = p; } public void popNamespaceContext() { staticPrefixes = (HashMap) namespaceStack.pop(); staticNamespaces = (HashMap) namespaceStack.pop(); } /** * Returns the last variable on the local variable stack. * The current variable context can be restored by passing * the return value to {@link #popLocalVariables(LocalVariable)}. * * @return last variable on the local variable stack */ public LocalVariable markLocalVariables(boolean newContext) { if (newContext) { if (lastVar == null) lastVar = new LocalVariable(QName.EMPTY_QNAME); contextStack.push(lastVar); } variableStackSize++; return lastVar; } /** * Restore the local variable stack to the position marked * by variable var. * * @param var */ public void popLocalVariables(LocalVariable var) { if(var != null) { var.after = null; if (!contextStack.isEmpty() && var == contextStack.peek()) { contextStack.pop(); } } lastVar = var; variableStackSize--; } /** * Returns the current size of the stack. This is used to determine * where a variable has been declared. * * @return current size of the stack */ public int getCurrentStackSize() { return variableStackSize; } /* ----------------- Function call stack ------------------------ */ /** * Report the start of a function execution. Adds the reported function signature * to the function call stack. */ public void functionStart(FunctionSignature signature) { callStack.push(signature); } /** * Report the end of the currently executed function. Pops the last function * signature from the function call stack. * */ public void functionEnd() { if (callStack.isEmpty()) { LOG.warn("Function call stack is empty, but XQueryContext.functionEnd() was called. This " + "could indicate a concurrency issue (shared XQueryContext?)"); } else callStack.pop(); } /** * Check if the specified function signature is found in the current function * called stack. If yes, the function might be tail recursive and needs to be * optimized. * * @param signature */ public boolean tailRecursiveCall(FunctionSignature signature) { return callStack.contains(signature); } /* ----------------- Module imports ------------------------ */ public void mapModule(String namespace, XmldbURI uri) { mappedModules.put(namespace, uri); } /** * Import a module and make it available in this context. The prefix and * location parameters are optional. If prefix is null, the default prefix specified * by the module is used. If location is null, the module will be read from the * namespace URI. * * @param namespaceURI * @param prefix * @param location * @throws XPathException */ public void importModule(String namespaceURI, String prefix, String location) throws XPathException { Module module = getRootModule(namespaceURI); if(module != null) { LOG.debug("Module " + namespaceURI + " already present."); // Set locally to remember the dependency in case it was inherited. setModule(namespaceURI, module); } else { if(location == null) { // check if there's a static mapping in the configuration location = getModuleLocation(namespaceURI); if (location == null) location = namespaceURI; } //Is the module's namespace mapped to a URL ? if (mappedModules.containsKey(location)) location = mappedModules.get(location).toString(); // is it a Java module? if(location.startsWith(JAVA_URI_START)) { location = location.substring(JAVA_URI_START.length()); module = loadBuiltInModule(namespaceURI, location); } else { Source source; if (location.startsWith(XmldbURI.XMLDB_URI_PREFIX) || (location.indexOf(':') < 0 && moduleLoadPath.startsWith(XmldbURI.XMLDB_URI_PREFIX))) { // Is the module source stored in the database? try { XmldbURI locationUri = XmldbURI.xmldbUriFor(location); if (moduleLoadPath.startsWith(XmldbURI.XMLDB_URI_PREFIX)) { XmldbURI moduleLoadPathUri = XmldbURI.xmldbUriFor(moduleLoadPath); locationUri = moduleLoadPathUri.resolveCollectionPath(locationUri); } DocumentImpl sourceDoc = null; try { sourceDoc = broker.getXMLResource(locationUri.toCollectionPathURI(), Lock.READ_LOCK); if (sourceDoc == null) throw new XPathException("source for module " + location + " not found in database"); if (sourceDoc.getResourceType() != DocumentImpl.BINARY_FILE || !sourceDoc.getMetadata().getMimeType().equals("application/xquery")) throw new XPathException("source for module " + location + " is not an XQuery or " + "declares a wrong mime-type"); source = new DBSource(broker, (BinaryDocument) sourceDoc, true); // we don't know if the module will get returned, oh well module = compileOrBorrowModule(prefix, namespaceURI, location, source); } catch (PermissionDeniedException e) { throw new XPathException("permission denied to read module source from " + location); } finally { if(sourceDoc != null) sourceDoc.getUpdateLock().release(Lock.READ_LOCK); } } catch(URISyntaxException e) { throw new XPathException(e.getMessage(), e); } } else { // No. Load from file or URL try { //TODO: use URIs to ensure proper resolution of relative locations source = SourceFactory.getSource(broker, moduleLoadPath, location, true); } catch (MalformedURLException e) { throw new XPathException("source location for module " + namespaceURI + " should be a valid URL: " + e.getMessage()); } catch (IOException e) { throw new XPathException("source for module '" + namespaceURI + "' not found at '" + location + "': " + e.getMessage()); } catch (PermissionDeniedException e) { throw new XPathException("Permission denied to access module '" + namespaceURI + "' at '" + location + "': " + e.getMessage()); } // we don't know if the module will get returned, oh well module = compileOrBorrowModule(prefix, namespaceURI, location, source); } } } if(prefix == null) prefix = module.getDefaultPrefix(); declareNamespace(prefix, namespaceURI); } /** * Returns the static location mapped to an XQuery source module, if known. * * @param namespaceURI the URI of the module * @return the location string */ public String getModuleLocation(String namespaceURI) { Map moduleMap = (Map) broker.getConfiguration().getProperty(PROPERTY_STATIC_MODULE_MAP); return (String) moduleMap.get(namespaceURI); } /** * Returns an iterator over all module namespace URIs which are statically * mapped to a known location. * @return an iterator */ public Iterator getMappedModuleURIs() { Map moduleMap = (Map) broker.getConfiguration().getProperty(PROPERTY_STATIC_MODULE_MAP); return moduleMap.keySet().iterator(); } private ExternalModule compileOrBorrowModule(String prefix, String namespaceURI, String location, Source source) throws XPathException { ExternalModule module = broker.getBrokerPool().getXQueryPool().borrowModule(broker, source, this); if (module == null) { module = compileModule(prefix, namespaceURI, location, source); } else { for (Iterator it = module.getContext().getAllModules(); it.hasNext();) { Module importedModule = (Module) it.next(); if (importedModule != null && !allModules.containsKey(importedModule.getNamespaceURI())) { setRootModule(importedModule.getNamespaceURI(), importedModule); } } } setModule(module.getNamespaceURI(), module); declareModuleVars(module); return module; } /** * @return The compiled module. * @throws XPathException */ private ExternalModule compileModule(String prefix, String namespaceURI, String location, Source source) throws XPathException { LOG.debug("Loading module from " + location); Reader reader; try { reader = source.getReader(); if (reader == null) { throw new XPathException("failed to load module '" + namespaceURI + "' from '" + source + ". Source not found. "); } } catch (IOException e) { throw new XPathException("IO exception while loading module '" + namespaceURI + "' from '" + source + "'", e); } ExternalModuleImpl modExternal = new ExternalModuleImpl(namespaceURI, prefix); setModule(namespaceURI, modExternal); XQueryContext modContext = new ModuleContext(this, prefix, namespaceURI, location); modExternal.setContext( modContext ); XQueryLexer lexer = new XQueryLexer(modContext, reader); XQueryParser parser = new XQueryParser(lexer); XQueryTreeParser astParser = new XQueryTreeParser(modContext, modExternal); try { parser.xpath(); if (parser.foundErrors()) { LOG.debug(parser.getErrorMessage()); throw new XPathException( "error found while loading module from " + location + ": " + parser.getErrorMessage()); } AST ast = parser.getAST(); PathExpr path = new PathExpr(modContext); astParser.xpath(ast, path); if (astParser.foundErrors()) { throw new XPathException( "error found while loading module from " + location + ": " + astParser.getErrorMessage(), astParser.getLastException()); } path.analyze(new AnalyzeContextInfo()); if(modExternal == null) throw new XPathException("source at " + location + " is not a valid module"); modExternal.setRootExpression(path); if(!modExternal.getNamespaceURI().equals(namespaceURI)) throw new XPathException("namespace URI declared by module (" + modExternal.getNamespaceURI() + ") does not match namespace URI in import statement, which was: " + namespaceURI); // Set source information on module context // String sourceClassName = source.getClass().getName(); modContext.setSource(XACMLSource.getInstance(source)); // modContext.setSourceKey(source.getKey().toString()); // Extract the source type from the classname by removing the package prefix and the "Source" suffix // modContext.setSourceType( sourceClassName.substring( 17, sourceClassName.length() - 6 ) ); modExternal.setSource(source); modExternal.setContext(modContext); modExternal.setIsReady(true); return modExternal; } catch (RecognitionException e) { throw new XPathException(e.getLine(), e.getColumn(), "error found while loading module from " + location + ": " + e.getMessage()); } catch (TokenStreamException e) { throw new XPathException( "error found while loading module from " + location + ": " + e.getMessage(), e); } catch (XPathException e) { e.prependMessage("Error while loading module " + location + ": "); throw e; } catch (Exception e) { throw new XPathException("Internal error while loading module: " + location, e); } finally { try { if (reader != null) reader.close(); } catch (IOException e) { LOG.warn("Error while closing module source: " + e.getMessage(), e); } } } private void declareModuleVars(Module module) { String moduleNS = module.getNamespaceURI(); for (Iterator<Variable> i = globalVariables.values().iterator(); i.hasNext(); ) { Variable var = i.next(); if (moduleNS.equals(var.getQName().getNamespaceURI())) { module.declareVariable(var); i.remove(); } } } /** * Add a forward reference to an undeclared function. Forward * references will be resolved later. * * @param call */ public void addForwardReference(FunctionCall call) { forwardReferences.push(call); } /** * Resolve all forward references to previously undeclared functions. * * @throws XPathException */ public void resolveForwardReferences() throws XPathException { while(!forwardReferences.empty()) { FunctionCall call = (FunctionCall)forwardReferences.pop(); UserDefinedFunction func = call.getContext().resolveFunction( call.getQName(), call.getArgumentCount() ); if(func == null) throw new XPathException(call, "Call to undeclared function: " + call.getQName().getStringValue()); call.resolveForwardReference(func); } } public boolean optimizationsEnabled() { return enableOptimizer; } /** * for static compile-time options i.e. declare option */ public void addOption(String qnameString, String contents) throws XPathException { if(staticOptions == null) staticOptions = new ArrayList(); addOption(staticOptions, qnameString, contents); } /** * for dynamic run-time options i.e. util:declare-option */ public void addDynamicOption(String qnameString, String contents) throws XPathException { if(dynamicOptions == null) dynamicOptions = new ArrayList(); addOption(dynamicOptions, qnameString, contents); } private void addOption(List options, String qnameString, String contents) throws XPathException { QName qn = QName.parse(this, qnameString, defaultFunctionNamespace); Option option = new Option(qn, contents); //if the option exists, remove it so we can add the new option for(int i = 0; i < options.size(); i++) { if(options.get(i).equals(option)) { options.remove(i); break; } } //add option options.add(option); // check predefined options if (Option.PROFILE_QNAME.compareTo(qn) == 0) { // configure profiling profiler.configure(option); } else if(Option.TIMEOUT_QNAME.compareTo(qn) == 0) watchdog.setTimeoutFromOption(option); else if(Option.OUTPUT_SIZE_QNAME.compareTo(qn) == 0) watchdog.setMaxNodesFromOption(option); else if (Option.OPTIMIZE_QNAME.compareTo(qn) == 0) { String params[] = option.tokenizeContents(); if (params.length > 0) { String[] param = Option.parseKeyValuePair(params[0]); if ("enable".equals(param[0])) { if ("yes".equals(param[1])) enableOptimizer = true; else enableOptimizer = false; } } } //TODO : not sure how these 2 options might/have to be related else if (Option.OPTIMIZE_IMPLICIT_TIMEZONE.compareTo(qn) == 0) { //TODO : error check Duration duration = TimeUtils.getInstance().newDuration(option.getContents()); implicitTimeZone = new SimpleTimeZone((int)duration.getTimeInMillis(new Date()), "XQuery context"); } else if (Option.CURRENT_DATETIME.compareTo(qn) == 0) { //TODO : error check DateTimeValue dtv = new DateTimeValue(option.getContents()); calendar = (XMLGregorianCalendar)dtv.calendar.clone(); } } public Option getOption(QName qname) { /* * check dynamic options that were declared at run-time * first as these have precedence and then check * static options that were declare at compile time */ if(dynamicOptions != null) { for(int i = 0; i < dynamicOptions.size(); i++) { Option option = (Option)dynamicOptions.get(i); if(qname.compareTo(option.getQName()) == 0) { return option; } } } if(staticOptions != null) { for(int i = 0; i < staticOptions.size(); i++) { Option option = (Option)staticOptions.get(i); if(qname.compareTo(option.getQName()) == 0) { return option; } } } return null; } public Pragma getPragma(String name, String contents) throws XPathException { QName qname = QName.parse(this, name); if ("".equals(qname.getNamespaceURI())) { throw new XPathException("XPST0081: pragma's ('" + name +"') namespace URI is empty"); } else if (Namespaces.EXIST_NS.equals(qname.getNamespaceURI())) { contents = StringValue.trimWhitespace(contents); if (TimerPragma.TIMER_PRAGMA.equalsSimple(qname)) { return new TimerPragma(qname, contents); } if (Optimize.OPTIMIZE_PRAGMA.equalsSimple(qname)) { return new Optimize(this, qname, contents, true); } if (BatchTransactionPragma.BATCH_TRANSACTION_PRAGMA.equalsSimple(qname)) { return new BatchTransactionPragma(qname, contents); } if (ForceIndexUse.EXCEPTION_IF_INDEX_NOT_USED_PRAGMA.equalsSimple(qname)) { return new ForceIndexUse(qname, contents); } if (ProfilePragma.PROFILING_PRAGMA.equalsSimple(qname)) { return new ProfilePragma(qname, contents); } if (NoIndexPragma.NO_INDEX_PRAGMA.equalsSimple(qname)) { return new NoIndexPragma(qname, contents); } } return null; } /** * Store the supplied data to a temporary document fragment. * * @param doc * @throws XPathException */ public DocumentImpl storeTemporaryDoc(org.exist.memtree.DocumentImpl doc) throws XPathException { try { DocumentImpl targetDoc = broker.storeTempResource(doc); if (targetDoc == null) throw new XPathException("Internal error: failed to store temporary doc fragment"); LOG.debug("Stored: " + targetDoc.getDocId() + ": " + targetDoc.getURI()); return targetDoc; } catch (EXistException e) { throw new XPathException(TEMP_STORE_ERROR, e); } catch (PermissionDeniedException e) { throw new XPathException(TEMP_STORE_ERROR, e); } catch (LockException e) { throw new XPathException(TEMP_STORE_ERROR, e); } } public void setAttribute(String attribute, Object value) { attributes.put(attribute, value); } public Object getAttribute(String attribute) { return attributes.get(attribute); } /** * Set an XQuery Context variable. * General variable storage in the xquery context * * @param name The variable name * @param XQvar The variable value, may be of any xs: type */ public void setXQueryContextVar(String name, Object XQvar) { XQueryContextVars.put(name, XQvar); } /** * Get an XQuery Context variable. * General variable storage in the xquery context * * @param name The variable name * @return The variable value indicated by name. */ public Object getXQueryContextVar(String name) { return(XQueryContextVars.get(name)); } /** * Starts a batch Transaction */ public void startBatchTransaction() throws TransactionException { //only allow one batch to exist at once, if there is a current batch then commit them if(batchTransaction != null) finishBatchTransaction(); TransactionManager txnMgr = getBroker().getBrokerPool().getTransactionManager(); batchTransaction = txnMgr.beginTransaction(); } /** * Determines if a batch transaction should be performed * * @return true if a batch update transaction should be performed */ public boolean hasBatchTransaction() { return(batchTransaction != null); } /** * Get the Transaction for the batch * * @return The Transaction */ public Txn getBatchTransaction() { return batchTransaction; } /** * Set's that a trigger should be executed for the provided document as part of the batch transaction * * @param doc The document to trigger for */ public void setBatchTransactionTrigger(DocumentImpl doc) { //we want the last updated version of the document, so remove any previous version (matched by xmldburi) Iterator itTrigDoc = batchTransactionTriggers.getDocumentIterator(); while(itTrigDoc.hasNext()) { DocumentImpl trigDoc = (DocumentImpl)itTrigDoc.next(); if(trigDoc.getURI().equals(doc.getURI())) { itTrigDoc.remove(); break; } } //store the document so we can later finish the trigger batchTransactionTriggers.add(doc); } /** * Completes a batch transaction, by committing the transaction and calling finish on any triggers * set by setBatchTransactionTrigger() * */ public void finishBatchTransaction() throws TransactionException { if(batchTransaction != null) { //commit the transaction batch TransactionManager txnMgr = getBroker().getBrokerPool().getTransactionManager(); txnMgr.commit(batchTransaction); //finish any triggers Iterator itDoc = batchTransactionTriggers.getDocumentIterator(); while(itDoc.hasNext()) { DocumentImpl doc = (DocumentImpl)itDoc.next(); //finish the trigger CollectionConfiguration config = doc.getCollection().getConfiguration(getBroker()); if(config != null) { DocumentTrigger trigger = null; try { trigger = (DocumentTrigger)config.newTrigger(Trigger.UPDATE_DOCUMENT_EVENT, getBroker(), doc.getCollection()); } catch (CollectionConfigurationException e) { LOG.debug("An error occurred while initializing a trigger for collection " + doc.getCollection().getURI() + ": " + e.getMessage(), e); } if(trigger != null) { try { trigger.finish(Trigger.UPDATE_DOCUMENT_EVENT, getBroker(), TriggerStatePerThread.getTransaction(), doc.getURI(), doc); } catch(Exception e) { LOG.debug("Trigger event UPDATE_DOCUMENT_EVENT for collection: " + doc.getCollection().getURI() + " with: " + doc.getURI() + " " + e.getMessage()); } } } } batchTransactionTriggers.clear(); batchTransaction = null; } } /** * Load the default prefix/namespace mappings table and set up * internal functions. */ protected void loadDefaults(Configuration config) { this.watchdog = new XQueryWatchDog(this); /* SymbolTable syms = broker.getSymbols(); String[] pfx = syms.defaultPrefixList(); namespaces = new HashMap(pfx.length); prefixes = new HashMap(pfx.length); String sym; for (int i = 0; i < pfx.length; i++) { sym = syms.getDefaultNamespace(pfx[i]); namespaces.put(pfx[i], sym); prefixes.put(sym, pfx[i]); } */ loadDefaultNS(); String param = (String) getBroker().getConfiguration().getProperty(PROPERTY_ENABLE_QUERY_REWRITING); enableOptimizer = param != null && param.equals("yes"); param = (String) getBroker().getConfiguration().getProperty(PROPERTY_XQUERY_BACKWARD_COMPATIBLE); backwardsCompatible = param == null || param.equals("yes"); Boolean option = ((Boolean) getBroker().getConfiguration().getProperty(PROPERTY_XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL)); raiseErrorOnFailedRetrieval = option != null && option.booleanValue(); // load built-in modules Map modules = (Map) config.getProperty(PROPERTY_BUILT_IN_MODULES); if (modules != null) { for (Iterator i = modules.entrySet().iterator(); i.hasNext(); ) { Map.Entry entry = (Map.Entry) i.next(); Class mClass = (Class) entry.getValue(); String namespaceURI = (String) entry.getKey(); // first check if the module has already been loaded // in the parent context Module module = getModule(namespaceURI); if (module == null) { instantiateModule(namespaceURI, mClass); } else if (getPrefixForURI(module.getNamespaceURI()) == null && module.getDefaultPrefix().length() > 0) { // make sure the namespaces of default modules are known, // even if they were imported in a parent context try { declareNamespace(module.getDefaultPrefix(), module.getNamespaceURI()); } catch (XPathException e) { LOG.warn("Internal error while loading default modules: " + e.getMessage(), e); } } } } } protected void loadDefaultNS() { try { // default namespaces staticNamespaces.put("xml", Namespaces.XML_NS); staticPrefixes.put(Namespaces.XML_NS, "xml"); declareNamespace("xs", Namespaces.SCHEMA_NS); declareNamespace("xsi", Namespaces.SCHEMA_INSTANCE_NS); //required for backward compatibility declareNamespace("xdt", Namespaces.XPATH_DATATYPES_NS); declareNamespace("fn", Namespaces.XPATH_FUNCTIONS_NS); declareNamespace("local", Namespaces.XQUERY_LOCAL_NS); //*not* as standard NS declareNamespace("exist", Namespaces.EXIST_NS); //TODO : include "err" namespace ? } catch (XPathException e) { //TODO : ignored because it should never happen } } public void registerUpdateListener(UpdateListener listener) { if (updateListener == null) { updateListener = new ContextUpdateListener(); broker.getBrokerPool().getNotificationService().subscribe(updateListener); } updateListener.addListener(listener); } protected void clearUpdateListeners() { if (updateListener != null) broker.getBrokerPool().getNotificationService().unsubscribe(updateListener); updateListener = null; } /** * Check if the XQuery contains pragmas that define serialization settings. * If yes, copy the corresponding settings to the current set of output * properties. * * @param properties the properties object to which serialization parameters will * be added. * @throws XPathException if an error occurs while parsing the option */ public void checkOptions(Properties properties) throws XPathException { Option pragma = 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]); } } /** * Read list of built-in modules from the configuration. This method will only make sure * that the specified module class exists and is a subclass of {@link org.exist.xquery.Module}. * * @param xquery configuration root * @throws DatabaseConfigurationException */ public static void loadModuleClasses(Element xquery, Map classMap, Map externalMap) throws DatabaseConfigurationException { // add the standard function module classMap.put(Namespaces.XPATH_FUNCTIONS_NS, org.exist.xquery.functions.ModuleImpl.class); // add other modules specified in configuration NodeList builtins = xquery.getElementsByTagName(CONFIGURATION_MODULES_ELEMENT_NAME); if (builtins.getLength() > 0) { Element elem = (Element) builtins.item(0); NodeList modules = elem.getElementsByTagName(CONFIGURATION_MODULE_ELEMENT_NAME); if (modules.getLength() > 0) { for (int i = 0; i < modules.getLength(); i++) { elem = (Element) modules.item(i); String uri = elem.getAttribute(XQueryContext.BUILT_IN_MODULE_URI_ATTRIBUTE); String clazz = elem.getAttribute(XQueryContext.BUILT_IN_MODULE_CLASS_ATTRIBUTE); String source = elem.getAttribute(XQueryContext.BUILT_IN_MODULE_SOURCE_ATTRIBUTE); if (uri == null) throw new DatabaseConfigurationException("element 'module' requires an attribute 'uri'"); if (clazz == null && source == null) throw new DatabaseConfigurationException("element 'module' requires either an attribute " + "'class' or 'src'"); if (source != null) { externalMap.put(uri, source); if (LOG.isDebugEnabled()) LOG.debug("Registered mapping for module '" + uri + "' to '" + source + "'"); } else { Class mClass = lookupModuleClass(uri, clazz); if (mClass != null) classMap.put(uri, mClass); if (LOG.isDebugEnabled()) LOG.debug("Configured module '" + uri + "' implemented in '" + clazz + "'"); } } } } } private static Class lookupModuleClass(String uri, String clazz) throws DatabaseConfigurationException { try { Class mClass = Class.forName(clazz); if (!(Module.class.isAssignableFrom(mClass))) { throw new DatabaseConfigurationException("Failed to load module: " + uri + ". Class " + clazz + " is not an instance of org.exist.xquery.Module."); } return mClass; } catch (ClassNotFoundException e) { // Note: can't throw an exception here since this would create // problems with test cases and jar dependencies LOG.warn("Configuration problem: failed to load class for module " + uri + "; class: " + clazz + "; message: " + e.getMessage()); } catch (NoClassDefFoundError e){ LOG.warn("Module " + uri + " could not be initialized due to a missing " + "dependancy (NoClassDefFoundError): " + e.getMessage()); } return null; } private class ContextUpdateListener implements UpdateListener { private List listeners = new ArrayList(); public void addListener(UpdateListener listener) { listeners.add(listener); } public void documentUpdated(DocumentImpl document, int event) { for (int i = 0; i < listeners.size(); i++) { UpdateListener listener = (UpdateListener) listeners.get(i); if (listener != null) listener.documentUpdated(document, event); } } public void unsubscribe() { for (int i = 0; i < listeners.size(); i++) { UpdateListener listener = (UpdateListener) listeners.get(i); if (listener != null) { listener.unsubscribe(); } } listeners.clear(); } public void nodeMoved(NodeId oldNodeId, StoredNode newNode) { for (int i = 0; i < listeners.size(); i++) { UpdateListener listener = (UpdateListener) listeners.get(i); if (listener != null) listener.nodeMoved(oldNodeId, newNode); } } public void debug() { LOG.debug("XQueryContext: "); for (int i = 0; i < listeners.size(); i++) { ((UpdateListener) listeners.get(i)).debug(); } } } private DebuggeeJoint debuggeeJoint = null; public void setDebuggeeJoint(DebuggeeJoint joint) { //XXX: if (debuggeeJoint != null) ??? debuggeeJoint = joint; } public DebuggeeJoint getDebuggeeJoint() { return debuggeeJoint; } private boolean isDebugMode = false; public void setDebugMode(boolean isDebugMode) { this.isDebugMode = isDebugMode; } public boolean isDebugMode() { return isDebugMode; } }