Package org.exist.xquery

Source Code of org.exist.xquery.XQueryContext

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2013 The eXist Project
*  http://exist-db.org
*
*  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 library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*
*  $Id$
*/
package org.exist.xquery;

import java.io.File;
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;

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 org.apache.log4j.Logger;
import org.exist.Database;
import org.exist.EXistException;
import org.exist.Namespaces;
import org.exist.collections.Collection;
import org.exist.debuggee.Debuggee;
import org.exist.debuggee.DebuggeeJoint;
import org.exist.dom.BinaryDocument;
import org.exist.dom.DefaultDocumentSet;
import org.exist.dom.DocumentImpl;
import org.exist.dom.DocumentSet;
import org.exist.dom.MutableDocumentSet;
import org.exist.dom.NodeProxy;
import org.exist.dom.QName;
import org.exist.dom.StoredNode;
import org.exist.http.servlets.RequestWrapper;
import org.exist.interpreter.Context;
import org.exist.memtree.InMemoryXMLStreamReader;
import org.exist.memtree.MemTreeBuilder;
import org.exist.memtree.NodeImpl;
import org.exist.numbering.NodeId;
import org.exist.repo.ExistRepository;
import org.exist.security.AuthenticationException;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.Subject;
import org.exist.security.xacml.*;
import org.exist.source.*;
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.util.Collations;
import org.exist.util.Configuration;
import org.exist.util.LockException;
import org.exist.util.hashtable.NamePool;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.functions.request.RequestModule;
import org.exist.xquery.parser.*;
import org.exist.xquery.pragmas.*;
import org.exist.xquery.update.Modification;
import org.exist.xquery.value.*;

import antlr.RecognitionException;
import antlr.TokenStreamException;
import antlr.collections.AST;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
* 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 implements BinaryValueManager, Context
{
    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";
    public static final String               ENFORCE_INDEX_USE_ATTRIBUTE              = "enforce-index-use";

    //TODO : move elsewhere ?
    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;
    public static final String               PROPERTY_ENFORCE_INDEX_USE            = "xquery.enforce-index-use";

    //TODO : move elsewhere ?
    public static final String                         PROPERTY_BUILT_IN_MODULES                        = "xquery.modules";
    public static final String                         PROPERTY_STATIC_MODULE_MAP                       = "xquery.modules.static";
    public static final String                         PROPERTY_MODULE_PARAMETERS                       = "xquery.modules.parameters";

    public 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";
    public static final String                         XQUERY_CONTEXTVAR_XQUERY_UPDATE_ERROR            = "_eXist_xquery_update_error";
    public static final String                         HTTP_SESSIONVAR_XMLDB_USER                       = "_eXist_xmldb_user";
    public static final String                         HTTP_REQ_ATTR_USER                               = "xquery.user";
    public static final String                         HTTP_REQ_ATTR_PASS                               = "xquery.password";

    // Static namespace/prefix mappings
    protected HashMap<String, String>                  staticNamespaces                                 = new HashMap<String, String>();

    // Static prefix/namespace mappings
    protected HashMap<String, String>                  staticPrefixes                                   = new HashMap<String, String>();

    // Local in-scope namespace/prefix mappings in the current context
    protected HashMap<String, String>                  inScopeNamespaces                                = new HashMap<String, String>();

    // Local prefix/namespace mappings in the current context
    protected HashMap<String, String>                  inScopePrefixes                                  = new HashMap<String, String>();

    // Inherited in-scope namespace/prefix mappings in the current context
    protected HashMap<String, String>                  inheritedInScopeNamespaces                       = new HashMap<String, String>();

    // Inherited prefix/namespace mappings in the current context
    protected HashMap<String, String>                  inheritedInScopePrefixes                         = new HashMap<String, String>();

    protected HashMap<String, XmldbURI>                mappedModules                                    = new HashMap<String, XmldbURI>();

    private boolean                                    preserveNamespaces                               = true;

    private boolean                                    inheritNamespaces                                = true;

    // Local namespace stack
    protected Stack<HashMap<String, String>>           namespaceStack                                   = new Stack<HashMap<String, String>>();

    // Known user defined functions in the local module
    protected TreeMap<FunctionId, UserDefinedFunction> declaredFunctions                                = new TreeMap<FunctionId, UserDefinedFunction>();

    // 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<FunctionSignature>                 callStack                                        = new Stack<FunctionSignature>();

    // The current size of the variable stack
    protected int                                      variableStackSize                                = 0;

    // Unresolved references to user defined functions
    protected Stack<FunctionCall>                      forwardReferences                                = new Stack<FunctionCall>();

    // List of options declared for this query at compile time - i.e. declare option
    protected List<Option>                             staticOptions                                    = null;

    // List of options declared for this query at run time - i.e. util:declare-option()
    protected List<Option>                             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>();

    /** Used to save current state when modules are imported dynamically */
    protected SavedState                    savedState                    = new SavedState();
   
    /**
     * 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.)
     */
    @SuppressWarnings( "unused" )
    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;

    /** A general-purpose map to set attributes in the current query context. */
    protected Map<String, Object>                      attributes                    = new HashMap<String, Object>();

    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;
    private Sequence                                   contextSequence               = 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<MemTreeBuilder>                      fragmentStack                 = new Stack<MemTreeBuilder>();

    /** 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. */
    protected Profiler                                 profiler;

    //For holding XQuery Context variables for general storage in the XQuery Context
    HashMap<String, Object>                            XQueryContextVars             = new HashMap<String, Object>();
   
    //For holding the environment variables
    Map<String,String> envs;
   
    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 Source source = null;
   
    private XACMLSource                                xacmlSource                        = null;

    private DebuggeeJoint                              debuggeeJoint                 = null;

    private int                                        xqueryVersion                 = 10;
   
    protected Database db;

    private boolean analyzed = false;
   
    /**
     * The Subject of the User that requested the execution of the XQuery
     * attached by this Context. This is not the same as the Effective User
     * as we may be executed setUid or setGid. The Effective User can be retrieved
     * through broker.getSubject()
     */
    private Subject realUser;

    public synchronized ExistRepository getRepository()
    throws XPathException {
        return getBroker().getBrokerPool().getExpathRepo();
    }

    private Module resolveInEXPathRepository(String namespace, String prefix)
            throws XPathException
    {
        // the repo and its eXist handler
        final ExistRepository repo = getRepository();
        // try an internal module
        final Module mod = repo.resolveJavaModule(namespace, this);
        if ( mod != null ) {
            return mod;
        }
        // try an eXist-specific module
        final File resolved = repo.resolveXQueryModule(namespace);
        // use the resolved file or return null
        if ( resolved == null ) {
            return null;
        }
        // build a module object from the file
        final Source src = new FileSource(resolved, "utf-8", false);
        return compileOrBorrowModule(prefix, namespace, "", src);
    }
    // TODO: end of expath repo manageer, may change


    protected XQueryContext( AccessContext accessCtx )
    {
        if( accessCtx == null ) {
            throw( new NullAccessContextException() );
        }
        this.accessCtx = accessCtx;
        profiler = new Profiler( null );
    }


    public XQueryContext( Database db, AccessContext accessCtx )
    {
        this( accessCtx );
        this.db = db;
        loadDefaults( db.getConfiguration() );
        this.profiler = new Profiler( db );
    }


    public XQueryContext( XQueryContext copyFrom )
    {
        this( copyFrom.getAccessContext() );
        this.db = copyFrom.db;
        loadDefaultNS();
        final Iterator<String> prefixes = copyFrom.staticNamespaces.keySet().iterator();

        while( prefixes.hasNext() ) {
            final String prefix = prefixes.next();

            if( "xml".equals(prefix) || "xmlns".equals(prefix) ) {
                continue;
            }

            try {
                declareNamespace( prefix, copyFrom.staticNamespaces.get( prefix ) );
            }
            catch( final 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()
    {
        final 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;
        this.dynamicOptions        = from.dynamicOptions;
        this.staticOptions        = from.staticOptions;
        this.db              = from.db;
    }


    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<FunctionId, UserDefinedFunction>( this.declaredFunctions );
        ctx.globalVariables          = new TreeMap<QName, Variable>( this.globalVariables );
        ctx.attributes               = new HashMap<String, Object>( this.attributes );

        // make imported modules available in the new context
        ctx.modules                  = new HashMap<String, Module>();

        for( final Module module : this.modules.values() ) {

            try {
                ctx.modules.put( module.getNamespaceURI(), module );
                final String prefix = this.staticPrefixes.get( module.getNamespaceURI() );
                ctx.declareNamespace( prefix, module.getNamespaceURI() );
            }
            catch( final XPathException e ) {
                // ignore
            }
        }
        ctx.allModules = new HashMap<String, Module>();

        for( final Module module : this.allModules.values() ) {

            if( module != null ) { //UNDERSTAND: why is it possible? -shabanovd
                ctx.allModules.put( module.getNamespaceURI(), module );
            }
        }

        ctx.watchdog          = this.watchdog;
        ctx.profiler          = getProfiler();
        ctx.lastVar           = this.lastVar;
        ctx.variableStackSize = getCurrentStackSize();
        ctx.contextStack      = this.contextStack;
        ctx.mappedModules     = new HashMap<String, XmldbURI>( this.mappedModules );
        ctx.staticNamespaces  = new HashMap<String, String>( this.staticNamespaces );
        ctx.staticPrefixes    = new HashMap<String, String>( this.staticPrefixes );
       
        if (this.dynamicOptions != null){
          ctx.dynamicOptions = new ArrayList<Option>( this.dynamicOptions );
        }
       
        if (this.staticOptions != null){
          ctx.staticOptions = new ArrayList<Option>( this.staticOptions );
        }
       
    }


    /**
     * Prepares the current context before xquery execution.
     */
    @Override
    public void prepareForExecution() {
        //if there is an existing user in the current http session
        //then set the DBBroker user
      final Subject user = getUserFromHttpSession();
        if(user != null) {
            getBroker().setSubject(user);
        }
       
        setRealUser(getBroker().getSubject());

        //Reset current context position
        setContextSequencePosition( 0, null );
        //Note that, for some reasons, an XQueryContext might be used without calling this method
    }


    public AccessContext getAccessContext()
    {
        return( accessCtx );
    }


    /**
     * Is profiling enabled?
     *
     * @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 );
    }


    @Override
    public void setXacmlSource(final XACMLSource xacmlSource) {
        this.xacmlSource = xacmlSource;
    }


    @Override
    public XACMLSource getXacmlSource() {
        return xacmlSource;
    }

    /**
     * Declare a user-defined static prefix/namespace mapping.
     *
     * <p>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.</p>
     *
     * @param   prefix
     * @param   uri
     *
     * @throws  XPathException 
     */
    public void declareNamespace( String prefix, String uri ) throws XPathException
    {
        if( prefix == null ) {
            prefix = "";
        }

        if( uri == null ) {
            uri = "";
        }

        if( "xml".equals(prefix) || "xmlns".equals(prefix) ) {
            throw( new XPathException( ErrorCodes.XQST0070, "Namespace predefined prefix '" + prefix + "' can not be bound" ) );
        }

        if( uri.equals( Namespaces.XML_NS ) ) {
            throw( new XPathException( ErrorCodes.XQST0070, "Namespace URI '" + uri + "' must be bound to the 'xml' prefix" ) );
        }
       
        final String prevURI = staticNamespaces.get( prefix );

        //This prefix was not bound
        if( prevURI == null ) {
   
            if( uri.length() > 0 ) {
                //Bind it
                staticNamespaces.put( prefix, uri );
                staticPrefixes.put( uri, prefix );
                return;
               
            } else {
                //Nothing to bind

                //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(    ( "xs".equals(prefix)    && Namespaces.SCHEMA_NS.equals( prevURI ) )
                || ( "xsi".equals(prefix)   && Namespaces.SCHEMA_INSTANCE_NS.equals( prevURI ) )
                || ( "xdt".equals(prefix)   && Namespaces.XPATH_DATATYPES_NS.equals( prevURI ) )
                || ( "fn".equals(prefix)    && Namespaces.XPATH_FUNCTIONS_NS.equals( prevURI ) )
                || ( "math".equals(prefix)) && Namespaces.XPATH_FUNCTIONS_MATH_NS.equals( prevURI )
                || ( "local".equals(prefix) && 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;
                   
                } else {
                    //Nothing to bind (not sure if it should raise an error though)

                    //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( ErrorCodes.XQST0033, "prefix '"+prefix+"' bind to '"+prevURI+"'" ) );
                }
            }
        }
    }


    public void declareNamespaces( Map<String, String> namespaceMap )
    {
        String prefix;
        String uri;

        for( final Map.Entry<String, String> entry : namespaceMap.entrySet() ) {
            prefix = entry.getKey();
            uri    = 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( final Iterator<String> i = staticNamespaces.values().iterator(); i.hasNext(); ) {

            if( i.next().equals( uri ) ) {
                i.remove();
                return;
            }
        }
        inScopePrefixes.remove( uri );

        if( inScopeNamespaces != null ) {

            for( final Iterator<String> i = inScopeNamespaces.values().iterator(); i.hasNext(); ) {

                if( i.next().equals( uri ) ) {
                    i.remove();
                    return;
                }
            }
        }

        //TODO : is this relevant ?
        inheritedInScopePrefixes.remove( uri );

        if( inheritedInScopeNamespaces != null ) {

            for( final Iterator<String> i = inheritedInScopeNamespaces.values().iterator(); i.hasNext(); ) {

                if( 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 : inScopeNamespaces.get( prefix ) );
    }


    public String getInScopePrefix( String uri )
    {
        return( ( inScopePrefixes == null ) ? null : inScopePrefixes.get( uri ) );
    }

    public Map<String, String> getInScopePrefixes( )
    {
        return( ( inScopePrefixes == null ) ? null : inScopePrefixes );
    }

    public String getInheritedNamespace( String prefix )
    {
        return( ( inheritedInScopeNamespaces == null ) ? null : inheritedInScopeNamespaces.get( prefix ) );
    }


    public String getInheritedPrefix( String uri )
    {
        return( ( inheritedInScopePrefixes == null ) ? null : 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 : inScopeNamespaces.get( prefix );

        if( uri != null ) {
            return( uri );
        }

        if( inheritNamespaces ) {
            uri = ( inheritedInScopeNamespaces == null ) ? null : inheritedInScopeNamespaces.get( prefix );

            if( uri != null ) {
                return( uri );
            }
        }
        return( 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;
              */
    }


    /**
     * Get URI Prefix
     *
     * @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 : inScopePrefixes.get( uri );

        if( prefix != null ) {
            return( prefix );
        }

        if( inheritNamespaces ) {
            prefix = ( inheritedInScopePrefixes == null ) ? null : inheritedInScopePrefixes.get( uri );

            if( prefix != null ) {
                return( prefix );
            }
        }
        return( staticPrefixes.get( uri ) );
    }


    /**
     * Clear all user-defined prefix/namespace mappings.
     *
     * @return 
     */
    // 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
     *
     * @throws  XPathException 
     */
    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
     *
     * @throws  XPathException 
     */
    public String getDefaultElementNamespaceSchema() throws XPathException
    {
        return( defaultElementNamespaceSchema.getStringValue() );
    }


    /**
     * Set the default element namespace. By default, this points to the empty uri.
     *
     * @param   uri
     *
     * @throws  XPathException 
     */
    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
     *
     * @throws  XPathException 
     */
    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( final 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( final 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 );
    }


    /**
     * Get statically known documents
     *
     * @return  set of statically known documents.
     *
     * @throws  XPathException 
     */
    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
            try {
                getBroker().getAllXMLResources( ndocs );
            } catch(final PermissionDeniedException pde) {
                LOG.warn("Permission denied to read resource all resources" + pde.getMessage(), pde);
                throw new XPathException("Permission denied to read resource all resources" + pde.getMessage(), pde);
            }
        } else {
            DocumentImpl doc;
            Collection   collection;

            for( int i = 0; i < staticDocumentPaths.length; i++ ) {

                try {
                    collection = getBroker().getCollection( staticDocumentPaths[i] );

                    if( collection != null ) {
                        collection.allDocs( getBroker(), ndocs, true);
                    } else {
                        doc = getBroker().getXMLResource( staticDocumentPaths[i], Lock.READ_LOCK );

                        if( doc != null ) {

                            if( doc.getPermissions().validate(
                                getBroker().getSubject(), Permission.READ ) ) {
                               
                              ndocs.add( doc );
                            }
                            doc.getUpdateLock().release( Lock.READ_LOCK );
                        }
                    }
                }
                catch( final 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 ) {
            final NodeImpl node = (NodeImpl)nv;
            reader = new InMemoryXMLStreamReader( node.getDocument(), node.getDocument() );
        } else {
            final 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?
     *
     * <p>see #setLockDocumentsOnLoad(boolean)</p>
     */
    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.
     *
     * @param  keepGlobals 
     */
    @Override
    public void reset(final boolean keepGlobals) {
        setRealUser(null);
       
        if( modifiedDocuments != null ) {

            try {
                Modification.checkFragmentation( this, modifiedDocuments );
            }
            catch( final EXistException e ) {
                LOG.warn( "Error while checking modified documents: " + e.getMessage(), e );
            }
            modifiedDocuments = null;
        }
        calendar         = null;
        implicitTimeZone = null;
       
        resetDocumentBuilder();

        contextSequence = null;

        if( !keepGlobals ) {

            // do not reset the statically known documents
            staticDocumentPaths = null;
            staticDocuments     = null;
        }

        if( !isShared ) {
            lastVar = null;
        }
        fragmentStack = new Stack<MemTreeBuilder>();
        callStack.clear();
        protectedDocuments = null;

        if( !keepGlobals ) {
            globalVariables.clear();
        }

        if( dynamicOptions != null ) {
            dynamicOptions.clear(); //clear any dynamic options
        }

        if( !isShared ) {
            watchdog.reset();
        }

        for( final Module module : modules.values() ) {
            if (module instanceof ExternalModule && ((ModuleContext)((ExternalModule)module).getContext()).getParentContext() != this) {
                continue;
            }
            module.reset( this );
        }

        if( !keepGlobals ) {
            mappedModules.clear();
        }

        savedState.restore();
       
        //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();
       
        attributes.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 greatest, 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;
    }


    /**
     * Get modules
     *
     * @return  iterator over all modules imported into this context
     */
    public Iterator<Module> getModules()
    {
        return( modules.values().iterator() );
    }


    /**
     * Get root modules
     *
     * @return  iterator over all modules registered in the entire context tree
     */
    public Iterator<Module> getRootModules()
    {
        return( getAllModules() );
    }


    public Iterator<Module> 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( modules.get( namespaceURI ) );
    }


    public Module getRootModule( String namespaceURI )
    {
        return( allModules.get( namespaceURI ) );
    }


    public void setModule( String namespaceURI, Module module )
    {
        if( module == null ) {
            modules.remove( namespaceURI ); // unbind the module
        } else {
            modules.put( namespaceURI, module );
        }
        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 (final Module module : allModules.values() ) {
        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 (final Module module : expr.getContext().modules.values()) {
            if( !module.isInternalModule() ) {
              final Expression root = ((ExternalModule)module).getRootExpression();
              ((ExternalModule)module).getContext().analyzeAndOptimizeIfModulesChanged(root);
            }
      }
        expr.analyze( new AnalyzeContextInfo() );

        if( optimizationsEnabled() ) {
            final 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
     *
     * @return   Module
     */
    public Module loadBuiltInModule( String namespaceURI, String moduleClass )
    {
        Module module = null;
        if (namespaceURI != null)
            {module = getModule( namespaceURI );}

        if( module != null ) {
//          LOG.debug("module " + namespaceURI + " is already present");
            return( module );
        }
        return( initBuiltInModule( namespaceURI, moduleClass ) );
    }


    @SuppressWarnings( "unchecked" )
    protected Module initBuiltInModule( String namespaceURI, String moduleClass )
    {
        Module module = null;

        try {

            // lookup the class
            final 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, (Class<Module>)mClass );
            // INOTE: expathrepo
             module = instantiateModule( namespaceURI, (Class<Module>)mClass, (Map<String, Map<String, List<? extends Object>>>) getBroker().getConfiguration().getProperty(PROPERTY_MODULE_PARAMETERS));
            //LOG.debug("module " + module.getNamespaceURI() + " loaded successfully.");
        }
        catch( final ClassNotFoundException e ) {
            LOG.warn( "module class " + moduleClass + " not found. Skipping..." );
        }
        return( module );
    }


    protected Module instantiateModule( String namespaceURI, Class<Module> mClass, Map<String, Map<String, List<? extends Object>>> moduleParameters) {
        Module module = null;

        try {

            final Constructor<Module> cnstr = mClass.getConstructor(Map.class);
           
            module = cnstr.newInstance(moduleParameters.get(namespaceURI));

            if(namespaceURI != null && !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);
        } catch(final InstantiationException ie) {
            LOG.warn("error while instantiating module class " + mClass.getName(), ie);
        } catch(final IllegalAccessException iae) {
            LOG.warn("error while instantiating module class " + mClass.getName(), iae);
        } catch(final XPathException xpe) {
            LOG.warn("error while instantiating module class " + mClass.getName(), xpe);
        } catch(final NoSuchMethodException nsme) {
            LOG.warn("error while instantiating module class " + mClass.getName(), nsme);
        } catch(final InvocationTargetException ite) {
            LOG.warn("error while instantiating module class " + mClass.getName(), ite);
        }
       
        return module;
    }


    /**
     * 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 db.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.
     
      final QName name = function.getSignature().getName();
     
        if(Namespaces.XML_NS.equals(name.getNamespaceURI())) {
            throw new XPathException(function, ErrorCodes.XQST0045, "Function '" + name + "' is in the forbidden namespace '" + Namespaces.XML_NS + "'" );
        }

        if(Namespaces.SCHEMA_NS.equals(name.getNamespaceURI())) {
            throw new XPathException(function, ErrorCodes.XQST0045, "Function '" + name + "' is in the forbidden namespace '" + Namespaces.SCHEMA_NS + "'");
        }

        if(Namespaces.SCHEMA_INSTANCE_NS.equals(name.getNamespaceURI())) {
            throw new XPathException(function, ErrorCodes.XQST0045, "Function '" + name + "' is in the forbidden namespace '" + Namespaces.SCHEMA_INSTANCE_NS + "'");
        }

        if(Namespaces.XPATH_FUNCTIONS_NS.equals(name.getNamespaceURI())) {
            throw new XPathException(function, ErrorCodes.XQST0045, "Function '" + name + "' is in the forbidden namespace '" + Namespaces.XPATH_FUNCTIONS_NS + "'");
        }

        if("".equals( name.getNamespaceURI())) {
            throw new XPathException(function, ErrorCodes.XQST0060, "Every declared function name must have a non-null namespace URI, but function '" + name + "' does not meet this requirement.");
        }

        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
     * @param   argCount 
     *
     * @return  user-defined function
     *
     * @throws  XPathException
     */
    public UserDefinedFunction resolveFunction( QName name, int argCount ) throws XPathException
    {
        final FunctionId          id   = new FunctionId( name, argCount );
        final UserDefinedFunction func = declaredFunctions.get( id );
        return( func );
    }


    public Iterator<FunctionSignature> getSignaturesForFunction( QName name )
    {
        final ArrayList<FunctionSignature> signatures = new ArrayList<FunctionSignature>( 2 );

        for( final UserDefinedFunction func : declaredFunctions.values() ) {

            if( func.getName().equals( name ) ) {
                signatures.add( func.getSignature() );
            }
        }
        return( signatures.iterator() );
    }


    public Iterator<UserDefinedFunction> localFunctions()
    {
        return( declaredFunctions.values().iterator() );
    }


    /**
     * Declare a local variable. This is called by variable binding expressions like "let" and "for".
     *
     * @param   var
     *
     * @return   LocalVariable
     *
     * @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
     *
     * @return  Variable
     *
     * @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.
     *
     * <p>The value argument is converted into an XPath value (@see XPathUtil#javaObjectToXPath(Object)).</p>
     *
     * @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;
        final Module   module = getModule( qn.getNamespaceURI() );

        if( module != null ) {
            var = module.declareVariable( qn, value );
            return( var );
        }
        final Sequence val = XPathUtil.javaObjectToXPath( value, this );
        var = globalVariables.get( qn );

        if( var == null ) {
            var = new VariableImpl( 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
    {
        final 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 ) {
            final 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
    {
        final 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 )
    {
        final Module module = getModule( qname.getNamespaceURI() );

        if( module != null ) {

            if( module.isVarDeclared( qname ) ) {
                return( true );
            }
        }
        return( globalVariables.get( qname ) != null );
    }


    public Map<QName, Variable> getVariables()
    {
        final Map<QName, Variable> variables = new HashMap<QName, Variable>();

        variables.putAll( globalVariables );

        final 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() {
        final Map<QName, Variable> variables = new HashMap<QName, Variable>();

        final 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 );
    }

    /**
     * Return a copy of all currently visible local variables.
     * Used by {@link InlineFunction} to implement closures.
     *
     * @return currently visible local variables as a stack
     */
    public List<Variable> getLocalStack() {
      final List<Variable> variables = new ArrayList<Variable>(10);
     
      final LocalVariable end = contextStack.isEmpty() ? null : contextStack.peek();

        for ( LocalVariable var = lastVar; var != null; var = var.before ) {

            if ( var == end ) {
                break;
            }

            variables.add( new LocalVariable(var, true) );
        }

        return ( variables );
    }
   
    public Map<QName, Variable> getGlobalVariables() {
        final Map<QName, Variable> variables = new HashMap<QName, Variable>();

        variables.putAll( globalVariables );

        return( variables );
    }
   
    /**
     * Restore a saved stack of local variables. Used to implement closures.
     *
     * @param stack
     * @throws XPathException
     */
    public void restoreStack(List<Variable> stack) throws XPathException {
      for (final Variable var : stack) {
        declareVariableBinding((LocalVariable) var);
      }
    }
   
    /**
     * Turn on/off XPath 1.0 backwards compatibility.
     *
     * <p>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.</p>
     *
     * @param  backwardsCompatible
     */
    public void setBackwardsCompatibility( boolean backwardsCompatible )
    {
        this.backwardsCompatible = backwardsCompatible;
    }


    /**
     * XPath 1.0 backwards compatibility turned on?
     *
     * <p>In XPath 1.0 compatible mode, additional conversions will be applied to values if a numeric value is expected.</p>
     */
    public boolean isBackwardsCompatible()
    {
        return( this.backwardsCompatible );
    }


    public boolean isRaiseErrorOnFailedRetrieval()
    {
        return( raiseErrorOnFailedRetrieval );
    }


    public Database getDatabase() {
      return db;
    }

    /**
     * Get the DBBroker instance used for the current query.
     *
     * <p>The DBBroker is the main database access object, providing access to all internal database functions.</p>
     *
     * @return  DBBroker instance
     */
    public DBBroker getBroker() {
      return db.getActiveBroker();
    }

    /**
     * Get the user which executes the current query.
     *
     * @return  user
     * @deprecated use getSubject
     */
    public Subject getUser() {
        return getSubject();
    }

    /**
     * Get the subject which executes the current query.
     *
     * @return  subject
     */
    public Subject getSubject() {
        return getBroker().getSubject();
    }

   
    /**
     * 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
     */
    public Subject getUserFromHttpSession()
    {
        final RequestModule myModule = (RequestModule)getModule( RequestModule.NAMESPACE_URI );

        //Sanity check : one may *not* want to bind the module !
        if( myModule == null ) {
            return( null );
        }

        Variable var = null;

        try {
            var = myModule.resolveVariable( RequestModule.REQUEST_VAR );
        }
        catch( final XPathException xpe ) {
            return( null );
        }

        if( ( var != null ) && ( var.getValue() != null ) ) {

            if( var.getValue().getItemType() == Type.JAVA_OBJECT ) {
                final JavaObjectValue reqValue = (JavaObjectValue)var.getValue().itemAt( 0 );

                if( reqValue.getObject() instanceof RequestWrapper) {
                    final RequestWrapper req = (RequestWrapper) reqValue.getObject();
                    final Object user = req.getAttribute(HTTP_REQ_ATTR_USER);
                    final Object passAttr = req.getAttribute(HTTP_REQ_ATTR_PASS);
                    if (user != null) {
                        final String password = passAttr == null ? null : passAttr.toString();
                        try {
                            return getBroker().getBrokerPool().getSecurityManager().authenticate(user.toString(), password);
                        } catch (final AuthenticationException e) {
                            LOG.error("User can not be authenticated: " + user.toString());
                        }
                    } else {
                        if (req.getSession() != null) {
                            return (Subject) req.getSession().getAttribute(HTTP_SESSIONVAR_XMLDB_USER);
                        }
                    }
                }
            }
        }

        return( null );
    }

    /** The builder used for creating in-memory document fragments. */
    private MemTreeBuilder documentBuilder = null;
   
    /**
     * Get the document builder currently used for creating temporary document fragments. A new document builder will be created on demand.
     *
     * @return  document builder
     */
    @Override
    public MemTreeBuilder getDocumentBuilder() {
        if(documentBuilder == null) {
            documentBuilder = new MemTreeBuilder(this);
            documentBuilder.startDocument();
        }
        return documentBuilder;
    }

    @Override
    public MemTreeBuilder getDocumentBuilder(boolean explicitCreation) {
        if(documentBuilder == null) {
            documentBuilder = new MemTreeBuilder(this);
            documentBuilder.startDocument(explicitCreation);
        }
        return documentBuilder;
    }
   
    private void resetDocumentBuilder() {
        setDocumentBuilder(null);
    }
   
    private void setDocumentBuilder(MemTreeBuilder documentBuilder) {
        this.documentBuilder = documentBuilder;
    }
   
   


    /**
     * 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 prologEnter(Expression expr) {
        if (debuggeeJoint != null) {
            debuggeeJoint.prologEnter(expr);
        }
    }

    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 void setWatchDog( XQueryWatchDog watchdog )
    {
        this.watchdog = watchdog;
    }


    public XQueryWatchDog getWatchDog()
    {
        return( watchdog );
    }


    /**
     * Push any document fragment created within the current execution context on the stack.
     */
    public void pushDocumentContext()
    {
        fragmentStack.push(getDocumentBuilder());
        resetDocumentBuilder();
    }


    public void popDocumentContext()
    {
        if( !fragmentStack.isEmpty() ) {
            setDocumentBuilder(fragmentStack.pop());
        }
    }


    /**
     * Set the base URI for the evaluation context.
     *
     * <p>This is the URI returned by the fn:base-uri() function.</p>
     *
     * @param  uri
     */
    public void setBaseURI( AnyURIValue uri )
    {
        setBaseURI( uri, false );
    }


    /**
     * Set the base URI for the evaluation context.
     *
     * <p>A base URI specified via the base-uri directive in the XQuery prolog overwrites any other setting.</p>
     *
     * @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
     */
    @Override
    public void setModuleLoadPath(String path) {
        this.moduleLoadPath = path;
    }


    @Override
    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.
     *
     * <p>This is the URI returned by the fn:base-uri() function.</p>
     *
     * @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
     * @param  sequence 
     */
    public void setContextSequencePosition( int pos, Sequence sequence )
    {
        contextPosition = pos;
        contextSequence = sequence;
    }


    /**
     * 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 Sequence getContextSequence()
    {
        return( contextSequence );
    }


    public void pushInScopeNamespaces()
    {
        pushInScopeNamespaces( true );
    }


    /**
     * Push all in-scope namespace declarations onto the stack.
     *
     * @param  inherit 
     */
    @SuppressWarnings( "unchecked" )
    public void pushInScopeNamespaces( boolean inherit )
    {
        //TODO : push into an inheritedInScopeNamespaces HashMap... and return an empty HashMap
        final HashMap<String, String> m = (HashMap)inScopeNamespaces.clone();
        final HashMap<String, String> 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<String, String>();
            inheritedInScopePrefixes   = new HashMap<String, String>();
        }

        //TODO : consider dynamic instanciation
        inScopeNamespaces = new HashMap<String, String>();
        inScopePrefixes   = new HashMap<String, String>();
    }


    public void popInScopeNamespaces()
    {
        inScopePrefixes            = namespaceStack.pop();
        inScopeNamespaces          = namespaceStack.pop();
        inheritedInScopePrefixes   = namespaceStack.pop();
        inheritedInScopeNamespaces = namespaceStack.pop();
    }


    @SuppressWarnings( "unchecked" )
    public void pushNamespaceContext()
    {
        HashMap<String, String> m = (HashMap)staticNamespaces.clone();
        HashMap<String, String> p = (HashMap)staticPrefixes.clone();
        namespaceStack.push( staticNamespaces );
        namespaceStack.push( staticPrefixes );
        staticNamespaces = m;
        staticPrefixes   = p;
    }


    public void popNamespaceContext()
    {
        staticPrefixes   = namespaceStack.pop();
        staticNamespaces = 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)}.
     *
     * @param   newContext 
     *
     * @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 );
    }


    public void popLocalVariables(LocalVariable var) {
        popLocalVariables(var, null);
    }

    /**
     * Restore the local variable stack to the position marked by variable var.
     *
     * @param  var
     *
     */
    public void popLocalVariables(LocalVariable var, Sequence resultSeq)
    {
        // clear all variables registered after var. they should be out of scope.
        LocalVariable outOfScope = lastVar;
        while (outOfScope != var) {
            if (!outOfScope.isClosureVar()) {
                outOfScope.destroy(this, resultSeq);
            }
            outOfScope = outOfScope.before;
        }
        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.
     *
     * @param  signature 
     */
    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 Module importModule( String namespaceURI, String prefix, String location ) throws XPathException {
     
        if(prefix != null && ("xml".equals(prefix) || "xmlns".equals(prefix))) {
            throw new XPathException(ErrorCodes.XQST0070, "The prefix declared for a module import must not be 'xml' or 'xmlns'.");
        }
       
      if(namespaceURI != null && namespaceURI.isEmpty()) {
            throw new XPathException(ErrorCodes.XQST0088, "The first URILiteral in a module import must be of nonzero length.");
        }
     
        Module module = null;

        if (namespaceURI != null)
            {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 is not specified, try to resolve in expath repo
            if (location == null && namespaceURI != null) {
                module = resolveInEXPathRepository(namespaceURI, prefix);
            }

            if ( module == null ) {

                if( location == null && namespaceURI != 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 moduleSource;

                    if( location.startsWith( XmldbURI.XMLDB_URI_PREFIX )
                            || ( ( location.indexOf( ':' ) == -1 ) && 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 ) ) {
                                final XmldbURI moduleLoadPathUri = XmldbURI.xmldbUriFor( moduleLoadPath );
                                locationUri = moduleLoadPathUri.resolveCollectionPath( locationUri );
                            }

                            DocumentImpl sourceDoc = null;

                            try {
                                sourceDoc = getBroker().getXMLResource( locationUri.toCollectionPathURI(), Lock.READ_LOCK );

                                if(sourceDoc == null) {
                                    throw moduleLoadException("Module location hint URI '" + location + " does not refer to anything.", location);
                                }

                                if(( sourceDoc.getResourceType() != DocumentImpl.BINARY_FILE ) || !"application/xquery".equals(sourceDoc.getMetadata().getMimeType())) {
                                    throw moduleLoadException("Module location hint URI '" + location + " does not refer to an XQuery.", location);
                                }

                                moduleSource = new DBSource( getBroker(), (BinaryDocument)sourceDoc, true );

                                // we don't know if the module will get returned, oh well
                                module = compileOrBorrowModule( prefix, namespaceURI, location, moduleSource );

                            } catch(final PermissionDeniedException e) {
                                throw moduleLoadException("Permission denied to read module source from location hint URI '" + location + ".", location, e);
                            } finally {
                                if(sourceDoc != null) {
                                    sourceDoc.getUpdateLock().release(Lock.READ_LOCK);
                                }
                            }
                        } catch(final URISyntaxException e) {
                            throw moduleLoadException("Invalid module location hint URI '" + location + ".", location, e);
                        }

                    } else {

                        // No. Load from file or URL
                        try {

                            //TODO: use URIs to ensure proper resolution of relative locations
                            moduleSource = SourceFactory.getSource( getBroker(), moduleLoadPath, location, true );

                        } catch(final MalformedURLException e) {
                            throw moduleLoadException("Invalid module location hint URI '" + location + ".", location, e);
                        } catch(final IOException e) {
                            throw moduleLoadException("Source for module '" + namespaceURI + "' not found module location hint URI '" + location + ".", location, e);
                        } catch(final PermissionDeniedException e) {
                            throw moduleLoadException("Permission denied to read module source from location hint URI '" + location + ".", location, e);
                        }

                        // we don't know if the module will get returned, oh well
                        module = compileOrBorrowModule(prefix, namespaceURI, location, moduleSource);
                    }
                }
            } // NOTE: expathrepo related, closes the EXPath else (if module != null)
        }
        if (namespaceURI == null) {
            namespaceURI = module.getNamespaceURI();
        }
        if( prefix == null ) {
            prefix = module.getDefaultPrefix();
        }
        declareNamespace( prefix, namespaceURI );
       
        return module;
    }

   
    protected XPathException moduleLoadException(final String message, final String moduleLocation) throws XPathException {
        return new XPathException(ErrorCodes.XQST0059, message, new ValueSequence(new StringValue(moduleLocation)));
    }
   
    protected XPathException moduleLoadException(final String message, final String moduleLocation, final Exception e) throws XPathException {
        return new XPathException(ErrorCodes.XQST0059, message, new ValueSequence(new StringValue(moduleLocation)), e);
    }

    /**
     * Returns the static location mapped to an XQuery source module, if known.
     *
     * @param   namespaceURI  the URI of the module
     *
     * @return  the location string
     */
    @SuppressWarnings( "unchecked" )
    public String getModuleLocation( String namespaceURI )
    {
        final Map<String, String> moduleMap = (Map)getBroker().getConfiguration().getProperty( PROPERTY_STATIC_MODULE_MAP );
        return( moduleMap.get( namespaceURI ) );
    }


    /**
     * Returns an iterator over all module namespace URIs which are statically mapped to a known location.
     *
     * @return  an iterator
     */
    @SuppressWarnings( "unchecked" )
    public Iterator<String> getMappedModuleURIs()
    {
        final Map<String, String> moduleMap = (Map)getBroker().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 = getBroker().getBrokerPool().getXQueryPool().borrowModule( getBroker(), source, this );

        if( module == null ) {
            module = compileModule( prefix, namespaceURI, location, source );
        } else {

            for( final Iterator<Module> it = module.getContext().getAllModules(); it.hasNext(); ) {
                final Module importedModule = it.next();

                if( ( importedModule != null ) && !allModules.containsKey( importedModule.getNamespaceURI() ) ) {
                    setRootModule( importedModule.getNamespaceURI(), importedModule );
                }
            }
        }
        setModule( module.getNamespaceURI(), module );
        declareModuleVars( module );
        return( module );
    }


    /**
     * Compile Module
     *
     * @param   prefix       
     * @param   namespaceURI 
     * @param   location     
     * @param   source       
     *
     * @return  The compiled module.
     *
     * @throws  XPathException
     */
    public 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 + "', location: '" + location + "'. Source not found. " ) );
            }

            if (namespaceURI == null) {
                final QName qname = source.isModule();
                if (qname == null)
                    {return null;}
                namespaceURI = qname.getNamespaceURI();
            }
        }
        catch( final IOException e ) {
            throw( new XPathException( "IO exception while loading module '" + namespaceURI + "' from '" + source + "'", e ) );
        }
        final ExternalModuleImpl modExternal = new ExternalModuleImpl(namespaceURI, prefix);
        setModule(namespaceURI, modExternal);
        final XQueryContext    modContext = new ModuleContext( this, prefix, namespaceURI, location );
        modExternal.setContext( modContext );
        final XQueryLexer      lexer      = new XQueryLexer( modContext, reader );
        final XQueryParser     parser     = new XQueryParser( lexer );
        final 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() ) );
            }
            final AST      ast  = parser.getAST();

            final 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() ) );
            }
           
            modExternal.setRootExpression(path);

            if(namespaceURI != null && !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.setXacmlSource( 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 );
            modContext.setSource(source);
            modExternal.setIsReady(true);
            return( modExternal );
        }
        catch( final RecognitionException e ) {
            throw( new XPathException( e.getLine(), e.getColumn(), "error found while loading module from " + location + ": " + e.getMessage() ) );
        }
        catch( final TokenStreamException e ) {
            throw( new XPathException( "error found while loading module from " + location + ": " + e.getMessage(), e ) );
        }
        catch( final XPathException e ) {
            e.prependMessage( "Error while loading module " + location + ": " );
            throw( e );
        }
        catch( final Exception e ) {
          e.printStackTrace();
            throw( new XPathException( "Internal error while loading module: " + location, e ) );
        }
        finally {

            try {

                if( reader != null ) {
                    reader.close();
                }
            }
            catch( final IOException e ) {
                LOG.warn( "Error while closing module source: " + e.getMessage(), e );
            }
        }
    }


    private void declareModuleVars( Module module )
    {
        final String moduleNS = module.getNamespaceURI();

        for( final Iterator<Variable> i = globalVariables.values().iterator(); i.hasNext(); ) {
            final 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() ) {
            final FunctionCall        call = forwardReferences.pop();
            final UserDefinedFunction func = call.getContext().resolveFunction( call.getQName(), call.getArgumentCount() );

            if( func == null ) {
                throw( new XPathException( call, ErrorCodes.XPST0017, "Call to undeclared function: " + call.getQName().getStringValue() ) );
            }
            call.resolveForwardReference( func );
        }
    }
   
    /**
     * Get environment variables. The variables shall not change
     * during execution of query.
     *
     * @return Map of environment variables
     */
    public Map<String, String> getEnvironmentVariables(){
        if(envs==null){
            envs = System.getenv();
        }
        return envs;
    }

    /**
     * Gets the Effective user
     * i.e. the user that the query is executing as
     *
     * @return The Effective User
     */
    public Subject getEffectiveUser() {
        return getBroker().getSubject();
    }
   
    /**
     * Gets the Real User
     * i.e. the user that initiated execution of the query
     * Note this is not necessarily the same as the user that the
     * query is executing as
     * @see org.exist.xquery.XQueryContext#getEffectiveUser()
     *
     * @return The Real User
     */
    public Subject getRealUser() {
        return realUser;
    }
   
    public void setRealUser(final Subject realUser) {
        this.realUser = realUser;
    }

    /* ----------------- Save state ------------------------ */
   
    private class SavedState {
     
      private HashMap<String, Module> modulesSaved = null;
      private HashMap<String, Module> allModulesSaved = null;
      private HashMap<String, String> staticNamespacesSaved = null;
      private HashMap<String, String> staticPrefixesSaved = null;
     
      @SuppressWarnings("unchecked")
    void save() {
        if (modulesSaved == null) {
          modulesSaved = (HashMap<String, Module>) modules.clone();
            allModulesSaved = (HashMap<String, Module>) allModules.clone();
            staticNamespacesSaved = (HashMap<String, String>) staticNamespaces.clone();
            staticPrefixesSaved = (HashMap<String, String>) staticPrefixes.clone();
        }
      }
     
      void restore() {
        if (modulesSaved != null) {
          modules = modulesSaved;
          modulesSaved = null;
          allModules = allModulesSaved;
          allModulesSaved = null;
          staticNamespaces = staticNamespacesSaved;
          staticNamespacesSaved = null;
          staticPrefixes = staticPrefixesSaved;
          staticPrefixesSaved = null;
        }
      }
    }
   
    /**
     * Before a dynamic import, make sure relevant parts of the current context a saved
     * to the stack. This is important for util:import-module. The context will be restored
     * during {@link #reset()}.
     */
    public void saveState() {
      savedState.save();
    }
   
    public boolean optimizationsEnabled()
    {
        return( enableOptimizer );
    }


    /**
     * for static compile-time options i.e. declare option
     *
     * @param   qnameString 
     * @param   contents    
     *
     * @throws  XPathException 
     */
    public void addOption( String qnameString, String contents ) throws XPathException
    {
        if( staticOptions == null ) {
            staticOptions = new ArrayList<Option>();
        }

        addOption( staticOptions, qnameString, contents );
    }


    /**
     * for dynamic run-time options i.e. util:declare-option
     *
     * @param   qnameString 
     * @param   contents    
     *
     * @throws  XPathException 
     */
    public void addDynamicOption( String qnameString, String contents ) throws XPathException
    {
        if( dynamicOptions == null ) {
            dynamicOptions = new ArrayList<Option>();
        }

        addOption( dynamicOptions, qnameString, contents );
    }


    private void addOption( List<Option> options, String qnameString, String contents ) throws XPathException
    {
        final QName  qn     = QName.parse( this, qnameString, defaultFunctionNamespace );

        final 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 ) {
            final String[] params = option.tokenizeContents();

            if( params.length > 0 ) {
                final String[] param = Option.parseKeyValuePair( params[0] );

                if( param != null && "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
            final 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
            final 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( final Option option : dynamicOptions ) {

                if( qname.compareTo( option.getQName() ) == 0 ) {
                    return( option );
                }
            }
        }

        if( staticOptions != null ) {

            for( final Option option : staticOptions ) {

                if( qname.compareTo( option.getQName() ) == 0 ) {
                    return( option );
                }
            }
        }

        return( null );
    }


    public Pragma getPragma( String name, String contents ) throws XPathException
    {
        final 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( 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
     *
     * @return  TemporaryDoc fragment
     *
     * @throws  XPathException
     */
    public DocumentImpl storeTemporaryDoc( org.exist.memtree.DocumentImpl doc ) throws XPathException
    {
        try {
            final DocumentImpl targetDoc = getBroker().storeTempResource( doc );

            if( targetDoc == null ) {
                throw( new XPathException( "Internal error: failed to store temporary doc fragment" ) );
            }
            LOG.warn( "Stored: " + targetDoc.getDocId() + ": " + targetDoc.getURI(), new Throwable() );
            return( targetDoc );
        }
        catch( final EXistException e ) {
            throw( new XPathException( TEMP_STORE_ERROR, e ) );
        }
        catch( final PermissionDeniedException e ) {
            throw( new XPathException( TEMP_STORE_ERROR, e ) );
        }
        catch( final 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 ) );
    }


    /**
     * Load the default prefix/namespace mappings table and set up internal functions.
     *
     * @param  config 
     */
    @SuppressWarnings( "unchecked" )
    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();
       
        // Switch: enable optimizer
        Object param = config.getProperty( PROPERTY_ENABLE_QUERY_REWRITING );
        enableOptimizer     = ( param != null ) && "yes".equals(param.toString());

        // Switch: Backward compatibility
        param = config.getProperty( PROPERTY_XQUERY_BACKWARD_COMPATIBLE );
        backwardsCompatible = ( param == null ) || "yes".equals(param.toString());

        // Switch: raiseErrorOnFailedRetrieval
        final Boolean option = ( (Boolean)config.getProperty( PROPERTY_XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL ) );
        raiseErrorOnFailedRetrieval = ( option != null ) && option.booleanValue();

        // Get map of built-in modules
        final Map<String, Class<Module>> builtInModules = (Map)config.getProperty( PROPERTY_BUILT_IN_MODULES );

        if( builtInModules != null ) {

            // Iterate on all map entries
            for( final Map.Entry<String, Class<Module>> entry : builtInModules.entrySet() ) {

                // Get URI and class
                final String        namespaceURI = entry.getKey();
                final Class<Module> moduleClass  = entry.getValue();
               
                // first check if the module has already been loaded in the parent context
                final Module        module       = getModule( namespaceURI );

                if( module == null ) {
                    // Module does not exist yet, instantiate
                    instantiateModule( namespaceURI, moduleClass, (Map<String, Map<String, List<? extends Object>>>)config.getProperty(PROPERTY_MODULE_PARAMETERS));

                } 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( final XPathException e ) {
                        LOG.warn( "Internal error while loading default modules: " + e.getMessage(), e );
                    }
                }
            }
        }
    }


    /**
     * Load default namespaces, e.g. xml, xsi, xdt, fn, local, exist and dbgp.
     */
    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 ?
            declareNamespace( "dbgp", Debuggee.NAMESPACE_URI );

        } catch( final XPathException e ) {
            //ignored because it should never happen
            LOG.debug(e);
        }
    }


    public void registerUpdateListener( UpdateListener listener )
    {
        if( updateListener == null ) {
            updateListener = new ContextUpdateListener();
            final DBBroker broker = getBroker();
            broker.getBrokerPool().getNotificationService().subscribe( updateListener );
        }
        updateListener.addListener( listener );
    }


    protected void clearUpdateListeners()
    {
        if( updateListener != null ) {
            final DBBroker broker = getBroker();
            broker.getBrokerPool().getNotificationService().unsubscribe( updateListener );
        }
        updateListener = null;
    }

    /**
     * Check if the XQuery contains options 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 {
        checkLegacyOptions(properties);
        if(dynamicOptions != null) {
            for(final Option option : dynamicOptions) {
                if (Namespaces.XSLT_XQUERY_SERIALIZATION_NS.equals(option.getQName().getNamespaceURI())) {
                    properties.put(option.getQName().getLocalName(), option.getContents());
                }
            }
        }

        if( staticOptions != null ) {
            for(final Option option : staticOptions) {
                if (Namespaces.XSLT_XQUERY_SERIALIZATION_NS.equals(option.getQName().getNamespaceURI())) {
                    if (!properties.containsKey(option.getQName().getLocalName()))
                        {properties.put(option.getQName().getLocalName(), option.getContents());}
                }
            }
        }
    }

    /**
     * Legacy method to check serialization properties set via option exist:serialize.
     *
     * @param properties
     * @throws XPathException
     */
    private void checkLegacyOptions( Properties properties ) throws XPathException
    {
        final Option pragma = getOption( Option.SERIALIZE_QNAME );

        if( pragma == null ) {
            return;
        }
        final String[] contents = pragma.tokenizeContents();

        for( int i = 0; i < contents.length; i++ ) {
            final 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] );
        }
    }


    public void setDebuggeeJoint( DebuggeeJoint joint )
    {
        //XXX: if (debuggeeJoint != null) ???
        debuggeeJoint = joint;
    }


    public DebuggeeJoint getDebuggeeJoint()
    {
        return( debuggeeJoint );
    }


    public boolean isDebugMode()
    {
        return( ( debuggeeJoint != null ) && isVarDeclared( Debuggee.SESSION ) );
    }

    public boolean requireDebugMode()
    {
        return isVarDeclared( Debuggee.SESSION );
    }

    private List<BinaryValue> binaryValueInstances;
   
    @Override
    public void registerBinaryValueInstance(final BinaryValue binaryValue) {
        if(binaryValueInstances == null) {
             binaryValueInstances = new ArrayList<BinaryValue>();
            
             cleanupTasks.add(new CleanupTask() {
                
                 @Override
                 public void cleanup(final XQueryContext context) {
                    if(context.binaryValueInstances != null) {
                       for(final BinaryValue bv : context.binaryValueInstances) {
                           try {
                               bv.close();
                           } catch (final IOException ioe) {
                               LOG.error("Unable to close binary value: " + ioe.getMessage(), ioe);
                           }
                       }
                       context.binaryValueInstances.clear();
                   }
                 }
             });
        }
       
        binaryValueInstances.add(binaryValue);
    }

    @Override
    public String getCacheClass() {
        return (String) getBroker().getConfiguration().getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY);
    }

    public void destroyBinaryValue(BinaryValue value) {
        if (binaryValueInstances != null) {
            for (int i = binaryValueInstances.size() - 1; i > -1; i--) {
                final BinaryValue bv = binaryValueInstances.get(i);
                if (bv == value) {
                    binaryValueInstances.remove(i);
                    return;
                }
            }
        }
    }
   
    public void setXQueryVersion(int version) {
        xqueryVersion=version;
    }

    public int getXQueryVersion(){
        return xqueryVersion;
    }

    @Override
    public Source getSource() {
        return source;
    }

    @Override
    public void setSource(final Source source) {
        this.source = source;
    }


    // ====================================================================================

   
    private class ContextUpdateListener implements UpdateListener {

        private List<UpdateListener> listeners = new ArrayList<UpdateListener>();

        public void addListener( UpdateListener listener )
        {
      synchronized( listeners ) { // TODO field must be final?
               listeners.add( listener );
      }
        }


        public void documentUpdated( DocumentImpl document, int event )
        {
      synchronized( listeners ) { // TODO field must be final?
              for( final UpdateListener listener : listeners ) {
 
                  if( listener != null ) {
                      listener.documentUpdated( document, event );
                  }
              }
      }
        }


        public void unsubscribe()
        {
      synchronized( listeners ) { // TODO field must be final?
              for( final UpdateListener listener : listeners ) {
 
                  if( listener != null ) {
                      listener.unsubscribe();
                  }
              }
              listeners.clear();
      }
        }


        public void nodeMoved( NodeId oldNodeId, StoredNode newNode )
        {
            for( int i = 0; i < listeners.size(); i++ ) {
                final UpdateListener listener = (UpdateListener)listeners.get( i );

                if( listener != null ) {
                    listener.nodeMoved( oldNodeId, newNode );
                }
            }
        }


        public void debug() {
           
            LOG.debug(String.format("XQueryContext: %s document update listeners", listeners.size()));
            for (int i = 0; i < listeners.size(); i++) {
                ((UpdateListener) listeners.get(i)).debug();
            }
        }

    }
   
    private List<CleanupTask> cleanupTasks = new ArrayList<CleanupTask>();
   
    public void registerCleanupTask(final CleanupTask cleanupTask) {
        cleanupTasks.add(cleanupTask);
    }
   
    public interface CleanupTask {
        public void cleanup(final XQueryContext context);
    }
   
    @Override
    public void runCleanupTasks() {
        for(final CleanupTask cleanupTask : cleanupTasks) {
            try {
                cleanupTask.cleanup(this);
            } catch(final Throwable t) {
                LOG.error("Cleaning up XQueryContext: Ignoring: " + t.getMessage(), t);
            }
        }
    }
}
TOP

Related Classes of org.exist.xquery.XQueryContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.