• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.25
/exist-core/src/main/java/org/exist/xquery/XQueryContext.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.xquery;
50

51
import java.io.IOException;
52
import java.io.Reader;
53
import java.lang.reflect.Constructor;
54
import java.lang.reflect.InvocationTargetException;
55
import java.net.MalformedURLException;
56
import java.net.URI;
57
import java.net.URISyntaxException;
58
import java.nio.charset.Charset;
59
import java.nio.file.Path;
60
import java.nio.file.Paths;
61
import java.util.*;
62
import java.util.concurrent.CopyOnWriteArrayList;
63
import java.util.concurrent.ThreadPoolExecutor;
64
import java.util.concurrent.atomic.AtomicReference;
65
import java.util.function.BiFunction;
66
import java.util.function.Consumer;
67
import java.util.function.Predicate;
68

69
import javax.annotation.Nullable;
70
import javax.xml.datatype.DatatypeConfigurationException;
71
import javax.xml.datatype.DatatypeFactory;
72
import javax.xml.datatype.Duration;
73
import javax.xml.datatype.XMLGregorianCalendar;
74
import javax.xml.stream.XMLStreamException;
75

76
import antlr.RecognitionException;
77
import antlr.TokenStreamException;
78
import antlr.collections.AST;
79
import com.evolvedbinary.j8fu.Either;
80
import com.evolvedbinary.j8fu.function.TriFunctionE;
81
import com.evolvedbinary.j8fu.function.QuadFunctionE;
82
import com.evolvedbinary.j8fu.tuple.Tuple2;
83
import com.ibm.icu.text.Collator;
84
import it.unimi.dsi.fastutil.Hash;
85
import it.unimi.dsi.fastutil.objects.*;
86
import net.jcip.annotations.Immutable;
87
import net.jcip.annotations.ThreadSafe;
88
import org.apache.logging.log4j.LogManager;
89
import org.apache.logging.log4j.Logger;
90
import org.exist.Database;
91
import org.exist.EXistException;
92
import org.exist.Namespaces;
93
import org.exist.collections.Collection;
94
import org.exist.debuggee.Debuggee;
95
import org.exist.debuggee.DebuggeeJoint;
96
import org.exist.dom.persistent.*;
97
import org.exist.dom.QName;
98
import org.exist.http.servlets.*;
99
import org.exist.dom.memtree.InMemoryXMLStreamReader;
100
import org.exist.dom.memtree.MemTreeBuilder;
101
import org.exist.dom.memtree.NodeImpl;
102
import org.exist.numbering.NodeId;
103
import org.exist.repo.ExistRepository;
104
import org.exist.security.AuthenticationException;
105
import org.exist.security.Permission;
106
import org.exist.security.PermissionDeniedException;
107
import org.exist.security.Subject;
108
import org.exist.source.*;
109
import org.exist.stax.ExtendedXMLStreamReader;
110
import org.exist.storage.BrokerPool;
111
import org.exist.storage.DBBroker;
112
import org.exist.storage.UpdateListener;
113
import org.exist.storage.lock.Lock.LockMode;
114
import org.exist.storage.lock.LockedDocumentMap;
115
import org.exist.storage.txn.Txn;
116
import org.exist.util.*;
117
import org.exist.util.hashtable.NamePool;
118
import org.exist.xmldb.XmldbURI;
119
import org.exist.xquery.parser.*;
120
import org.exist.xquery.pragmas.*;
121
import org.exist.xquery.update.Modification;
122
import org.exist.xquery.util.SerializerUtils;
123
import org.exist.xquery.value.*;
124
import org.jgrapht.Graph;
125
import org.jgrapht.alg.interfaces.ShortestPathAlgorithm;
126
import org.jgrapht.alg.shortestpath.TransitNodeRoutingShortestPath;
127
import org.jgrapht.graph.DefaultEdge;
128
import org.jgrapht.graph.DefaultGraphType;
129
import org.jgrapht.opt.graph.fastutil.FastutilMapGraph;
130
import org.jgrapht.util.ConcurrencyUtil;
131
import org.jgrapht.util.SupplierUtil;
132
import org.w3c.dom.Node;
133

134
import static com.evolvedbinary.j8fu.OptionalUtil.or;
135
import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
136
import static javax.xml.XMLConstants.XMLNS_ATTRIBUTE;
137
import static javax.xml.XMLConstants.XML_NS_PREFIX;
138
import static org.exist.Namespaces.XML_NS;
139
import static org.exist.util.MapUtil.hashMap;
140

141
/**
142
 * The current XQuery execution context. Contains the static as well as the dynamic
143
 * XQuery context components.
144
 *
145
 * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang Meier</a>
146
 */
147
public class XQueryContext implements BinaryValueManager, Context {
148

149
    private static final Logger LOG = LogManager.getLogger(XQueryContext.class);
1✔
150

151
    public static final String ENABLE_QUERY_REWRITING_ATTRIBUTE = "enable-query-rewriting";
152
    public static final String XQUERY_BACKWARD_COMPATIBLE_ATTRIBUTE = "backwardCompatible";
153
    public static final String XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_ATTRIBUTE = "raise-error-on-failed-retrieval";
154
    public static final String ENFORCE_INDEX_USE_ATTRIBUTE = "enforce-index-use";
155

156
    //TODO : move elsewhere ?
157
    public static final String BUILT_IN_MODULE_URI_ATTRIBUTE = "uri";
158
    public static final String BUILT_IN_MODULE_CLASS_ATTRIBUTE = "class";
159
    public static final String BUILT_IN_MODULE_SOURCE_ATTRIBUTE = "src";
160

161
    public static final String PROPERTY_XQUERY_BACKWARD_COMPATIBLE = "xquery.backwardCompatible";
162
    public static final String PROPERTY_ENABLE_QUERY_REWRITING = "xquery.enable-query-rewriting";
163
    public static final String PROPERTY_XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL = "xquery.raise-error-on-failed-retrieval";
164
    public static final boolean XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_DEFAULT = false;
165
    public static final String PROPERTY_ENFORCE_INDEX_USE = "xquery.enforce-index-use";
166

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

172
    public static final String JAVA_URI_START = "java:";
173
    //private static final String XMLDB_URI_START = "xmldb:exist://";
174

175
    private static final String TEMP_STORE_ERROR = "Error occurred while storing temporary data";
176
    public static final String XQUERY_CONTEXTVAR_XQUERY_UPDATE_ERROR = "_eXist_xquery_update_error";
177
    public static final String HTTP_SESSIONVAR_XMLDB_USER = "_eXist_xmldb_user";
178

179
    public static final String HTTP_REQ_ATTR_USER = "xquery.user";
180
    public static final String HTTP_REQ_ATTR_PASS = "xquery.password";
181

182
    // Static namespace/prefix mappings
183
    protected Map<String, String> staticNamespaces = new HashMap<>();
1✔
184

185
    // Static prefix/namespace mappings
186
    protected Map<String, String> staticPrefixes = new HashMap<>();
1✔
187

188
    // Local in-scope namespace/prefix mappings in the current context
189
    Map<String, String> inScopeNamespaces = new HashMap<>();
1✔
190

191
    // Local prefix/namespace mappings in the current context
192
    private Map<String, String> inScopePrefixes = new HashMap<>();
1✔
193

194
    // Inherited in-scope namespace/prefix mappings in the current context
195
    private Map<String, String> inheritedInScopeNamespaces = new HashMap<>();
1✔
196

197
    // Inherited prefix/namespace mappings in the current context
198
    private Map<String, String> inheritedInScopePrefixes = new HashMap<>();
1✔
199

200
    private boolean preserveNamespaces = true;
1✔
201

202
    private boolean inheritNamespaces = true;
1✔
203

204
    // Local namespace stack
205
    private final Deque<Map<String, String>> namespaceStack = new ArrayDeque<>();
1✔
206

207
    // Known user defined functions in the local module
208
    private Map<FunctionId, UserDefinedFunction> declaredFunctions = new Object2ObjectAVLTreeMap<>();
1✔
209

210
    // Globally declared variables
211
    protected Map<QName, Variable> globalVariables = new Object2ObjectRBTreeMap<>();
1✔
212

213
    // The last element in the linked list of local in-scope variables
214
    private LocalVariable lastVar = null;
1✔
215

216
    private Deque<LocalVariable> contextStack = new ArrayDeque<>();
1✔
217

218
    private final Deque<FunctionSignature> callStack = new ArrayDeque<>();
1✔
219

220
    // The current size of the variable stack
221
    private int variableStackSize = 0;
1✔
222

223
    // Unresolved references to user defined functions
224
    private final Deque<FunctionCall> forwardReferences = new ArrayDeque<>();
1✔
225

226
    // Inline functions using closures need to be cleared after execution
227
    private final Deque<UserDefinedFunction> closures = new ArrayDeque<>();
1✔
228

229
    // List of options declared for this query at compile time - i.e. declare option
230
    private List<Option> staticOptions = null;
1✔
231

232
    // List of options declared for this query at run time - i.e. util:declare-option()
233
    private List<Option> dynamicOptions = null;
1✔
234

235
    //The Calendar for this context : may be changed by some options
236
    private XMLGregorianCalendar calendar = null;
1✔
237
    private TimeZone implicitTimeZone = null;
1✔
238

239
    private final Map<String, Sequence> cachedUriCollectionResults = new HashMap<>();
1✔
240

241
    /**
242
     * the watchdog object assigned to this query.
243
     */
244
    protected XQueryWatchDog watchdog;
245

246
    /**
247
     * Loaded modules within this module.
248
     * <p>
249
     * The format of the map is: <code>Map&lt;NamespaceURI, Modules&gt;</code>.
250
     */
251
    protected Object2ObjectMap<String, Module[]> modules = new Object2ObjectOpenHashMap<>(8, Hash.VERY_FAST_LOAD_FACTOR);
1✔
252

253
    /**
254
     * Loaded modules, including ones bubbled up from imported modules.
255
     * <p>
256
     * The format of the map is: <code>Map&lt;NamespaceURI, Modules&gt;</code>
257
     */
258
    private Object2ObjectMap<String, Module[]> allModules = new Object2ObjectOpenHashMap<>();
1✔
259

260
    /**
261
     * Describes a graph of all the modules and how they import each other.
262
     */
263
    private @Nullable Graph<ModuleVertex, DefaultEdge> modulesDependencyGraph;
264
    private @Nullable ThreadPoolExecutor modulesDependencyGraphSPExecutor;
265

266
    /**
267
     * Used to save current state when modules are imported dynamically
268
     */
269
    private final SavedState savedState = new SavedState();
1✔
270

271
    /**
272
     * Whether some modules were rebound to new instances since the last time this context's query was analyzed. (This assumes that each context is
273
     * attached to at most one query.)
274
     */
275
    @SuppressWarnings("unused")
276
    private boolean modulesChanged = true;
1✔
277

278
    /**
279
     * The set of statically known documents specified as an array of paths to documents and collections.
280
     */
281
    private XmldbURI[] staticDocumentPaths = null;
1✔
282

283
    /**
284
     * The actual set of statically known documents. This will be generated on demand from staticDocumentPaths.
285
     */
286
    private DocumentSet staticDocuments = null;
1✔
287

288
    /**
289
     * The available documents of the dynamic context.
290
     * <p>
291
     * {@see https://www.w3.org/TR/xpath-31/#dt-available-docs}.
292
     */
293
    private Map<String, TriFunctionE<DBBroker, Txn, String, Either<org.exist.dom.memtree.DocumentImpl, DocumentImpl>, XPathException>> dynamicDocuments = null;
1✔
294

295
    /**
296
     * The available test resources of the dynamic context.
297
     * <p>
298
     * {@see https://www.w3.org/TR/xpath-31/#dt-available-text-resources}.
299
     */
300
    private Map<Tuple2<String, Charset>, QuadFunctionE<DBBroker, Txn, String, Charset, Reader, XPathException>> dynamicTextResources = null;
1✔
301

302
    /**
303
     * The available collections of the dynamic context.
304
     * <p>
305
     * {@see https://www.w3.org/TR/xpath-31/#dt-available-collections}.
306
     */
307
    private Map<String, TriFunctionE<DBBroker, Txn, String, Sequence, XPathException>> dynamicCollections = null;
1✔
308

309
    /**
310
     * A set of documents which were modified during the query, usually through an XQuery update extension. The documents will be checked after the
311
     * query completed to see if a defragmentation run is needed.
312
     */
313
    protected MutableDocumentSet modifiedDocuments = null;
1✔
314

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

320
    protected AnyURIValue baseURI = AnyURIValue.EMPTY_URI;
1✔
321

322
    private boolean baseURISetInProlog = false;
1✔
323

324
    protected String moduleLoadPath = ".";
1✔
325

326
    private String defaultFunctionNamespace = Function.BUILTIN_FUNCTION_NS;
1✔
327
    private AnyURIValue defaultElementNamespace = AnyURIValue.EMPTY_URI;
1✔
328
    private AnyURIValue defaultElementNamespaceSchema = AnyURIValue.EMPTY_URI;
1✔
329

330
    /**
331
     * The default collation URI.
332
     */
333
    private String defaultCollation = Collations.UNICODE_CODEPOINT_COLLATION_URI;
1✔
334

335
    /**
336
     * The default language
337
     */
338
    private static final String DefaultLanguage = Locale.getDefault().getLanguage();
1✔
339

340
    /**
341
     * Default Collator. Will be null for the default unicode codepoint collation.
342
     */
343
    private Collator defaultCollator = null;
1✔
344

345
    /**
346
     * Set to true to enable XPath 1.0 backwards compatibility.
347
     */
348
    private boolean backwardsCompatible = false;
1✔
349

350
    /**
351
     * Should whitespace inside node constructors be stripped?
352
     */
353
    private boolean stripWhitespace = true;
1✔
354

355
    /**
356
     * Should empty order greatest or least?
357
     */
358
    private boolean orderEmptyGreatest = false;
1✔
359

360
    /**
361
     * XQuery 3.0 - declare context item :=
362
     */
363
    private ContextItemDeclaration contextItemDeclaration = null;
1✔
364

365
    /**
366
     * The context item set in the query prolog or externally
367
     */
368
    private Sequence contextItem = Sequence.EMPTY_SEQUENCE;
1✔
369

370
    /**
371
     * The position of the currently processed item in the context sequence. This field has to be set on demand, for example, before calling the
372
     * fn:position() function.
373
     */
374
    private int contextPosition = 0;
1✔
375
    private Sequence contextSequence = null;
1✔
376

377
    /**
378
     * Shared name pool used by all in-memory documents constructed in this query context.
379
     */
380
    private NamePool sharedNamePool = null;
1✔
381

382
    /**
383
     * Stack for temporary document fragments.
384
     */
385
    private Deque<MemTreeBuilder> fragmentStack = new ArrayDeque<>();
1✔
386

387
    /**
388
     * The root of the expression tree.
389
     */
390
    private Expression rootExpression;
391

392
    /**
393
     * An incremental counter to count the expressions in the current XQuery. Used during compilation to assign a unique ID to every expression.
394
     */
395
    private int expressionCounter = 0;
1✔
396

397
    private LockedDocumentMap protectedDocuments = null;
1✔
398

399
    /**
400
     * The profiler instance used by this context.
401
     */
402
    protected Profiler profiler;
403

404
    //For holding the environment variables
405
    private Map<String, String> envs;
406

407
    private ContextUpdateListener updateListener = null;
1✔
408

409
    private boolean enableOptimizer = true;
1✔
410

411
    private boolean raiseErrorOnFailedRetrieval = XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL_DEFAULT;
1✔
412

413
    private boolean isShared = false;
1✔
414

415
    private Source source = null;
1✔
416

417
    private DebuggeeJoint debuggeeJoint = null;
1✔
418

419
    private int xqueryVersion = 31;
1✔
420

421
    protected Database db;
422

423
    protected Configuration configuration;
424

425
    private boolean analyzed = false;
1✔
426

427
    /**
428
     * The Subject of the User that requested the execution of the XQuery
429
     * attached by this Context. This is not the same as the Effective User
430
     * as we may be executed setUid or setGid. The Effective User can be retrieved
431
     * through broker.getCurrentSubject()
432
     */
433
    private Subject realUser;
434

435
    /**
436
     * Indicates whether a user from a http session
437
     * was pushed onto the current broker from {@link XQueryContext#prepareForExecution()},
438
     * if so then we must pop the user in {@link XQueryContext#reset(boolean)}
439
     */
440
    private boolean pushedUserFromHttpSession = false;
1✔
441

442
    /**
443
     * The HTTP context within which the XQuery
444
     * is executing, or null if there is no
445
     * HTTP context.
446
     */
447
    private @Nullable HttpContext httpContext = null;
1✔
448
    private static final QName UNNAMED_DECIMAL_FORMAT = new QName("__UNNAMED__", Function.BUILTIN_FUNCTION_NS);
1✔
449

450
    private final Map<QName, DecimalFormat> staticDecimalFormats = hashMap(Tuple(UNNAMED_DECIMAL_FORMAT, DecimalFormat.UNNAMED));
1✔
451

452
    // Only used for testing, e.g. {@link org.exist.test.runner.XQueryTestRunner}.
453
    private Optional<ExistRepository> testRepository = Optional.empty();
1✔
454

455
    /**
456
     * Holds a list of any new XQuery Contexts that
457
     * were created by the XQuery (owning this XQuery Context)
458
     * dynamically importing, compiling, and/or evaluating modules.
459
     * <p>
460
     * NOTE(AR) - This is needed to ensure that these "imported contexts" are
461
     * also correctly reset and cleaned up when this XQuery is finished.
462
     */
463
    private Set<XQueryContext> importedContexts = null;
1✔
464

465
    /**
466
     * Holds a list of the {@link #runCleanupTasks(Predicate)} functions
467
     * of the {@link #importedContexts}.
468
     * <p>
469
     * NOTE(AR) - This is needed to ensure that these the user call
470
     * {@link #reset()} or {@link #runCleanupTasks(Predicate)}
471
     * in any order.
472
     */
473
    private List<Consumer<Predicate<Object>>> importedContextsCleanupTasksFns = null;
1✔
474

475
    public XQueryContext() {
476
        this(null, null, null);
1✔
477
    }
1✔
478

479
    public XQueryContext(final Configuration configuration) {
480
        this(null, configuration, null);
1✔
481
    }
1✔
482

483
    public XQueryContext(final Database db) {
484
        this(db, null, null);
1✔
485
    }
1✔
486

487
    public XQueryContext(final Database db, final Profiler profiler) {
488
        this(db, null, profiler);
1✔
489
    }
1✔
490

491
    public XQueryContext(@Nullable final Database db, @Nullable final Configuration configuration, @Nullable final Profiler profiler) {
492
        this(db, configuration, profiler, true);
1✔
493
    }
1✔
494

495
    protected XQueryContext(@Nullable final Database db, @Nullable final Configuration configuration, @Nullable final Profiler profiler, final boolean loadDefaults) {
1✔
496
        this.db = db;
1✔
497

498
        // if needed, fallback to db.getConfiguration
499
        if (configuration != null) {
1✔
500
            this.configuration = configuration;
1✔
501
        } else if (db != null) {
1✔
502
            this.configuration = db.getConfiguration();
1✔
503
        } else {
1✔
504
            this.configuration = null;
1✔
505
        }
506

507
        // if needed, fallback to profiler for the db, or default profiler
508
        if (profiler != null) {
1✔
509
            this.profiler = profiler;
1✔
510
        } else if (db != null) {
1✔
511
            this.profiler = new Profiler(db);
1✔
512
        } else {
1✔
513
            this.profiler = new Profiler(null);
1✔
514
        }
515

516
        this.watchdog = new XQueryWatchDog(this);
1✔
517

518
        // load configuration defaults
519
        if (loadDefaults) {
1✔
520
            loadDefaults(this.configuration);
1✔
521
        }
522
    }
1✔
523

524
    public XQueryContext(final XQueryContext copyFrom) {
525
        this(copyFrom.db, copyFrom.configuration, copyFrom.profiler);
1✔
526

527
        for (final String prefix : copyFrom.staticNamespaces.keySet()) {
1✔
528
            if (XML_NS_PREFIX.equals(prefix) || XMLNS_ATTRIBUTE.equals(prefix)) {
1!
529
                continue;
×
530
            }
531

532
            try {
533
                declareNamespace(prefix, copyFrom.staticNamespaces.get(prefix));
1✔
534
            } catch (final XPathException ex) {
1✔
535
                ex.printStackTrace();
×
536
            }
537
        }
538
    }
1✔
539

540

541
    /**
542
     * Get the HTTP context of the XQuery.
543
     *
544
     * @return the HTTP context, or null if the query
545
     * is not being executed within an HTTP context.
546
     */
547
    public @Nullable HttpContext getHttpContext() {
548
        return httpContext;
1✔
549
    }
550

551
    /**
552
     * Set the HTTP context of the XQuery.
553
     *
554
     * @param httpContext the HTTP context within which the XQuery
555
     *                    is being executed.
556
     */
557
    public void setHttpContext(@Nullable final HttpContext httpContext) {
558
        this.httpContext = httpContext;
1✔
559
    }
1✔
560

561
    /**
562
     * Set the EXPath repository used for testing,
563
     * only should be called from {@link org.exist.test.runner.XQueryTestRunner}.
564
     *
565
     * @param testRepository the EXPath repository to use for test execution.
566
     */
567
    public void setTestRepository(final Optional<ExistRepository> testRepository) {
568
        this.testRepository = testRepository;
1✔
569
    }
1✔
570

571
    /**
572
     * Get the EXPath repository configured for the BrokerPool, if present.
573
     *
574
     * @return the EXPath repository if present.
575
     */
576
    public Optional<ExistRepository> getRepository() {
577
        return or(
1✔
578
                testRepository,
1✔
579
                () -> Optional.ofNullable(getBroker()).map(DBBroker::getBrokerPool).flatMap(BrokerPool::getExpathRepo)
1✔
580
        );
581
    }
582

583
    /**
584
     * Resolve a Module from the EXPath Repository.
585
     *
586
     * @param namespace namespace URI
587
     * @param prefix namespace prefix
588
     *
589
     * @return the module or null
590
     *
591
     * @throws XPathException if the namespace URI is invalid (XQST0046),
592
     *     if the module could not be loaded (XQST0059) or compiled (XPST0003)
593
     */
594
    private @Nullable Module resolveInEXPathRepository(final String namespace, final String prefix)
595
            throws XPathException {
596
        // the repo and its eXist handler
597
        final Optional<ExistRepository> repo = getRepository();
1✔
598

599
        if (repo.isEmpty()) {
1!
600
            return null;
×
601
        }
602

603
        // try an internal module
604
        final Module jMod = repo.get().resolveJavaModule(namespace, this);
1✔
605
        if (jMod != null) {
1!
606
            return jMod;
×
607
        }
608

609
        // try an eXist-specific module
610
        final Path resolved = repo.get().resolveXQueryModule(namespace);
1✔
611
        if (resolved == null) {
1✔
612
            return null;
1✔
613
        }
614

615
        // use the resolved file or return null
616
        if (resolved != null) {
1!
617

618
            String location = "";
1✔
619

620
            try {
621

622
                    // see if the src exists in the database and if so, use that instead
623
                    Source src = repo.get().resolveStoredXQueryModuleFromDb(getBroker(), resolved);
1✔
624
                    if (src != null) {
1!
625
                        // NOTE(AR) set the location of the module to import relative to this module's load path - so that transient imports of the imported module will resolve correctly!
626
                        final Path srcCollectionPath = Paths.get(((DBSource)src).getDocumentPath().getCollectionPath());
1✔
627
                        if (srcCollectionPath.isAbsolute()) {
1!
628
                            location = srcCollectionPath.toString();
1✔
629
                        } else {
1✔
630
                            final Path moduleLoadPathCollectionPath = Paths.get(XmldbURI.create(moduleLoadPath).getCollectionPath());
×
631
                            location = moduleLoadPathCollectionPath.relativize(srcCollectionPath).toString();
×
632
                        }
633
                    } else {
×
634
                        // else, fallback to the one from the filesystem
635
                        src = new FileSource(resolved, false);
×
636
                    }
637

638
                // build a module object from the source
639
                final ExternalModule module = compileOrBorrowModule(namespace, prefix, location, src);
1✔
640
                return module;
1✔
641

642
            } catch (final PermissionDeniedException e) {
×
643
                throw new XPathException(e.getMessage(), e);
×
644
            }
645
        }
646

647
        return null;
×
648
    }
649

650
    /**
651
     * Prepares the XQuery Context for use.
652
     * <p>
653
     * Should be called before compilation to prepare the query context,
654
     * or before re-execution if the query was cached.
655
     *
656
     * @throws XPathException in case of static error
657
     */
658
    public void prepareForReuse() throws XPathException {
659
        // prepare the variables of the internal modules (which were previously reset)
660
        for (final Module[] modules : allModules.values()) {
1✔
661
            for (final Module module : modules) {
1✔
662
                if (module instanceof InternalModule) {
1✔
663
                    ((InternalModule) module).prepare(this);
1✔
664
                }
665
            }
666
        }
667
    }
1✔
668

669
    @Override
670
    public boolean hasParent() {
671
        return false;
×
672
    }
673

674
    @Override
675
    public XQueryContext getRootContext() {
676
        return this;
1✔
677
    }
678

679
    @Override
680
    public XQueryContext copyContext() {
681
        final XQueryContext ctx = new XQueryContext(this);
1✔
682
        copyFields(ctx);
1✔
683
        return ctx;
1✔
684
    }
685

686
    @Override
687
    public void updateContext(final XQueryContext from) {
688
        this.watchdog = from.watchdog;
1✔
689
        this.lastVar = from.lastVar;
1✔
690
        this.contextStack = from.contextStack;
1✔
691
        this.inScopeNamespaces = from.inScopeNamespaces;
1✔
692
        this.inScopePrefixes = from.inScopePrefixes;
1✔
693
        this.inheritedInScopeNamespaces = from.inheritedInScopeNamespaces;
1✔
694
        this.inheritedInScopePrefixes = from.inheritedInScopePrefixes;
1✔
695
        this.variableStackSize = from.variableStackSize;
1✔
696
        this.attributes = from.attributes;
1✔
697
        this.updateListener = from.updateListener;
1✔
698
        this.modules = from.modules;
1✔
699
        this.allModules = from.allModules;
1✔
700
        this.dynamicOptions = from.dynamicOptions;
1✔
701
        this.staticOptions = from.staticOptions;
1✔
702
        this.db = from.db;
1✔
703
        this.configuration = from.configuration;
1✔
704
        this.httpContext = from.httpContext;
1✔
705
    }
1✔
706

707
    protected void copyFields(final XQueryContext ctx) {
708
        ctx.calendar = this.calendar;
1✔
709
        ctx.implicitTimeZone = this.implicitTimeZone;
1✔
710
        ctx.baseURI = this.baseURI;
1✔
711
        ctx.baseURISetInProlog = this.baseURISetInProlog;
1✔
712
        ctx.staticDocumentPaths = this.staticDocumentPaths;
1✔
713
        ctx.staticDocuments = this.staticDocuments;
1✔
714
        ctx.dynamicDocuments = this.dynamicDocuments;
1✔
715
        ctx.dynamicTextResources = this.dynamicTextResources;
1✔
716
        ctx.dynamicCollections = this.dynamicCollections;
1✔
717
        ctx.moduleLoadPath = this.moduleLoadPath;
1✔
718
        ctx.defaultFunctionNamespace = this.defaultFunctionNamespace;
1✔
719
        ctx.defaultElementNamespace = this.defaultElementNamespace;
1✔
720
        ctx.defaultCollation = this.defaultCollation;
1✔
721
        ctx.defaultCollator = this.defaultCollator;
1✔
722
        ctx.backwardsCompatible = this.backwardsCompatible;
1✔
723
        ctx.enableOptimizer = this.enableOptimizer;
1✔
724
        ctx.stripWhitespace = this.stripWhitespace;
1✔
725
        ctx.preserveNamespaces = this.preserveNamespaces;
1✔
726
        ctx.inheritNamespaces = this.inheritNamespaces;
1✔
727
        ctx.orderEmptyGreatest = this.orderEmptyGreatest;
1✔
728

729
        ctx.declaredFunctions = new Object2ObjectAVLTreeMap<>(this.declaredFunctions);
1✔
730
        ctx.globalVariables = new Object2ObjectRBTreeMap<>(this.globalVariables);
1✔
731
        ctx.attributes = new HashMap<>(this.attributes);
1✔
732

733
        // make imported modules available in the new context
734
        ctx.modules = new Object2ObjectOpenHashMap<>(this.modules.size(), Hash.VERY_FAST_LOAD_FACTOR);
1✔
735
        for (final Object2ObjectMap.Entry<String, Module[]> entry : Object2ObjectMaps.fastIterable(this.modules)) {
1✔
736
            final String namespaceURI = entry.getKey();
1✔
737
            final Module[] modules = entry.getValue();
1✔
738

739
            ctx.modules.put(namespaceURI, Arrays.copyOf(modules, modules.length));
1✔
740

741
            final String prefix = this.staticPrefixes.get(namespaceURI);
1✔
742
            try {
743
                ctx.declareNamespace(prefix, namespaceURI);
1✔
744
            } catch (final XPathException e) {
1✔
745
                // ignore
746
                LOG.warn(e);
×
747
            }
748
        }
749

750
        ctx.allModules = new Object2ObjectOpenHashMap<>(this.allModules.size());
1✔
751
        for (final Object2ObjectMap.Entry<String, Module[]> allModulesEntry : Object2ObjectMaps.fastIterable(this.allModules)) {
1✔
752
            final Module[] modules = allModulesEntry.getValue();
1✔
753
            ctx.allModules.put(allModulesEntry.getKey(), Arrays.copyOf(modules, modules.length));
1✔
754
        }
755

756
        ctx.watchdog = this.watchdog;
1✔
757
        ctx.profiler = getProfiler();
1✔
758
        ctx.lastVar = this.lastVar;
1✔
759
        ctx.variableStackSize = getCurrentStackSize();
1✔
760
        ctx.contextStack = this.contextStack;
1✔
761
        ctx.staticNamespaces = new HashMap<>(this.staticNamespaces);
1✔
762
        ctx.staticPrefixes = new HashMap<>(this.staticPrefixes);
1✔
763

764
        if (this.dynamicOptions != null) {
1!
765
            ctx.dynamicOptions = new ArrayList<>(this.dynamicOptions);
×
766
        }
767

768
        if (this.staticOptions != null) {
1✔
769
            ctx.staticOptions = new ArrayList<>(this.staticOptions);
1✔
770
        }
771

772
        ctx.source = this.source;
1✔
773
        ctx.httpContext = this.httpContext;
1✔
774
    }
1✔
775

776
    @Override
777
    public void prepareForExecution() {
778
        //if there is an existing user in the current http session
779
        //then set the DBBroker user
780
        final Subject user = getUserFromHttpSession();
1✔
781
        if (user != null) {
1✔
782
            getBroker().pushSubject(user);      //this will be popped in {@link XQueryContext#reset(boolean)}
1✔
783
            this.pushedUserFromHttpSession = true;
1✔
784
        }
785

786
        setRealUser(getBroker().getCurrentSubject());   //this will be unset in {@link XQueryContext#reset(boolean)}
1✔
787

788
        //Reset current context position
789
        setContextSequencePosition(0, null);
1✔
790
        //Note that, for some reasons, an XQueryContext might be used without calling this method
791
    }
1✔
792

793
    public void setContextItem(final Sequence contextItem) {
794
        this.contextItem = contextItem;
1✔
795
    }
1✔
796

797
    public void setContextItemDeclaration(final ContextItemDeclaration contextItemDeclaration) {
798
        this.contextItemDeclaration = contextItemDeclaration;
1✔
799
    }
1✔
800

801
    public ContextItemDeclaration getContextItemDeclartion() {
802
        return contextItemDeclaration;
1✔
803
    }
804

805
    public Sequence getContextItem() {
806
        return contextItem;
1✔
807
    }
808

809
    @Override
810
    public boolean isProfilingEnabled() {
811
        return profiler.isEnabled();
1✔
812
    }
813

814
    @Override
815
    public boolean isProfilingEnabled(final int verbosity) {
816
        return profiler.isEnabled() && profiler.verbosity() >= verbosity;
1!
817
    }
818

819
    @Override
820
    public Profiler getProfiler() {
821
        return profiler;
1✔
822
    }
823

824
    @Override
825
    public void setRootExpression(final Expression expr) {
826
        this.rootExpression = expr;
1✔
827
    }
1✔
828

829
    @Override
830
    public Expression getRootExpression() {
831
        return rootExpression;
1✔
832
    }
833

834
    /**
835
     * Returns the next unique expression id. Every expression in the XQuery is identified by a unique id. During compilation, expressions are
836
     * assigned their id by calling this method.
837
     *
838
     * @return The next unique expression id.
839
     */
840
    int nextExpressionId() {
841
        return expressionCounter++;
1✔
842
    }
843

844
    @Override
845
    public int getExpressionCount() {
846
        return expressionCounter;
×
847
    }
848

849
    @Override
850
    public void declareNamespace(final String prefix, final String uri) throws XPathException {
851
        if (XML_NS_PREFIX.equals(prefix) || XMLNS_ATTRIBUTE.equals(prefix)) {
1!
852
            throw new XPathException(rootExpression, ErrorCodes.XQST0070,
1✔
853
                    "Namespace predefined prefix '" + prefix + "' can not be bound");
1✔
854
        }
855

856
        if (XML_NS.equals(uri)) {
1✔
857
            throw new XPathException(rootExpression, ErrorCodes.XQST0070,
1✔
858
                    "Namespace URI '" + uri + "' must be bound to the 'xml' prefix");
1✔
859
        }
860

861
        final String nonNullPrefix = prefix == null ?  "" : prefix;
1✔
862
        final String nonNullUri = uri == null ? "" : uri;
1✔
863

864
        //This prefix was not bound
865
        if (!staticNamespaces.containsKey(nonNullPrefix)) {
1✔
866
            if (nonNullUri.isEmpty()) {
1✔
867
                //Nothing to bind
868
                //TODO : check the specs : unbinding an NS which is not already bound may be disallowed.
869
                // FIXME (JL): despite the log message nothing happens in this execution branch
870
                LOG.warn("Unbinding unbound prefix '{}'", prefix);
1✔
871
            } else {
1✔
872
                //Bind it
873
                staticNamespaces.put(nonNullPrefix, nonNullUri);
1✔
874
                staticPrefixes.put(nonNullUri, nonNullPrefix);
1✔
875
            }
876
            return;
1✔
877
        }
878

879
        //This prefix was bound
880
        final String prevURI = staticNamespaces.get(nonNullPrefix);
1✔
881
        //Unbind it
882
        if (nonNullUri.isEmpty()) {
1✔
883
            // if an empty namespace is specified,
884
            // remove any existing mapping for this namespace
885
            // FIXME (JL): this allows to rebind namespaces to a different URI
886
            staticPrefixes.remove(nonNullUri);
1✔
887
            staticNamespaces.remove(nonNullPrefix);
1✔
888
            return;
1✔
889
        }
890

891
        //those prefixes can be rebound to different URIs
892
        if (("xs".equals(nonNullPrefix) && Namespaces.SCHEMA_NS.equals(prevURI))
1!
893
                || ("xsi".equals(nonNullPrefix) && Namespaces.SCHEMA_INSTANCE_NS.equals(prevURI))
1!
894
                || ("xdt".equals(nonNullPrefix) && Namespaces.XPATH_DATATYPES_NS.equals(prevURI))
1!
895
                || ("fn".equals(nonNullPrefix) && Namespaces.XPATH_FUNCTIONS_NS.equals(prevURI))
1!
896
                || ("math".equals(nonNullPrefix)) && Namespaces.XPATH_FUNCTIONS_MATH_NS.equals(prevURI)
1!
897
                || ("local".equals(nonNullPrefix) && Namespaces.XQUERY_LOCAL_NS.equals(prevURI))) {
1!
898

899
            staticPrefixes.remove(prevURI);
1✔
900
            staticNamespaces.remove(nonNullPrefix);
1✔
901

902
            staticNamespaces.put(nonNullPrefix, nonNullUri);
1✔
903
            staticPrefixes.put(nonNullUri, nonNullPrefix);
1✔
904
            return;
1✔
905
        }
906
        //Forbids rebinding the *same* prefix in a *different* namespace in this *same* context
907
        if (!nonNullUri.equals(prevURI)) {
1✔
908
            throw new XPathException(rootExpression, ErrorCodes.XQST0033,
1✔
909
                    "Cannot bind prefix '" + prefix + "' to '" + nonNullUri + "' it is already bound to '" + prevURI + "'");
1✔
910
        }
911

912
        // FIXME (JL): function call can end up in unhandled case
913
        // e.g. declareNamespace(null,null)
914
    }
1✔
915

916
    @Override
917
    public void declareNamespaces(final Map<String, String> namespaceMap) {
918
        for (final Map.Entry<String, String> entry : namespaceMap.entrySet()) {
1✔
919
            String prefix = entry.getKey();
1✔
920
            String uri = entry.getValue();
1✔
921

922
            if (prefix == null) {
1!
923
                prefix = "";
×
924
            }
925

926
            if (uri == null) {
1!
927
                uri = "";
×
928
            }
929
            staticNamespaces.put(prefix, uri);
1✔
930
            staticPrefixes.put(uri, prefix);
1✔
931
        }
932
    }
1✔
933

934
    @Override
935
    public void removeNamespace(final String uri) {
936
        staticPrefixes.remove(uri);
×
937

938
        for (final Iterator<String> i = staticNamespaces.values().iterator(); i.hasNext(); ) {
×
939
            if (i.next().equals(uri)) {
×
940
                i.remove();
×
941
                return;
×
942
            }
943
        }
944
        inScopePrefixes.remove(uri);
×
945

946
        if (inScopeNamespaces != null) {
×
947
            for (final Iterator<String> i = inScopeNamespaces.values().iterator(); i.hasNext(); ) {
×
948
                if (i.next().equals(uri)) {
×
949
                    i.remove();
×
950
                    return;
×
951
                }
952
            }
953
        }
954
        inheritedInScopePrefixes.remove(uri);
×
955

956
        if (inheritedInScopeNamespaces != null) {
×
957
            for (final Iterator<String> i = inheritedInScopeNamespaces.values().iterator(); i.hasNext(); ) {
×
958
                if (i.next().equals(uri)) {
×
959
                    i.remove();
×
960
                    return;
×
961
                }
962
            }
963
        }
964
    }
×
965

966
    @Override
967
    public void declareInScopeNamespace(final String prefix, final String uri) {
968
        if (prefix == null || uri == null) {
1!
969
            throw new IllegalArgumentException("null argument passed to declareNamespace");
×
970
        }
971

972
        //Activate the namespace by removing it from the inherited namespaces
973
        if (inheritedInScopePrefixes.containsKey(getURIForPrefix(prefix))) {
1✔
974
            inheritedInScopePrefixes.remove(uri);
1✔
975
        }
976

977
        inheritedInScopeNamespaces.remove(prefix);
1✔
978

979
        inScopePrefixes.put(uri, prefix);
1✔
980
        inScopeNamespaces.put(prefix, uri);
1✔
981
    }
1✔
982

983
    @Override
984
    public String getInScopeNamespace(final String prefix) {
985
        return inScopeNamespaces == null ? null : inScopeNamespaces.get(prefix);
1!
986
    }
987

988
    @Override
989
    public String getInScopePrefix(final String uri) {
990
        return inScopePrefixes == null ? null : inScopePrefixes.get(uri);
1!
991
    }
992

993
    public Map<String, String> getInScopePrefixes() {
994
        return inScopePrefixes;
1✔
995
    }
996

997
    @Override
998
    public String getInheritedNamespace(final String prefix) {
999
        return inheritedInScopeNamespaces == null ? null : inheritedInScopeNamespaces.get(prefix);
1!
1000
    }
1001

1002
    @Override
1003
    public String getInheritedPrefix(final String uri) {
1004
        return inheritedInScopePrefixes == null ? null : inheritedInScopePrefixes.get(uri);
1!
1005
    }
1006

1007
    @Override
1008
    public String getURIForPrefix(final String prefix) {
1009
        // try in-scope namespace declarations
1010
        String uri = (inScopeNamespaces == null) ? null : inScopeNamespaces.get(prefix);
1!
1011

1012
        if (uri != null) {
1✔
1013
            return uri;
1✔
1014
        }
1015

1016
        if (inheritNamespaces) {
1!
1017
            uri = (inheritedInScopeNamespaces == null) ? null : inheritedInScopeNamespaces.get(prefix);
1!
1018

1019
            if (uri != null) {
1✔
1020
                return uri;
1✔
1021
            }
1022
        }
1023
        return staticNamespaces.get(prefix);
1✔
1024
    }
1025

1026
    @Override
1027
    public String getPrefixForURI(final String uri) {
1028
        String prefix = (inScopePrefixes == null) ? null : inScopePrefixes.get(uri);
1!
1029

1030
        if (prefix != null) {
1!
1031
            return prefix;
×
1032
        }
1033

1034
        if (inheritNamespaces) {
1!
1035
            prefix = (inheritedInScopePrefixes == null) ? null : inheritedInScopePrefixes.get(uri);
1!
1036

1037
            if (prefix != null) {
1!
1038
                return prefix;
×
1039
            }
1040
        }
1041
        return staticPrefixes.get(uri);
1✔
1042
    }
1043

1044
    @Override
1045
    public String getDefaultFunctionNamespace() {
1046
        return defaultFunctionNamespace;
1✔
1047
    }
1048

1049
    @Override
1050
    public void setDefaultFunctionNamespace(final String uri) throws XPathException {
1051
        //Not sure for the 2nd clause : eXist-db forces the function NS as default.
1052
        if (defaultFunctionNamespace != null
1!
1053
                && !defaultFunctionNamespace.equals(Function.BUILTIN_FUNCTION_NS)
1!
1054
                && !defaultFunctionNamespace.equals(uri)) {
×
1055
            throw new XPathException(rootExpression, ErrorCodes.XQST0066,
×
1056
                    "Default function namespace is already set to: '" + defaultFunctionNamespace + "'");
×
1057
        }
1058
        defaultFunctionNamespace = uri;
1✔
1059
    }
1✔
1060

1061
    @Override
1062
    public String getDefaultElementNamespaceSchema() {
1063
        return defaultElementNamespaceSchema.getStringValue();
×
1064
    }
1065

1066
    @Override
1067
    public void setDefaultElementNamespaceSchema(final String uri) throws XPathException {
1068
        // eXist forces the empty element NS as default.
1069
        if (!defaultElementNamespaceSchema.equals(AnyURIValue.EMPTY_URI)) {
×
1070
            throw new XPathException(rootExpression, ErrorCodes.XQST0066, "Default function namespace schema is already set to: '" + defaultElementNamespaceSchema.getStringValue() + "'");
×
1071
        }
1072
        defaultElementNamespaceSchema = new AnyURIValue(uri);
×
1073
    }
×
1074

1075
    @Override
1076
    public String getDefaultElementNamespace() {
1077
        return defaultElementNamespace.getStringValue();
×
1078
    }
1079

1080
    @Override
1081
    public void setDefaultElementNamespace(final String uri, @Nullable final String schema) throws XPathException {
1082
        // eXist forces the empty element NS as default.
1083
        if (!defaultElementNamespace.equals(AnyURIValue.EMPTY_URI)) {
×
1084
            throw new XPathException(rootExpression, ErrorCodes.XQST0066,
×
1085
                    "Default element namespace is already set to: '" + defaultElementNamespace.getStringValue() + "'");
×
1086
        }
1087
        defaultElementNamespace = new AnyURIValue(uri);
×
1088

1089
        if (schema != null) {
×
1090
            defaultElementNamespaceSchema = new AnyURIValue(schema);
×
1091
        }
1092
    }
×
1093

1094
    @Override
1095
    public void setDefaultCollation(final String uri) throws XPathException {
1096
        if (uri.equals(Collations.UNICODE_CODEPOINT_COLLATION_URI) || uri.equals(Collations.CODEPOINT_SHORT)) {
×
1097
            defaultCollation = Collations.UNICODE_CODEPOINT_COLLATION_URI;
×
1098
            defaultCollator = null;
×
1099
        }
1100

1101
        final URI uriTest;
1102
        try {
1103
            uriTest = new URI(uri);
×
1104
        } catch (final URISyntaxException e) {
×
1105
            throw new XPathException(rootExpression, ErrorCodes.XQST0038, "Unknown collation : '" + uri + "'");
×
1106
        }
1107

1108
        if (uri.startsWith(Collations.EXIST_COLLATION_URI) || uri.charAt(0) == '?' || uriTest.isAbsolute()) {
×
1109
            defaultCollator = Collations.getCollationFromURI(uri, rootExpression);
×
1110
            defaultCollation = uri;
×
1111
        } else {
×
1112
            String absUri = getBaseURI().getStringValue() + uri;
×
1113
            defaultCollator = Collations.getCollationFromURI(absUri, rootExpression);
×
1114
            defaultCollation = absUri;
×
1115
        }
1116
    }
×
1117

1118
    @Override
1119
    public String getDefaultCollation() {
1120
        return defaultCollation;
1✔
1121
    }
1122

1123
    @Override
1124
    public Collator getCollator(String uri) throws XPathException {
1125
        return getCollator(uri, ErrorCodes.XQST0076);
1✔
1126
    }
1127

1128
    @Override
1129
    public Collator getCollator(String uri, final ErrorCodes.ErrorCode errorCode) throws XPathException {
1130
        if (uri == null) {
1!
1131
            return defaultCollator;
×
1132
        }
1133

1134
        // if the uri is relative, resolve it against the base uri
1135
        try {
1136
            final URI u = new AnyURIValue(uri).toURI();
1✔
1137
            if (!u.isAbsolute()) {
1✔
1138
                final URI uu = getBaseURI().toURI().resolve(u);
1✔
1139
                if (uu.isAbsolute()) {
1!
1140
                    uri = uu.toString();
×
1141
                }
1142
            }
1143
        } catch (final XPathException e) {
×
1144
            // no-op
1145
        }
1146

1147
        return Collations.getCollationFromURI(uri, rootExpression, errorCode);
1✔
1148
    }
1149

1150
    @Override
1151
    public Collator getDefaultCollator() {
1152
        return defaultCollator;
1✔
1153
    }
1154

1155
    @Override
1156
    public void setStaticallyKnownDocuments(final XmldbURI[] docs) {
1157
        staticDocumentPaths = docs;
1✔
1158
    }
1✔
1159

1160
    @Override
1161
    public void setStaticallyKnownDocuments(final DocumentSet set) {
1162
        staticDocuments = set;
1✔
1163
    }
1✔
1164

1165
    public void addDynamicallyAvailableDocument(final String uri,
1166
                                                final TriFunctionE<DBBroker, Txn, String, Either<org.exist.dom.memtree.DocumentImpl, DocumentImpl>, XPathException> supplier) {
1167
        if (dynamicDocuments == null) {
1!
1168
            dynamicDocuments = new HashMap<>();
1✔
1169
        }
1170
        dynamicDocuments.put(uri, supplier);
1✔
1171
    }
1✔
1172

1173
    public void addDynamicallyAvailableTextResource(final String uri, final Charset encoding,
1174
                                                    final QuadFunctionE<DBBroker, Txn, String, Charset, Reader, XPathException> supplier) {
1175
        if (dynamicTextResources == null) {
1!
1176
            dynamicTextResources = new HashMap<>();
1✔
1177
        }
1178
        dynamicTextResources.put(Tuple(uri, encoding), supplier);
1✔
1179
    }
1✔
1180

1181
    public void addDynamicallyAvailableCollection(final String uri,
1182
                                                  final TriFunctionE<DBBroker, Txn, String, Sequence, XPathException> supplier) {
1183
        if (dynamicCollections == null) {
1!
1184
            dynamicCollections = new HashMap<>();
1✔
1185
        }
1186
        dynamicCollections.put(uri, supplier);
1✔
1187
    }
1✔
1188

1189
    @Override
1190
    public void setCalendar(final XMLGregorianCalendar newCalendar) {
1191
        this.calendar = (XMLGregorianCalendar) newCalendar.clone();
×
1192
    }
×
1193

1194
    @Override
1195
    public void setTimeZone(final TimeZone newTimeZone) {
1196
        this.implicitTimeZone = newTimeZone;
×
1197
    }
×
1198

1199
    @Override
1200
    public XMLGregorianCalendar getCalendar() {
1201
        //TODO : we might prefer to return null
1202
        if (calendar == null) {
1✔
1203
            try {
1204
                //Initialize to current dateTime
1205
                calendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar());
1✔
1206
            } catch (final DatatypeConfigurationException e) {
1✔
1207
                LOG.error(e.getMessage(), e);
×
1208
            }
1209
        }
1210

1211
        //That's how we ensure stability of that static context function
1212
        return calendar;
1✔
1213
    }
1214

1215
    @Override
1216
    public TimeZone getImplicitTimeZone() {
1217
        if (implicitTimeZone == null) {
1!
1218
            implicitTimeZone = TimeZone.getDefault();
×
1219

1220
            if (implicitTimeZone.inDaylightTime(new Date())) {
×
1221
                implicitTimeZone.setRawOffset(implicitTimeZone.getRawOffset() + implicitTimeZone.getDSTSavings());
×
1222
            }
1223
        }
1224

1225
        //That's how we ensure stability of that static context function
1226
        return this.implicitTimeZone;
1✔
1227
    }
1228

1229
    @Override
1230
    public DocumentSet getStaticallyKnownDocuments() throws XPathException {
1231
        if (staticDocuments != null) {
1✔
1232

1233
            // the document set has already been built, return it
1234
            return staticDocuments;
1✔
1235
        }
1236

1237
        if (protectedDocuments != null) {
1✔
1238
            staticDocuments = protectedDocuments.toDocumentSet();
1✔
1239
            return staticDocuments;
1✔
1240
        }
1241
        final MutableDocumentSet ndocs = new DefaultDocumentSet(40);
1✔
1242

1243
        if (staticDocumentPaths == null) {
1✔
1244

1245
            // no path defined: return all documents in the db
1246
            try {
1247
                getBroker().getAllXMLResources(ndocs);
1✔
1248
            } catch (final PermissionDeniedException | LockException e) {
1✔
1249
                LOG.warn(e);
×
1250
                throw new XPathException(rootExpression, "Permission denied to read resource all resources: " + e.getMessage(), e);
×
1251
            }
1252
        } else {
1253
            for (final XmldbURI staticDocumentPath : staticDocumentPaths) {
1✔
1254

1255
                try {
1256
                    final Collection collection = getBroker().getCollection(staticDocumentPath);
1✔
1257

1258
                    if (collection != null) {
1✔
1259
                        collection.allDocs(getBroker(), ndocs, true);
1✔
1260
                    } else {
1✔
1261
                        try (final LockedDocument lockedDocument = getBroker().getXMLResource(staticDocumentPath, LockMode.READ_LOCK)) {
1✔
1262

1263
                            final DocumentImpl doc = lockedDocument == null ? null : lockedDocument.getDocument();
1!
1264
                            if (doc != null) {
1!
1265

1266
                                if (doc.getPermissions().validate(
1✔
1267
                                        getBroker().getCurrentSubject(), Permission.READ)) {
1!
1268

1269
                                    ndocs.add(doc);
1✔
1270
                                }
1271
                            }
1272
                        }
1273
                    }
1274
                } catch (final PermissionDeniedException | LockException e) {
×
1275
                    LOG.warn("Permission denied to read resource {}. Skipping it.", staticDocumentPath);
×
1276
                }
1277
            }
1278
        }
1279
        staticDocuments = ndocs;
1✔
1280
        return staticDocuments;
1✔
1281
    }
1282

1283
    public DocumentSet getStaticDocs() {
1284
        return staticDocuments;
1✔
1285
    }
1286

1287
    /**
1288
     * Gets a document from the "Available documents" of the
1289
     * dynamic context.
1290
     *
1291
     * @param uri the URI by which the document was registered
1292
     * @return sequence of available documents matching the URI
1293
     * @throws XPathException in case of dynamic error
1294
     */
1295
    public @Nullable Sequence getDynamicallyAvailableDocument(final String uri) throws XPathException {
1296
        if (dynamicDocuments == null) {
1✔
1297
            return null;
1✔
1298
        }
1299

1300
        final TriFunctionE<DBBroker, Txn, String, Either<org.exist.dom.memtree.DocumentImpl, DocumentImpl>, XPathException> docSupplier
1✔
1301
                = dynamicDocuments.get(uri);
1✔
1302
        if (docSupplier == null) {
1!
1303
            return null;
×
1304
        }
1305

1306
        return docSupplier.apply(getBroker(), getBroker().getCurrentTransaction(), uri).fold(md -> md, pd -> (Sequence) pd);
1✔
1307
    }
1308

1309
    /**
1310
     * Gets a text resource from the "Available text resources" of the
1311
     * dynamic context.
1312
     *
1313
     * @param uri the URI by which the document was registered
1314
     * @param charset the charset to use for retrieving the resource
1315
     * @return a reader to read the resource content from
1316
     * @throws XPathException in case of a dynamic error
1317
     */
1318
    public @Nullable Reader getDynamicallyAvailableTextResource(final String uri, final Charset charset)
1319
            throws XPathException {
1320
        if (dynamicTextResources == null) {
1✔
1321
            return null;
1✔
1322
        }
1323

1324
        final QuadFunctionE<DBBroker, Txn, String, Charset, Reader, XPathException> textResourceSupplier
1✔
1325
                = dynamicTextResources.get(Tuple(uri, charset));
1✔
1326
        if (textResourceSupplier == null) {
1!
1327
            return null;
×
1328
        }
1329

1330
        return textResourceSupplier.apply(getBroker(), getBroker().getCurrentTransaction(), uri, charset);
1✔
1331
    }
1332

1333
    /**
1334
     * Gets a collection from the "Available collections" of the
1335
     * dynamic context.
1336
     *
1337
     * @param uri the URI of the collection to retrieve
1338
     * @return a sequence of document nodes
1339
     * @throws XPathException in case of dynamic error
1340
     */
1341
    public @Nullable Sequence getDynamicallyAvailableCollection(final String uri) throws XPathException {
1342
        if (dynamicCollections == null) {
1✔
1343
            return null;
1✔
1344
        }
1345

1346
        final TriFunctionE<DBBroker, Txn, String, Sequence, XPathException> collectionSupplier
1✔
1347
                = dynamicCollections.get(uri);
1✔
1348
        if (collectionSupplier == null) {
1!
1349
            return null;
×
1350
        }
1351

1352
        return collectionSupplier.apply(getBroker(), getBroker().getCurrentTransaction(), uri);
1✔
1353
    }
1354

1355
    @Override
1356
    public ExtendedXMLStreamReader getXMLStreamReader(final NodeValue nv) throws XMLStreamException, IOException {
1357
        final ExtendedXMLStreamReader reader;
1358
        if (nv.getImplementationType() == NodeValue.IN_MEMORY_NODE) {
1!
1359
            final NodeImpl node = (NodeImpl) nv;
1✔
1360
            final org.exist.dom.memtree.DocumentImpl ownerDoc = node.getNodeType() == Node.DOCUMENT_NODE ? (org.exist.dom.memtree.DocumentImpl) node : node.getOwnerDocument();
1!
1361
            reader = new InMemoryXMLStreamReader(ownerDoc, ownerDoc);
1✔
1362
        } else {
1✔
1363
            final NodeProxy proxy = (NodeProxy) nv;
×
1364
            reader = getBroker().newXMLStreamReader(new NodeProxy(rootExpression, proxy.getOwnerDocument(), NodeId.DOCUMENT_NODE, proxy.getOwnerDocument().getFirstChildAddress()), false);
×
1365
        }
1366
        return reader;
1✔
1367
    }
1368

1369
    @Override
1370
    public void setProtectedDocs(final LockedDocumentMap map) {
1371
        this.protectedDocuments = map;
1✔
1372
    }
1✔
1373

1374
    @Override
1375
    public LockedDocumentMap getProtectedDocs() {
1376
        return this.protectedDocuments;
1✔
1377
    }
1378

1379
    @Override
1380
    public boolean inProtectedMode() {
1381
        return protectedDocuments != null;
1✔
1382
    }
1383

1384
    @Override
1385
    public boolean lockDocumentsOnLoad() {
1386
        return false;
1✔
1387
    }
1388

1389
    @Override
1390
    public void setShared(final boolean shared) {
1391
        isShared = shared;
1✔
1392
    }
1✔
1393

1394
    @Override
1395
    public boolean isShared() {
1396
        return isShared;
×
1397
    }
1398

1399
    @Override
1400
    public void addModifiedDoc(final DocumentImpl document) {
1401
        if (modifiedDocuments == null) {
1✔
1402
            modifiedDocuments = new DefaultDocumentSet();
1✔
1403
        }
1404
        modifiedDocuments.add(document);
1✔
1405
    }
1✔
1406

1407
    @Override
1408
    public void reset() {
1409
        reset(false);
1✔
1410
    }
1✔
1411

1412
    @Override
1413
    public void reset(final boolean keepGlobals) {
1414
        if (importedContexts != null) {
1✔
1415
            for (final XQueryContext importedContext : importedContexts) {
1✔
1416
                importedContext.reset(keepGlobals);
1✔
1417
            }
1418
            importedContexts = null;
1✔
1419
        }
1420

1421
        setRealUser(null);
1✔
1422

1423
        if (this.pushedUserFromHttpSession) {
1✔
1424
            try {
1425
                getBroker().popSubject();
1✔
1426
            } finally {
1✔
1427
                this.pushedUserFromHttpSession = false;
1✔
1428
            }
1429
        }
1430

1431
        if (modifiedDocuments != null) {
1✔
1432
            try {
1433
                Modification.checkFragmentation(this, modifiedDocuments);
1✔
1434
            } catch (final LockException | EXistException e) {
1✔
1435
                LOG.warn("Error while checking modified documents: {}", e.getMessage(), e);
×
1436
            }
1437
            modifiedDocuments = null;
1✔
1438
        }
1439

1440
        calendar = null;
1✔
1441
        implicitTimeZone = null;
1✔
1442

1443
        resetDocumentBuilder();
1✔
1444

1445
        contextSequence = null;
1✔
1446
        contextItem = Sequence.EMPTY_SEQUENCE;
1✔
1447

1448
        if (!keepGlobals) {
1✔
1449
            // do not reset the statically known documents
1450
            staticDocumentPaths = null;
1✔
1451
            staticDocuments = null;
1✔
1452
            dynamicDocuments = null;
1✔
1453
            dynamicTextResources = null;
1✔
1454
            dynamicCollections = null;
1✔
1455
        }
1456

1457
        if (!isShared) {
1✔
1458
            lastVar = null;
1✔
1459
        }
1460

1461
        // clear inline functions using closures
1462
        closures.forEach(func -> func.setClosureVariables(null));
1✔
1463
        closures.clear();
1✔
1464

1465
        fragmentStack = new ArrayDeque<>();
1✔
1466
        callStack.clear();
1✔
1467
        protectedDocuments = null;
1✔
1468

1469
        cachedUriCollectionResults.clear();
1✔
1470

1471
        if (!keepGlobals) {
1✔
1472
            globalVariables.clear();
1✔
1473
        }
1474

1475
        if (dynamicOptions != null) {
1!
1476
            dynamicOptions.clear(); //clear any dynamic options
×
1477
        }
1478

1479
        if (!isShared) {
1✔
1480
            watchdog.reset();
1✔
1481
        }
1482

1483
        /*
1484
            NOTE: we use `modules` (and not `allModules`) here so as to only reset
1485
            the modules of this module.
1486
            The inner call to `module.reset` will be called on sub-modules
1487
            which in-turn will reset their modules, and so on.
1488
         */
1489
        for (final Module[] modules : modules.values()) {
1✔
1490
            for (final Module module : modules) {
1✔
1491
                module.reset(this, keepGlobals);
1✔
1492
            }
1493
        }
1494

1495
        savedState.restore();
1✔
1496

1497
        attributes.clear();
1✔
1498

1499
        clearUpdateListeners();
1✔
1500

1501
        profiler.reset();
1✔
1502

1503
        if (!keepGlobals) {
1✔
1504
            httpContext = null;
1✔
1505
        }
1506

1507
        if (modulesDependencyGraphSPExecutor != null) {
1✔
1508
            try {
1509
                ConcurrencyUtil.shutdownExecutionService(modulesDependencyGraphSPExecutor);
1✔
1510
            } catch (final InterruptedException e) {
1✔
1511
                Thread.currentThread().interrupt();
×
1512
            }
1513
            modulesDependencyGraphSPExecutor = null;
1✔
1514
        }
1515

1516
        analyzed = false;
1✔
1517
    }
1✔
1518

1519
    @Override
1520
    public boolean stripWhitespace() {
1521
        return stripWhitespace;
1✔
1522
    }
1523

1524
    @Override
1525
    public void setStripWhitespace(final boolean strip) {
1526
        this.stripWhitespace = strip;
1✔
1527
    }
1✔
1528

1529
    @Override
1530
    public boolean preserveNamespaces() {
1531
        return preserveNamespaces;
1✔
1532
    }
1533

1534
    @Override
1535
    public void setPreserveNamespaces(final boolean preserve) {
1536
        this.preserveNamespaces = preserve;
×
1537
    }
×
1538

1539
    @Override
1540
    public boolean inheritNamespaces() {
1541
        return inheritNamespaces;
1✔
1542
    }
1543

1544
    @Override
1545
    public void setInheritNamespaces(final boolean inherit) {
1546
        this.inheritNamespaces = inherit;
×
1547
    }
×
1548

1549
    @Override
1550
    public boolean orderEmptyGreatest() {
1551
        return orderEmptyGreatest;
1✔
1552
    }
1553

1554
    @Override
1555
    public void setOrderEmptyGreatest(final boolean order) {
1556
        this.orderEmptyGreatest = order;
×
1557
    }
×
1558

1559
    @Override
1560
    public Iterator<Module> getModules() {
1561
        return new CollectionOfArrayIterator<>(modules.values());
1✔
1562
    }
1563

1564
    @Override
1565
    public Iterator<Module> getRootModules() {
1566
        return getAllModules();
1✔
1567
    }
1568

1569
    @Override
1570
    public Iterator<Module> getAllModules() {
1571
        return new CollectionOfArrayIterator<>(allModules.values());
1✔
1572
    }
1573

1574
    @Override
1575
    @Nullable
1576
    public Module[] getModules(final String namespaceURI) {
1577
        return modules.get(namespaceURI);
1✔
1578
    }
1579

1580
    @Override
1581
    public Module[] getRootModules(final String namespaceURI) {
1582
        return allModules.get(namespaceURI);
1✔
1583
    }
1584

1585
    @Override
1586
    public void setModules(final String namespaceURI, @Nullable final Module[] modules) {
1587
        if (modules == null) {
1!
1588
            this.modules.remove(namespaceURI); // unbind the module
×
1589
        } else {
×
1590
            this.modules.put(namespaceURI, modules);
1✔
1591
        }
1592
        setRootModules(namespaceURI, modules);
1✔
1593
    }
1✔
1594

1595
    @Override
1596
    public void addModule(final String namespaceURI, final Module module) {
1597
        if (module == null) {
1!
1598
            throw new IllegalArgumentException();
×
1599
        } else {
1600
            this.modules.compute(namespaceURI, addToMapValueArray(module));
1✔
1601
        }
1602
        addRootModule(namespaceURI, module);
1✔
1603
    }
1✔
1604

1605
    private Graph<ModuleVertex, DefaultEdge> getModulesDependencyGraph() {
1606
        // NOTE(AR) intentionally lazily initialised!
1607
        if (modulesDependencyGraph == null) {
1✔
1608
            this.modulesDependencyGraph = new FastutilMapGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), DefaultGraphType.directedPseudograph().asUnweighted());
1✔
1609
        }
1610
        return modulesDependencyGraph;
1✔
1611
    }
1612

1613
    /**
1614
     * Add a vertex to the Modules Dependency Graph.
1615
     *
1616
     * @param moduleVertex the module vertex
1617
     */
1618
    protected void addModuleVertex(final ModuleVertex moduleVertex) {
1619
        getModulesDependencyGraph().addVertex(moduleVertex);
1✔
1620
    }
1✔
1621

1622
    /**
1623
     * Check if a vertex exists in the Modules Dependency Graph.
1624
     *
1625
     * @param moduleVertex the module vertex to look for
1626
     *
1627
     * @return true if the module vertex exists, false otherwise
1628
     */
1629
    protected boolean hasModuleVertex(final ModuleVertex moduleVertex) {
1630
        return getModulesDependencyGraph().containsVertex(moduleVertex);
1✔
1631
    }
1632

1633
    /**
1634
     * Add an edge between two Modules in the Dependency Graph.
1635
     *
1636
     * @param source the importing module
1637
     * @param sink the imported module
1638
     */
1639
    protected void addModuleEdge(final ModuleVertex source, final ModuleVertex sink) {
1640
        getModulesDependencyGraph().addEdge(source, sink);
1✔
1641
    }
1✔
1642

1643
    /**
1644
     * Look for a path between two Modules in the Dependency Graph.
1645
     *
1646
     * @param source the module to start searching from
1647
     * @param sink the destination module to attempt to reach
1648
     *
1649
     * @return true, if there is a path between the mdoules, false otherwise
1650
     */
1651
    protected boolean hasModulePath(final ModuleVertex source, final ModuleVertex sink) {
1652
        if (modulesDependencyGraph == null) {
1!
1653
            return false;
×
1654
        }
1655

1656
        // NOTE(AR) intentionally lazily initialised!
1657
        if (modulesDependencyGraphSPExecutor == null) {
1✔
1658
            modulesDependencyGraphSPExecutor = ConcurrencyUtil.createThreadPoolExecutor(2);
1✔
1659
        }
1660

1661
        final ShortestPathAlgorithm<ModuleVertex, DefaultEdge> spa = new TransitNodeRoutingShortestPath<>(getModulesDependencyGraph(), modulesDependencyGraphSPExecutor);
1✔
1662
        return spa.getPath(source, sink) != null;
1✔
1663
    }
1664

1665
    protected void setRootModules(final String namespaceURI, @Nullable final Module[] modules) {
1666
        if (modules == null) {
1!
1667
            allModules.remove(namespaceURI); // unbind the module
×
1668
            return;
×
1669
        }
1670

1671
        if (allModules.get(namespaceURI) != modules) {
1!
1672
            setModulesChanged();
×
1673
        }
1674
        allModules.put(namespaceURI, modules);
1✔
1675
    }
1✔
1676

1677
    protected void addRootModule(final String namespaceURI, final Module module) {
1678
        if (module == null) {
1!
1679
            throw new IllegalArgumentException();
×
1680
        } else {
1681
            final Module[] current = this.allModules.get(namespaceURI);
1✔
1682
            final Module[] updated = this.allModules.compute(namespaceURI, addToMapValueArray(module));
1✔
1683
            if (current != updated) {
1✔
1684
                setModulesChanged();  // NOTE: intentional object identity comparison
1✔
1685
            }
1686
        }
1687
    }
1✔
1688

1689
    protected void setModulesChanged() {
1690
        this.modulesChanged = true;
1✔
1691
    }
1✔
1692

1693
    @Override
1694
    public boolean checkModulesValid() {
1695
        for (final Module[] modules : allModules.values()) {
1✔
1696
            for (final Module module : modules) {
1✔
1697
                if (!module.isInternalModule()) {
1✔
1698
                    if (!((ExternalModule) module).moduleIsValid()) {
1✔
1699
                        if (LOG.isDebugEnabled()) {
1!
1700
                            LOG.debug("Module with URI {} has changed and needs to be reloaded", module.getNamespaceURI());
×
1701
                        }
1702
                        return false;
1✔
1703
                    }
1704
                }
1705
            }
1706
        }
1707
        return true;
1✔
1708
    }
1709

1710
    @Override
1711
    public void analyzeAndOptimizeIfModulesChanged(final Expression expr) throws XPathException {
1712
        if (analyzed) {
1✔
1713
            return;
1✔
1714
        }
1715
        analyzed = true;
1✔
1716
        for (final Module[] modules : expr.getContext().modules.values()) {
1✔
1717
            for (final Module module : modules) {
1✔
1718
                if (!module.isInternalModule()) {
1✔
1719
                    final Expression root = ((ExternalModule) module).getRootExpression();
1✔
1720
                    ((ExternalModule) module).getContext().analyzeAndOptimizeIfModulesChanged(root);
1✔
1721
                }
1722
            }
1723
        }
1724
        expr.analyze(new AnalyzeContextInfo());
1✔
1725

1726
        if (optimizationsEnabled()) {
1✔
1727
            final Optimizer optimizer = new Optimizer(this);
1✔
1728
            expr.accept(optimizer);
1✔
1729

1730
            if (optimizer.hasOptimized()) {
1✔
1731
                reset(true);
1✔
1732
                expr.resetState(true);
1✔
1733
                expr.analyze(new AnalyzeContextInfo());
1✔
1734
            }
1735
        }
1736
        modulesChanged = false;
1✔
1737
    }
1✔
1738

1739
    @Override
1740
    public @Nullable Module loadBuiltInModule(final String namespaceURI, final String moduleClass) {
1741
        Module[] modules = null;
1✔
1742
        if (namespaceURI != null) {
1!
1743
            modules = getModules(namespaceURI);
1✔
1744
        }
1745

1746
        if (modules != null) {
1!
1747
            for (final Module module : modules) {
×
1748
                if (moduleClass.equals(module.getClass().getName())) {
×
1749
                    if (LOG.isDebugEnabled()) {
×
1750
                        LOG.debug("module {} is already present", namespaceURI);
×
1751
                    }
1752
                    return module;
×
1753
                }
1754
            }
1755
        }
1756

1757
        return initBuiltInModule(namespaceURI, moduleClass);
1✔
1758
    }
1759

1760
    @SuppressWarnings("unchecked")
1761
    Module initBuiltInModule(final String namespaceURI, final String moduleClassName) {
1762
        try {
1763
            // lookup the class
1764
                        final ClassLoader existClassLoader;
1765
            if (getBroker() != null) {
1✔
1766
                existClassLoader = getBroker().getBrokerPool().getClassLoader();
1✔
1767
            } else {
1✔
1768
                existClassLoader = Thread.currentThread().getContextClassLoader();
1✔
1769
            }
1770

1771
            final Class<?> mClass = Class.forName(moduleClassName, false, existClassLoader);
1✔
1772

1773
            if (!(Module.class.isAssignableFrom(mClass))) {
1!
1774
                LOG.info("failed to load module. {} is not an instance of org.exist.xquery.Module.", moduleClassName);
×
1775
                return null;
×
1776
            }
1777
            // INOTE: expathrepo
1778
            final Module module = instantiateModule(namespaceURI, (Class<Module>) mClass,
1✔
1779
                    getConfiguration() != null ? (Map<String, Map<String, List<? extends Object>>>) getConfiguration().getProperty(PROPERTY_MODULE_PARAMETERS) : Collections.emptyMap());
1✔
1780

1781
            if (LOG.isDebugEnabled()) {
1!
1782
                LOG.debug("module {} loaded successfully.", module.getNamespaceURI());
×
1783
            }
1784
            return module;
1✔
1785
        } catch (final ClassNotFoundException e) {
1✔
1786
            LOG.warn("module class {} not found. Skipping...", moduleClassName);
1✔
1787
            return null;
1✔
1788
        }
1789
    }
1790

1791
    private @Nullable Module instantiateModule(final String namespaceURI, final Class<Module> moduleClass,
1792
                                               final Map<String, Map<String, List<? extends Object>>> moduleParameters) {
1793
        try {
1794
            final Constructor<Module> constructor = XQueryContext.getModuleConstructor(moduleClass);
1✔
1795

1796
            if (constructor == null) {
1!
1797
                LOG.warn("Module {} has no matching public constructor!", moduleClass.getName());
×
1798
                return null;
×
1799
            }
1800

1801
            final Module module;
1802
            if (constructor.getParameterCount() == 1) {
1!
1803
                module = constructor.newInstance(moduleParameters.get(namespaceURI));
1✔
1804
            } else {
1✔
1805
                // attempt for a constructor that takes 0 arguments
1806
                module = constructor.newInstance();
×
1807
            }
1808

1809
            if (namespaceURI != null && !module.getNamespaceURI().equals(namespaceURI)) {
1!
1810
                LOG.warn("the module declares a different namespace URI. Expected: {} found: {}", namespaceURI, module.getNamespaceURI());
×
1811
                return null;
×
1812
            }
1813

1814
            if (getPrefixForURI(module.getNamespaceURI()) == null && !module.getDefaultPrefix().isEmpty()) {
1!
1815
                declareNamespace(module.getDefaultPrefix(), module.getNamespaceURI());
1✔
1816
            }
1817

1818
            modules.compute(module.getNamespaceURI(), addToMapValueArray(module));
1✔
1819
            allModules.compute(module.getNamespaceURI(), addToMapValueArray(module));
1✔
1820

1821
            if (module instanceof InternalModule) {
1!
1822
                ((InternalModule) module).prepare(this);
1✔
1823
            }
1824
            return module;
1✔
1825
        } catch (final InstantiationException | IllegalAccessException | InvocationTargetException | XPathException e) {
×
1826
            LOG.warn("error while instantiating module class {}", moduleClass.getName(), e);
×
1827
            return null;
×
1828
        }
1829
    }
1830

1831
    private static @Nullable Constructor<Module> getModuleConstructor(Class<Module> moduleClazz) {
1832
        try {
1833
            // prefer constructor that takes one Map argument
1834
            return moduleClazz.getConstructor(Map.class);
1✔
1835
        } catch (final NoSuchMethodException noPreferredConstructor) {
×
1836
            // attempt for a constructor that takes 0 arguments
1837
            try {
1838
                return moduleClazz.getConstructor();
×
1839
            } catch (final NoSuchMethodException noUsableConstructor) {
×
1840
                return null;
×
1841
            }
1842
        }
1843
    }
1844

1845
    private static BiFunction<String, Module[], Module[]> addToMapValueArray(final Module module) {
1846
        return (namespaceURI, modules) -> {
1✔
1847
            if (modules == null) {
1✔
1848
                return new Module[]{ module };
1✔
1849
            }
1850

1851
            // check if the module is already present
1852
            for (final Module existingModule : modules) {
1✔
1853
                if (existingModule == module) {  // NOTE: intentional object identity comparison
1✔
1854
                    return modules;  // already present, no further action needed
1✔
1855
                }
1856
            }
1857

1858
            // add the module to the end of current array of modules
1859
            final Module[] newModules = Arrays.copyOf(modules, modules.length + 1);
1✔
1860
            newModules[newModules.length - 1] = module;
1✔
1861
            return newModules;
1✔
1862
        };
1863
    }
1864

1865
    @Override
1866
    public void declareFunction(final UserDefinedFunction function) throws XPathException {
1867
        // TODO: redeclaring functions should be forbidden. however, throwing an
1868
        // exception will currently break util:eval.
1869

1870
        final QName name = function.getSignature().getName();
1✔
1871
        final String uri = name.getNamespaceURI();
1✔
1872

1873
        if (uri.isEmpty()) {
1!
1874
            throw new XPathException(function, ErrorCodes.XQST0060,
×
1875
                    "Every declared function name must have a non-null namespace URI, " +
×
1876
                            "but function '" + name + "' does not meet this requirement.");
×
1877
        }
1878

1879
        if (Namespaces.PROTECTED_NS.contains(uri)) {
1!
1880
            throw new XPathException(function, ErrorCodes.XQST0045,
×
1881
                    "Function '" + name + "' is in the forbidden namespace '" + uri + "'");
×
1882
        }
1883

1884
        final FunctionSignature signature = function.getSignature();
1✔
1885
        final FunctionId functionKey = signature.getFunctionId();
1✔
1886
        if (declaredFunctions.containsKey(functionKey)) {
1✔
1887
            throw new XPathException(function, ErrorCodes.XQST0034,
1✔
1888
                    "Function " +  signature.getName().toURIQualifiedName() + '#' + signature.getArgumentCount()
1✔
1889
                            + " is already defined.");
1890
        }
1891

1892
        declaredFunctions.put(functionKey, function);
1✔
1893
    }
1✔
1894

1895
    @Override
1896
    public @Nullable UserDefinedFunction resolveFunction(final QName name, final int argCount) {
1897
        final FunctionId id = new FunctionId(name, argCount);
1✔
1898
        return declaredFunctions.get(id);
1✔
1899
    }
1900

1901
    @Override
1902
    public Iterator<FunctionSignature> getSignaturesForFunction(final QName name) {
1903
        final List<FunctionSignature> signatures = new ArrayList<>(2);
×
1904
        for (final UserDefinedFunction func : declaredFunctions.values()) {
×
1905
            if (func.getName().equals(name)) {
×
1906
                signatures.add(func.getSignature());
×
1907
            }
1908
        }
1909
        return signatures.iterator();
×
1910
    }
1911

1912
    @Override
1913
    public Iterator<UserDefinedFunction> localFunctions() {
1914
        return declaredFunctions.values().iterator();
1✔
1915
    }
1916

1917
    @Override
1918
    public LocalVariable declareVariableBinding(final LocalVariable var) throws XPathException {
1919
        if (lastVar != null) {
1✔
1920
            lastVar.addAfter(var);
1✔
1921
        }
1922
        lastVar = var;
1✔
1923
        var.setStackPosition(getCurrentStackSize());
1✔
1924
        return var;
1✔
1925
    }
1926

1927
    @Override
1928
    public Variable declareGlobalVariable(final Variable var) {
1929
        globalVariables.put(var.getQName(), var);
1✔
1930
        var.setStackPosition(getCurrentStackSize());
1✔
1931
        return var;
1✔
1932
    }
1933

1934
    @Override
1935
    public void undeclareGlobalVariable(final QName name) {
1936
        globalVariables.remove(name);
×
1937
    }
×
1938

1939
    @Override
1940
    public Variable declareVariable(final String qname, final Object value) throws XPathException {
1941
        try {
1942
            return declareVariable(QName.parse(this, qname, null), value);
1✔
1943
        } catch (final QName.IllegalQNameException e) {
×
1944
            throw new XPathException(rootExpression, ErrorCodes.XPST0081, "No namespace defined for prefix: " + qname);
×
1945
        }
1946
    }
1947

1948
    @Override
1949
    public Variable declareVariable(final QName qn, final Object value) throws XPathException {
1950
        @Nullable final Module[] modules = getModules(qn.getNamespaceURI());
1✔
1951

1952
        if (modules != null && modules.length > 0) {
1!
1953
            if (modules.length > 1) {
1!
1954
                // TODO(AR) is seems that we will never enter this state...
1955
                throw new IllegalStateException("There is more than one module, but the variable can only be declared in one!");
×
1956
            }
1957

1958
            return modules[0].declareVariable(qn, value);
1✔
1959
        }
1960

1961
        final Sequence val = XPathUtil.javaObjectToXPath(value, this, rootExpression);
1✔
1962

1963
        final Variable var;
1964
        if (globalVariables.containsKey(qn)) {
1✔
1965
            var = globalVariables.get(qn);
1✔
1966
        } else {
1✔
1967
            var = new VariableImpl(qn);
1✔
1968
            globalVariables.put(qn, var);
1✔
1969
        }
1970

1971
        // deliberate duplication of code to return early
1972
        if (var.getSequenceType() == null) {
1✔
1973
            var.setValue(val);
1✔
1974
            return var;
1✔
1975
        }
1976

1977
        final Cardinality actualCardinality;
1978

1979
        if (val.isEmpty()) {
1!
1980
            actualCardinality = Cardinality.EMPTY_SEQUENCE;
×
1981
        } else if (val.hasMany()) {
1!
1982
            actualCardinality = Cardinality._MANY;
×
1983
        } else {
×
1984
            actualCardinality = Cardinality.EXACTLY_ONE;
1✔
1985
        }
1986

1987
        //Type.EMPTY is *not* a subtype of other types ; checking cardinality first
1988
        if (!var.getSequenceType().getCardinality().isSuperCardinalityOrEqualOf(actualCardinality)) {
1!
1989
            throw new XPathException(rootExpression, ErrorCodes.XPTY0004,
×
1990
                    "XPTY0004: Invalid cardinality for variable $" + var.getQName() + ". " +
×
1991
                            "Expected " + var.getSequenceType().getCardinality().getHumanDescription() + ", " +
×
1992
                            "got " + actualCardinality.getHumanDescription());
×
1993
        }
1994

1995
        //TODO : ignore nodes right now ; they are returned as xs:untypedAtomicType
1996
        if (!val.isEmpty() && !Type.subTypeOf(val.getItemType(), var.getSequenceType().getPrimaryType())) {
1!
1997
            throw new XPathException(rootExpression, ErrorCodes.XPTY0004,
×
1998
                    "XPTY0004: Invalid type for variable $" + var.getQName() + ". " +
×
1999
                            "Expected " + Type.getTypeName(var.getSequenceType().getPrimaryType()) + ", " +
×
2000
                            "got " + Type.getTypeName(val.getItemType()));
×
2001
        }
2002

2003
        //TODO : should we allow global variable *re*declaration ?
2004
        var.setValue(val);
1✔
2005
        return var;
1✔
2006
    }
2007

2008
    @Override
2009
    public Variable resolveVariable(final String name) throws XPathException {
2010
        try {
2011
            final QName qn = QName.parse(this, name, null);
1✔
2012
            return resolveVariable(qn);
1✔
2013
        } catch (final QName.IllegalQNameException e) {
×
2014
            throw new XPathException(rootExpression, ErrorCodes.XPST0081, "No namespace defined for prefix " + name);
×
2015
        }
2016
    }
2017

2018
    @Override
2019
    public Variable resolveVariable(final QName qname) throws XPathException {
2020
        return resolveVariable(null, qname);
1✔
2021
    }
2022

2023
    @Override
2024
    public Variable resolveVariable(@Nullable final AnalyzeContextInfo contextInfo, final QName qname) throws XPathException {
2025
        // check if the variable is declared local
2026
        Variable var = resolveLocalVariable(qname);
1✔
2027

2028
        // check if the variable is declared in a module
2029
        if (var == null) {
1✔
2030
            final Module[] modules = getModules(qname.getNamespaceURI());
1✔
2031

2032
            if (modules != null) {
1✔
2033
                for (final Module module : modules) {
1✔
2034
                    var = module.resolveVariable(contextInfo, qname);
1✔
2035
                    if (var != null) {
1✔
2036
                        break;
1✔
2037
                    }
2038
                }
2039
            }
2040
        }
2041

2042
        // check if the variable is declared global
2043
        if (var == null) {
1✔
2044
            var = globalVariables.get(qname);
1✔
2045
        }
2046

2047
        //if (var == null)
2048
        //  throw new XPathException(rootExpression, "variable $" + qname + " is not bound");
2049
        return var;
1✔
2050
    }
2051

2052
    Variable resolveGlobalVariable(final QName qname) {
2053
        return globalVariables.get(qname);
1✔
2054
    }
2055

2056
    protected Variable resolveLocalVariable(final QName qname) throws XPathException {
2057
        final LocalVariable end = contextStack.peek();
1✔
2058
        for (LocalVariable var = lastVar; var != null; var = var.before) {
1✔
2059
            if (var == end) {
1✔
2060
                return null;
1✔
2061
            }
2062
            if (qname.equals(var.getQName())) {
1✔
2063
                return var;
1✔
2064
            }
2065
        }
2066
        return null;
1✔
2067
    }
2068

2069
    @Override
2070
    public boolean isVarDeclared(final QName qname) {
2071
        final Module[] modules = getModules(qname.getNamespaceURI());
1✔
2072
        if (modules != null) {
1!
2073
            for (final Module module : modules) {
×
2074
                if (module.isVarDeclared(qname)) {
×
2075
                    return true;
×
2076
                }
2077
            }
2078
        }
2079
        return globalVariables.containsKey(qname);
1✔
2080
    }
2081

2082
    @Override
2083
    public Map<QName, Variable> getVariables() {
2084
        final Map<QName, Variable> variables = new Object2ObjectRBTreeMap<>(globalVariables);
×
2085
        LocalVariable end = contextStack.peek();
×
2086
        for (LocalVariable var = lastVar; var != null; var = var.before) {
×
2087
            if (var == end) {
×
2088
                break;
×
2089
            }
2090
            variables.put(var.getQName(), var);
×
2091
        }
2092
        return variables;
×
2093
    }
2094

2095
    @Override
2096
    public Map<QName, Variable> getLocalVariables() {
2097
        final Map<QName, Variable> variables = new HashMap<>();
×
2098
        LocalVariable end = contextStack.peek();
×
2099
        for (LocalVariable var = lastVar; var != null; var = var.before) {
×
2100
            if (var == end) {
×
2101
                break;
×
2102
            }
2103
            variables.put(var.getQName(), var);
×
2104
        }
2105
        return variables;
×
2106
    }
2107

2108
    /**
2109
     * Return a copy of all currently visible local variables.
2110
     * Used by {@link InlineFunction} to implement closures.
2111
     *
2112
     * @return currently visible local variables as a stack
2113
     */
2114
    public List<ClosureVariable> getLocalStack() {
2115
        List<ClosureVariable> closure = null;
1✔
2116
        final LocalVariable end = contextStack.peek();
1✔
2117
        for (LocalVariable var = lastVar; var != null; var = var.before) {
1✔
2118

2119
            if (var == end) {
1✔
2120
                break;
1✔
2121
            }
2122

2123
            if (closure == null) {
1✔
2124
                closure = new ArrayList<>(6);
1✔
2125
            }
2126
            closure.add(new ClosureVariable(var));
1✔
2127
        }
2128

2129
        return closure;
1✔
2130
    }
2131

2132
    @Override
2133
    public Map<QName, Variable> getGlobalVariables() {
2134
        return new Object2ObjectRBTreeMap<>(globalVariables);
×
2135
    }
2136

2137
    /**
2138
     * Restore a saved stack of local variables. Used to implement closures.
2139
     *
2140
     * @param stack the stack of local variables
2141
     * @throws XPathException if the stack cannot be restored
2142
     */
2143
    public void restoreStack(final List<ClosureVariable> stack) throws XPathException {
2144
        for (int i = stack.size() - 1; i > -1; i--) {
1✔
2145
            declareVariableBinding(new ClosureVariable(stack.get(i)));
1✔
2146
        }
2147
    }
1✔
2148

2149
    @Override
2150
    public void setBackwardsCompatibility(boolean backwardsCompatible) {
2151
        this.backwardsCompatible = backwardsCompatible;
×
2152
    }
×
2153

2154
    @Override
2155
    public boolean isBackwardsCompatible() {
2156
        return this.backwardsCompatible;
1✔
2157
    }
2158

2159
    @Override
2160
    public boolean isRaiseErrorOnFailedRetrieval() {
2161
        return raiseErrorOnFailedRetrieval;
1✔
2162
    }
2163

2164
    public Database getDatabase() {
2165
        return db;
1✔
2166
    }
2167

2168
    @Override
2169
    public DBBroker getBroker() {
2170
        if (db != null) {
1✔
2171
            return db.getActiveBroker();
1✔
2172
        }
2173
        return null;
1✔
2174
    }
2175

2176
    public Configuration getConfiguration() {
2177
        return configuration;
1✔
2178
    }
2179

2180
    @Override
2181
    public Subject getSubject() {
2182
        return getBroker().getCurrentSubject();
1✔
2183
    }
2184

2185
    /**
2186
     * 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.
2187
     *
2188
     * @return The user or null if there is no session or no user
2189
     */
2190
    Subject getUserFromHttpSession() {
2191
        final Optional<RequestWrapper> maybeRequest = Optional.ofNullable(getHttpContext())
1✔
2192
                .map(HttpContext::getRequest);
1✔
2193

2194
        if (maybeRequest.isPresent()) {
1✔
2195
            final RequestWrapper request = maybeRequest.get();
1✔
2196
            final Object user = request.getAttribute(HTTP_REQ_ATTR_USER);
1✔
2197
            final Object passAttr = request.getAttribute(HTTP_REQ_ATTR_PASS);
1✔
2198
            if (user != null) {
1!
2199
                final String password = passAttr == null ? null : passAttr.toString();
×
2200
                try {
2201
                    return getBroker().getBrokerPool().getSecurityManager().authenticate(user.toString(), password);
×
2202
                } catch (final AuthenticationException e) {
×
2203
                    LOG.error("User can not be authenticated: {}", user.toString());
×
2204
                }
2205
            } else {
×
2206
                final Optional<SessionWrapper> maybeSession = Optional.ofNullable(getHttpContext())
1✔
2207
                        .map(HttpContext::getSession);
1✔
2208
                if (maybeSession.isPresent()) {
1✔
2209
                    try {
2210
                        return (Subject) maybeSession.get().getAttribute(HTTP_SESSIONVAR_XMLDB_USER);
1✔
2211
                    } catch (final IllegalStateException e) {
×
2212
                        // this is thrown if HttpSession#getAttribute is called on an invalid session
2213
                        return null;
×
2214
                    }
2215
                }
2216
            }
2217
        }
2218

2219
        return null;
1✔
2220
    }
2221

2222
    /**
2223
     * The builder used for creating in-memory document fragments.
2224
     */
2225
    private MemTreeBuilder documentBuilder = null;
1✔
2226

2227
    @Override
2228
    public MemTreeBuilder getDocumentBuilder() {
2229
        if (documentBuilder == null) {
1✔
2230
            documentBuilder = new MemTreeBuilder(rootExpression, this);
1✔
2231
            documentBuilder.startDocument();
1✔
2232
        }
2233
        return documentBuilder;
1✔
2234
    }
2235

2236
    @Override
2237
    public MemTreeBuilder getDocumentBuilder(final boolean explicitCreation) {
2238
        if (documentBuilder == null) {
1!
2239
            documentBuilder = new MemTreeBuilder(rootExpression, this);
1✔
2240
            documentBuilder.startDocument(explicitCreation);
1✔
2241
        }
2242
        return documentBuilder;
1✔
2243
    }
2244

2245
    private void resetDocumentBuilder() {
2246
        this.documentBuilder = null;
1✔
2247
    }
1✔
2248

2249
    private void setDocumentBuilder(final MemTreeBuilder documentBuilder) {
2250
        this.documentBuilder = documentBuilder;
1✔
2251
    }
1✔
2252

2253
    @Override
2254
    public NamePool getSharedNamePool() {
2255
        if (sharedNamePool == null) {
1✔
2256
            sharedNamePool = new NamePool();
1✔
2257
        }
2258
        return sharedNamePool;
1✔
2259
    }
2260

2261
    @Override
2262
    public XQueryContext getContext() {
2263
        return null;
×
2264
    }
2265

2266
    @Override
2267
    public void prologEnter(final Expression expr) {
2268
        if (debuggeeJoint != null) {
1!
2269
            debuggeeJoint.prologEnter(expr);
×
2270
        }
2271
    }
1✔
2272

2273
    @Override
2274
    public void expressionStart(final Expression expr) throws TerminatedException {
2275
        if (debuggeeJoint != null) {
1!
2276
            debuggeeJoint.expressionStart(expr);
×
2277
        }
2278
    }
1✔
2279

2280
    @Override
2281
    public void expressionEnd(final Expression expr) {
2282
        if (debuggeeJoint != null) {
1!
2283
            debuggeeJoint.expressionEnd(expr);
×
2284
        }
2285
    }
1✔
2286

2287
    @Override
2288
    public void stackEnter(final Expression expr) throws TerminatedException {
2289
        if (debuggeeJoint != null) {
1!
2290
            debuggeeJoint.stackEnter(expr);
×
2291
        }
2292
    }
1✔
2293

2294
    @Override
2295
    public void stackLeave(final Expression expr) {
2296
        if (debuggeeJoint != null) {
1!
2297
            debuggeeJoint.stackLeave(expr);
×
2298
        }
2299
    }
1✔
2300

2301
    @Override
2302
    public void proceed() throws TerminatedException {
2303
        getWatchDog().proceed(null);
1✔
2304
    }
1✔
2305

2306
    @Override
2307
    public void proceed(final Expression expr) throws TerminatedException {
2308
        getWatchDog().proceed(expr);
1✔
2309
    }
1✔
2310

2311
    @Override
2312
    public void proceed(final Expression expr, final MemTreeBuilder builder) throws TerminatedException {
2313
        getWatchDog().proceed(expr, builder);
1✔
2314
    }
1✔
2315

2316
    @Override
2317
    public void setWatchDog(final XQueryWatchDog watchdog) {
2318
        this.watchdog = watchdog;
1✔
2319
    }
1✔
2320

2321
    @Override
2322
    public XQueryWatchDog getWatchDog() {
2323
        return watchdog;
1✔
2324
    }
2325

2326
    private static final MemTreeBuilder NULL_DOCUMENT_BUILDER = new MemTreeBuilder((Expression) null);
1✔
2327

2328
    @Override
2329
    public void pushDocumentContext() {
2330
        if (documentBuilder == null) {
1✔
2331
            fragmentStack.push(NULL_DOCUMENT_BUILDER);
1✔
2332
        } else {
1✔
2333
            fragmentStack.push(documentBuilder);
1✔
2334
            resetDocumentBuilder();
1✔
2335
        }
2336
    }
1✔
2337

2338
    @Override
2339
    public void popDocumentContext() {
2340
        if (!fragmentStack.isEmpty()) {
1!
2341
            final MemTreeBuilder prevBuilder = fragmentStack.pop();
1✔
2342
            if (prevBuilder == NULL_DOCUMENT_BUILDER) {
1✔
2343
                setDocumentBuilder(null);
1✔
2344
            } else {
1✔
2345
                setDocumentBuilder(prevBuilder);
1✔
2346
            }
2347
        }
2348
    }
1✔
2349

2350
    @Override
2351
    public void setBaseURI(final AnyURIValue uri) {
2352
        setBaseURI(uri, false);
1✔
2353
    }
1✔
2354

2355
    @Override
2356
    public void setBaseURI(final AnyURIValue uri, final boolean setInProlog) {
2357
        if (baseURISetInProlog) {
1!
2358
            return;
×
2359
        }
2360

2361
        // TODO(JL): the previous code hinted that null _should_ be treated differently
2362
        // baseURI = (uri == null) ? AnyURIValue.EMPTY_URI : uri;
2363
        baseURI = uri;
1✔
2364
        baseURISetInProlog = setInProlog;
1✔
2365
    }
1✔
2366

2367
    @Override
2368
    public void setModuleLoadPath(final String path) {
2369
        this.moduleLoadPath = path;
1✔
2370
    }
1✔
2371

2372
    @Override
2373
    public String getModuleLoadPath() {
2374
        return moduleLoadPath;
1✔
2375
    }
2376

2377
    @Override
2378
    public boolean isBaseURIDeclared() {
2379
        return baseURI != null && !baseURI.equals(AnyURIValue.EMPTY_URI);
1!
2380
    }
2381

2382
    @Override
2383
    public AnyURIValue getBaseURI() throws XPathException {
2384
        // the base URI in the static context is established according to the
2385
        // principles outlined in [RFC3986] Section 5.1—that is, it defaults
2386
        // first to the base URI of the encapsulating entity, then to the URI
2387
        // used to retrieve the entity, and finally to an implementation-defined
2388
        // default. If the URILiteral in the base URI declaration is a relative
2389
        // URI, then it is made absolute by resolving it with respect to this
2390
        // same hierarchy.
2391

2392
        // It is not intrinsically an error if this process fails to establish
2393
        // an absolute base URI; however, the base URI in the static context
2394
        // is then undefined, and any attempt to use its value may result in
2395
        // an error [err:XPST0001].
2396
//        if ((baseURI == null) || baseURI.equals(AnyURIValue.EMPTY_URI)) {
2397
//            //throw new XPathException(rootExpression, ErrorCodes.XPST0001, "Base URI of the static context  has not been assigned a value.");
2398
//            // We catch and resolve this to the XmlDbURI.ROOT_COLLECTION_URI
2399
//            // at least in DocumentImpl so maybe we should do it here./ljo
2400
//        }
2401
        return baseURI;
1✔
2402
    }
2403

2404
    @Override
2405
    public void setContextSequencePosition(final int pos, final Sequence sequence) {
2406
        contextPosition = pos;
1✔
2407
        contextSequence = sequence;
1✔
2408
    }
1✔
2409

2410
    @Override
2411
    public int getContextPosition() {
2412
        return contextPosition;
1✔
2413
    }
2414

2415
    @Override
2416
    public Sequence getContextSequence() {
2417
        return contextSequence;
1✔
2418
    }
2419

2420
    @Override
2421
    public void pushInScopeNamespaces() {
2422
        pushInScopeNamespaces(true);
1✔
2423
    }
1✔
2424

2425
    @Override
2426
    public void pushInScopeNamespaces(final boolean inherit) {
2427
        //TODO : push into an inheritedInScopeNamespaces HashMap... and return an empty HashMap
2428
        namespaceStack.push(inheritedInScopeNamespaces);
1✔
2429
        namespaceStack.push(inheritedInScopePrefixes);
1✔
2430
        namespaceStack.push(inScopeNamespaces);
1✔
2431
        namespaceStack.push(inScopePrefixes);
1✔
2432

2433
        //Current namespaces now become inherited just like the previous inherited ones
2434
        if (inherit) {
1✔
2435
            inheritedInScopeNamespaces = new HashMap<>(inheritedInScopeNamespaces);
1✔
2436
            inheritedInScopeNamespaces.putAll(inScopeNamespaces);
1✔
2437
            inheritedInScopePrefixes = new HashMap<>(inheritedInScopePrefixes);
1✔
2438
            inheritedInScopePrefixes.putAll(inScopePrefixes);
1✔
2439
        } else {
1✔
2440
            inheritedInScopeNamespaces = new HashMap<>();
1✔
2441
            inheritedInScopePrefixes = new HashMap<>();
1✔
2442
        }
2443

2444
        //TODO : consider dynamic instanciation
2445
        inScopeNamespaces = new HashMap<>();
1✔
2446
        inScopePrefixes = new HashMap<>();
1✔
2447
    }
1✔
2448

2449
    @Override
2450
    public void popInScopeNamespaces() {
2451
        inScopePrefixes = namespaceStack.pop();
1✔
2452
        inScopeNamespaces = namespaceStack.pop();
1✔
2453
        inheritedInScopePrefixes = namespaceStack.pop();
1✔
2454
        inheritedInScopeNamespaces = namespaceStack.pop();
1✔
2455
    }
1✔
2456

2457
    @Override
2458
    public void pushNamespaceContext() {
2459
        final Map<String, String> m = new HashMap<>(staticNamespaces);
1✔
2460
        final Map<String, String> p = new HashMap<>(staticPrefixes);
1✔
2461
        namespaceStack.push(staticNamespaces);
1✔
2462
        namespaceStack.push(staticPrefixes);
1✔
2463
        staticNamespaces = m;
1✔
2464
        staticPrefixes = p;
1✔
2465
    }
1✔
2466

2467
    @Override
2468
    public void popNamespaceContext() {
2469
        staticPrefixes = namespaceStack.pop();
1✔
2470
        staticNamespaces = namespaceStack.pop();
1✔
2471
    }
1✔
2472

2473
    @Override
2474
    public LocalVariable markLocalVariables(final boolean newContext) {
2475
        if (newContext) {
1✔
2476
            if (lastVar == null) {
1✔
2477
                lastVar = new LocalVariable(QName.EMPTY_QNAME);
1✔
2478
            }
2479
            contextStack.push(lastVar);
1✔
2480
        }
2481
        variableStackSize++;
1✔
2482
        return lastVar;
1✔
2483
    }
2484

2485
    @Override
2486
    public void popLocalVariables(@Nullable final LocalVariable var) {
2487
        popLocalVariables(var, null);
1✔
2488
    }
1✔
2489

2490
    /**
2491
     * Restore the local variable stack to the position marked by variable var.
2492
     *
2493
     * @param var       only clear variables after this variable, or null
2494
     * @param resultSeq the result sequence
2495
     */
2496
    public void popLocalVariables(@Nullable final LocalVariable var, @Nullable final Sequence resultSeq) {
2497
        if (var != null) {
1✔
2498
            // clear all variables registered after var. they should be out of scope.
2499
            LocalVariable outOfScope = var.after;
1✔
2500
            while (outOfScope != null) {
1✔
2501
                if (outOfScope != var && !outOfScope.isClosureVar()) {
1!
2502
                    outOfScope.destroy(this, resultSeq);
1✔
2503
                }
2504
                outOfScope = outOfScope.after;
1✔
2505
            }
2506
            // reset the stack
2507
            var.after = null;
1✔
2508

2509
            if (!contextStack.isEmpty() && (var == contextStack.peek())) {
1✔
2510
                contextStack.pop();
1✔
2511
            }
2512
        }
2513
        lastVar = var;
1✔
2514
        variableStackSize--;
1✔
2515
    }
1✔
2516

2517
    /**
2518
     * Register a inline function using closure variables so it can be cleared
2519
     * after query execution.
2520
     *
2521
     * @param func an inline function definition using closure variables
2522
     */
2523
    void pushClosure(final UserDefinedFunction func) {
2524
        closures.add(func);
1✔
2525
    }
1✔
2526

2527
    @Override
2528
    public int getCurrentStackSize() {
2529
        return variableStackSize;
1✔
2530
    }
2531

2532
    @Override
2533
    public void functionStart(final FunctionSignature signature) {
2534
        callStack.push(signature);
1✔
2535
    }
1✔
2536

2537
    @Override
2538
    public void functionEnd() {
2539
        if (callStack.isEmpty()) {
1!
2540
            LOG.warn("Function call stack is empty, but XQueryContext.functionEnd() was called. This "
×
2541
                    + "could indicate a concurrency issue (shared XQueryContext?)");
2542
        } else {
×
2543
            callStack.pop();
1✔
2544
        }
2545
    }
1✔
2546

2547
    @Override
2548
    public boolean tailRecursiveCall(final FunctionSignature signature) {
2549
        // NOTE(AR) this should be improved further... eXist-db lacked any sort of proper escape analysis!
2550
        for (final FunctionSignature existingFunctionSignature : callStack) {
1✔
2551
            if (existingFunctionSignature == signature) {
1✔
2552
                return true;
1✔
2553
            }
2554
        }
2555
        return false;
1✔
2556
    }
2557

2558
    @Override
2559
    public @Nullable Module[] importModule(@Nullable String namespaceURI, @Nullable String prefix, @Nullable AnyURIValue[] locationHints) throws XPathException {
2560

2561
        if (XML_NS_PREFIX.equals(prefix) || XMLNS_ATTRIBUTE.equals(prefix)) {
1✔
2562
            throw new XPathException(rootExpression, ErrorCodes.XQST0070,
1✔
2563
                    "The prefix declared for a module import must not be 'xml' or 'xmlns'.");
1✔
2564
        }
2565

2566
        if (namespaceURI != null && namespaceURI.isEmpty()) {
1✔
2567
            throw new XPathException(rootExpression, ErrorCodes.XQST0088,
1✔
2568
                    "The first URILiteral in a module import must be of nonzero length.");
1✔
2569
        }
2570

2571
        @Nullable Module[] modules = null;
1✔
2572

2573
        if (namespaceURI != null) {
1✔
2574
            modules = getRootModules(namespaceURI);
1✔
2575
        }
2576

2577
        if (modules != null && modules.length > 0) {
1!
2578
            if (LOG.isDebugEnabled()) {
1!
2579
                LOG.debug("Module {} already present.", namespaceURI);
×
2580
            }
2581
            // Set locally to remember the dependency in case it was inherited.
2582
            setModules(namespaceURI, modules);
1✔
2583

2584
        } else {
1✔
2585
            // if location is not specified, try to resolve in expath repo
2586
            if ((locationHints == null || locationHints.length == 0) && namespaceURI != null) {
1!
2587
                final Module module = resolveInEXPathRepository(namespaceURI, prefix);
1✔
2588
                if (module != null) {
1✔
2589
                    modules = new Module[]{ module };
1✔
2590
                }
2591
            }
2592

2593
            if (modules == null || modules.length == 0) {
1!
2594

2595
                if ((locationHints == null || locationHints.length == 0) && namespaceURI != null) {
1!
2596
                    // check if there's a static mapping in the configuration
2597
                    final String moduleLocation = getModuleLocation(namespaceURI);
1✔
2598
                    if (moduleLocation != null) {
1✔
2599
                        locationHints = new AnyURIValue[]{ new AnyURIValue(moduleLocation) };
1✔
2600
                    }
2601

2602
                    if (locationHints == null || locationHints.length == 0) {
1✔
2603
                        locationHints = new AnyURIValue[] { new AnyURIValue(namespaceURI) };
1✔
2604
                    }
2605
                }
2606

2607
                for (int i = 0; i < locationHints.length; i++) {
1✔
2608
                    final Module module = importModuleFromLocation(namespaceURI, prefix, locationHints[i]);
1✔
2609
                    if (module != null) {
1✔
2610
                        if (modules == null) {
1✔
2611
                            modules = new Module[1];
1✔
2612
                        } else if (i >= modules.length) {
1!
2613
                            modules = Arrays.copyOf(modules, modules.length + 1);
1✔
2614
                        }
2615
                        modules[modules.length - 1] = module;
1✔
2616
                    }
2617
                }
2618

2619
            } // NOTE: expathrepo related, closes the EXPath else (if module != null)
2620
        }
2621

2622
        if (modules != null && modules.length > 0) {
1!
2623
            if (namespaceURI == null) {
1✔
2624
                namespaceURI = modules[0].getNamespaceURI();
1✔
2625
            }
2626
            if (prefix == null) {
1✔
2627
                prefix = modules[0].getDefaultPrefix();
1✔
2628
            }
2629
            declareNamespace(prefix, namespaceURI);
1✔
2630
        }
2631

2632
        return modules;
1✔
2633
    }
2634

2635
    protected @Nullable Module importModuleFromLocation(final String namespaceURI, @Nullable final String prefix, final AnyURIValue locationHint) throws XPathException {
2636
        String location = locationHint.toString();
1✔
2637

2638
        // is it a Java module?
2639
        if (location.startsWith(JAVA_URI_START)) {
1✔
2640
            location = location.substring(JAVA_URI_START.length());
1✔
2641
            return loadBuiltInModule(namespaceURI, location);
1✔
2642
        }
2643

2644
        // Is the module source stored in the database?
2645
        if (location.startsWith(XmldbURI.XMLDB_URI_PREFIX)
1✔
2646
                || ((location.indexOf(':') == -1) && moduleLoadPath.startsWith(XmldbURI.XMLDB_URI_PREFIX))) {
1✔
2647

2648
            try {
2649
                XmldbURI locationUri = XmldbURI.xmldbUriFor(location);
1✔
2650

2651
                if (moduleLoadPath.startsWith(XmldbURI.XMLDB_URI_PREFIX)) {
1✔
2652
                    final XmldbURI moduleLoadPathUri = XmldbURI.xmldbUriFor(moduleLoadPath);
1✔
2653
                    locationUri = moduleLoadPathUri.resolveCollectionPath(locationUri);
1✔
2654
                }
2655

2656
                try (final LockedDocument lockedSourceDoc = getBroker().getXMLResource(locationUri.toCollectionPathURI(), LockMode.READ_LOCK)) {
1✔
2657

2658
                    final DocumentImpl sourceDoc = lockedSourceDoc == null ? null : lockedSourceDoc.getDocument();
1✔
2659
                    if (sourceDoc == null) {
1✔
2660
                        throw moduleLoadException("Module location hint URI '"
1✔
2661
                                + location + "' does not refer to anything.", location);
1✔
2662
                    }
2663

2664
                    if (sourceDoc.getResourceType() != DocumentImpl.BINARY_FILE
1!
2665
                            || !"application/xquery".equals(sourceDoc.getMimeType())) {
1!
2666
                        throw moduleLoadException("Module location hint URI '"
×
2667
                                + location + "' does not refer to an XQuery.", location);
×
2668
                    }
2669

2670
                    final Source moduleSource = new DBSource(getBroker().getBrokerPool(), (BinaryDocument) sourceDoc, true);
1✔
2671
                    return compileOrBorrowModule(namespaceURI, prefix, location, moduleSource);
1✔
2672

2673
                } catch (final PermissionDeniedException e) {
×
2674
                    throw moduleLoadException("Permission denied to read module source from location hint URI '" + location + ".", location, e);
×
2675
                }
2676
            } catch (final URISyntaxException e) {
×
2677
                throw moduleLoadException("Invalid module location hint URI '" + location + "'.", location, e);
×
2678
            }
2679

2680
        }
2681

2682
        // No. Load from file or URL
2683
        final Source moduleSource;
2684
        try {
2685
            //TODO: use URIs to ensure proper resolution of relative locations
2686
            final String contextPath;
2687
            if (source instanceof FileSource) {
1✔
2688
                final Path sourcePath = ((FileSource) source).getPath();
1✔
2689
                contextPath = sourcePath.resolveSibling(moduleLoadPath).normalize().toString();
1✔
2690
            } else {
1✔
2691
                contextPath = moduleLoadPath;
1✔
2692
            }
2693

2694
            moduleSource = SourceFactory.getSource(getBroker(), contextPath, location, true);
1✔
2695
            if (moduleSource == null) {
1✔
2696
                throw moduleLoadException("Source for module '" + namespaceURI + "' " +
1✔
2697
                        "not found module location hint URI '" + location + "'.", location);
1✔
2698
            }
2699
        } catch (final MalformedURLException e) {
×
2700
            throw moduleLoadException("Invalid module location hint URI '" + location + "'.", location, e);
×
2701
        } catch (final IOException e) {
×
2702
            throw moduleLoadException("Source for module '" + namespaceURI + "' could not be read, " +
×
2703
                    "module location hint URI '" + location + "'.", location, e);
×
2704
        } catch (final PermissionDeniedException e) {
×
2705
            throw moduleLoadException("Permission denied to read module source from location hint URI '" + location + ".", location, e);
×
2706
        }
2707

2708
        return compileOrBorrowModule(namespaceURI, prefix, location, moduleSource);
1✔
2709
    }
2710

2711
    protected XPathException moduleLoadException(final String message, final String moduleLocation)
2712
            throws XPathException {
2713
        return new XPathException(rootExpression, ErrorCodes.XQST0059, message, new ValueSequence(new StringValue(moduleLocation)));
1✔
2714
    }
2715

2716
    protected XPathException moduleLoadException(final String message, final String moduleLocation, final Exception e)
2717
            throws XPathException {
2718
        return new XPathException(rootExpression, ErrorCodes.XQST0059, message, new ValueSequence(new StringValue(moduleLocation)), e);
×
2719
    }
2720

2721
    @SuppressWarnings("unchecked")
2722
    @Override
2723
    public String getModuleLocation(final String namespaceURI) {
2724
        final Map<String, String> moduleMap =
1✔
2725
                (Map<String, String>) getConfiguration().getProperty(PROPERTY_STATIC_MODULE_MAP);
1✔
2726
        return moduleMap.get(namespaceURI);
1✔
2727
    }
2728

2729
    @SuppressWarnings("unchecked")
2730
    @Override
2731
    public Iterator<String> getMappedModuleURIs() {
2732
        final Map<String, String> moduleMap =
×
2733
                (Map<String, String>) getConfiguration().getProperty(PROPERTY_STATIC_MODULE_MAP);
×
2734
        return moduleMap.keySet().iterator();
×
2735
    }
2736

2737
    /**
2738
     * Compile of borrow an already compile module from the cache.
2739
     *
2740
     * @param namespaceURI the module namespace URI
2741
     * @param prefix the module namespace prefix
2742
     * @param location the location hint
2743
     * @param source the source for the module
2744
     *
2745
     * @return the module or null
2746
     *
2747
     * @throws XPathException if the module could not be loaded (XQST0059) or compiled (XPST0003)
2748
     */
2749
    private ExternalModule compileOrBorrowModule(final String namespaceURI, final String prefix, final String location,
2750
                                                 final Source source) throws XPathException {
2751
        final ExternalModule module = compileModule(namespaceURI, prefix, location, source);
1✔
2752
        if (module != null) {
1✔
2753
            addModule(module.getNamespaceURI(), module);
1✔
2754
            declareModuleVars(module);
1✔
2755
        }
2756
        return module;
1✔
2757
    }
2758

2759
    /**
2760
     * Compile an XQuery Module
2761
     *
2762
     * @param namespaceURI the namespace URI of the module.
2763
     * @param prefix       the namespace prefix of the module.
2764
     * @param location     the location of the module
2765
     * @param source       the source of the module.
2766
     * @return The compiled module, or null if the source is not a module
2767
     * @throws XPathException if the module could not be loaded (XQST0059) or compiled (XPST0003)
2768
     */
2769
    private @Nullable ExternalModule compileModule(String namespaceURI, final String prefix, final String location,
2770
                                                   final Source source) throws XPathException {
2771
        if (LOG.isDebugEnabled()) {
1!
2772
            LOG.debug("Loading module from {}", location);
×
2773
        }
2774

2775
        try (final Reader reader = source.getReader()) {
1✔
2776
            if (reader == null) {
1✔
2777
                throw moduleLoadException("failed to load module: '" + namespaceURI + "' from: " +
1✔
2778
                        "'" + source + "', location: '" + location + "'. Source not found. ", location);
1✔
2779
            }
2780

2781
            if (namespaceURI == null) {
1✔
2782
                final QName qname = source.isModule();
1✔
2783
                if (qname == null) {
1✔
2784
                    return null;
1✔
2785
                }
2786
                namespaceURI = qname.getNamespaceURI();
1✔
2787
            }
2788

2789
            final ExternalModuleImpl modExternal = new ExternalModuleImpl(namespaceURI, prefix);
1✔
2790

2791
            // NOTE(AR) this is needed to support cyclic imports in XQuery 3.1, see: https://github.com/eXist-db/exist/pull/4996
2792
            addModule(namespaceURI, modExternal);
1✔
2793
            addModuleVertex(new ModuleVertex(namespaceURI, location));
1✔
2794

2795
            final XQueryContext modContext = new ModuleContext(this, namespaceURI, prefix, location);
1✔
2796
            modExternal.setContext(modContext);
1✔
2797
            final XQueryLexer lexer = new XQueryLexer(modContext, reader);
1✔
2798
            final XQueryParser parser = new XQueryParser(lexer);
1✔
2799
            final XQueryTreeParser astParser = new XQueryTreeParser(modContext, modExternal);
1✔
2800

2801
            try {
2802
                parser.xpath();
1✔
2803

2804
                if (parser.foundErrors()) {
1!
2805
                    if (LOG.isDebugEnabled()) {
×
2806
                        LOG.debug(parser.getErrorMessage());
×
2807
                    }
2808
                    throw new XPathException(rootExpression, ErrorCodes.XPST0003, "error found while loading module from " + location + ": " + parser.getErrorMessage());
×
2809
                }
2810

2811
                final AST ast = parser.getAST();
1✔
2812

2813
                final PathExpr path = new PathExpr(modContext);
1✔
2814
                astParser.xpath(ast, path);
1✔
2815

2816
                if (astParser.foundErrors()) {
1!
2817
                    throw new XPathException(rootExpression, ErrorCodes.XPST0003, "error found while loading module from " + location + ": " + astParser.getErrorMessage(), astParser.getLastException());
×
2818
                }
2819

2820
                modExternal.setRootExpression(path);
1✔
2821

2822
                if (namespaceURI != null && !modExternal.getNamespaceURI().equals(namespaceURI)) {
1!
2823
                    throw new XPathException(rootExpression, ErrorCodes.XQST0059, "namespace URI declared by module (" + modExternal.getNamespaceURI() + ") does not match namespace URI in import statement, which was: " + namespaceURI);
1✔
2824
                }
2825

2826
                // Set source information on module context
2827
//            String sourceClassName = source.getClass().getName();
2828
//            modContext.setSourceKey(source.getKey().toString());
2829
                // Extract the source type from the classname by removing the package prefix and the "Source" suffix
2830
//            modContext.setSourceType( sourceClassName.substring( 17, sourceClassName.length() - 6 ) );
2831

2832
                modExternal.setSource(source);
1✔
2833
                modContext.setSource(source);
1✔
2834
                modExternal.setIsReady(true);
1✔
2835
                return modExternal;
1✔
2836
            } catch (final RecognitionException e) {
×
2837
                throw new XPathException(e.getLine(), e.getColumn(), ErrorCodes.XPST0003, "error found while loading module from " + location + ": " + e.getMessage());
×
2838
            } catch (final TokenStreamException e) {
×
2839
                throw new XPathException(rootExpression, ErrorCodes.XPST0003, "error found while loading module from " + location + ": " + e.getMessage(), e);
×
2840
            } catch (final XPathException e) {
1✔
2841
                e.prependMessage("Error while loading module " + location + ": ");
1✔
2842
                throw e;
1✔
2843
            }
2844
        } catch (final IOException e) {
×
2845
            throw moduleLoadException("IO exception while loading module '" + namespaceURI + "'" + " from '" + source + "'", location, e);
×
2846
        }
2847
    }
2848

2849
    private void declareModuleVars(final Module module) {
2850
        final String moduleNS = module.getNamespaceURI();
1✔
2851

2852
        for (final Iterator<Variable> i = globalVariables.values().iterator(); i.hasNext(); ) {
1✔
2853
            final Variable var = i.next();
1✔
2854

2855
            if (moduleNS.equals(var.getQName().getNamespaceURI())) {
1✔
2856
                module.declareVariable(var);
1✔
2857
                i.remove();
1✔
2858
            }
2859
        }
2860
    }
1✔
2861

2862
    @Override
2863
    public void addForwardReference(final FunctionCall call) {
2864
        forwardReferences.add(call);
1✔
2865
    }
1✔
2866

2867
    @Override
2868
    public void resolveForwardReferences() throws XPathException {
2869
        while (!forwardReferences.isEmpty()) {
1✔
2870
            final FunctionCall call = forwardReferences.pop();
1✔
2871
            final UserDefinedFunction func = call.getContext().resolveFunction(call.getQName(), call.getArgumentCount());
1✔
2872

2873
            if (func == null) {
1✔
2874
                throw new XPathException(call, ErrorCodes.XPST0017, "Call to undeclared function: " + call.getQName().getStringValue());
1✔
2875
            }
2876
            call.resolveForwardReference(func);
1✔
2877
        }
2878
    }
1✔
2879

2880
    /**
2881
     * Get environment variables. The variables shall not change
2882
     * during execution of query.
2883
     *
2884
     * @return Map of environment variables
2885
     */
2886
    public Map<String, String> getEnvironmentVariables() {
2887
        if (envs == null) {
×
2888
            envs = System.getenv();
×
2889
        }
2890
        return envs;
×
2891
    }
2892

2893
    /**
2894
     * Gets the Effective user
2895
     * i.e. the user that the query is executing as
2896
     *
2897
     * @return The Effective User
2898
     */
2899
    public Subject getEffectiveUser() {
2900
        return getBroker().getCurrentSubject();
1✔
2901
    }
2902

2903
    /**
2904
     * Gets the Real User
2905
     * i.e. the user that initiated execution of the query
2906
     * Note this is not necessarily the same as the user that the
2907
     * query is executing as
2908
     *
2909
     * @return The Real User
2910
     * @see org.exist.xquery.XQueryContext#getEffectiveUser()
2911
     */
2912
    public Subject getRealUser() {
2913
        return realUser;
1✔
2914
    }
2915

2916
    private void setRealUser(final Subject realUser) {
2917
        this.realUser = realUser;
1✔
2918
    }
1✔
2919

2920
    /**
2921
     * Get a static decimal format.
2922
     *
2923
     * @param qnDecimalFormat the name of the decimal format, or null for the UNNAMED format.
2924
     * @return the decimal format, or null if there is no format matching the name
2925
     */
2926
    public @Nullable DecimalFormat getStaticDecimalFormat(@Nullable final QName qnDecimalFormat) {
2927
        final QName format = qnDecimalFormat == null ? UNNAMED_DECIMAL_FORMAT : qnDecimalFormat;
1!
2928
        return staticDecimalFormats.get(format);
1✔
2929
    }
2930

2931
    /**
2932
     * Set a static decimal format.
2933
     *
2934
     * @param qnDecimalFormat the name of the decimal format
2935
     * @param decimalFormat the decimal format
2936
     */
2937
    public void setStaticDecimalFormat(final QName qnDecimalFormat, final DecimalFormat decimalFormat) {
2938
        staticDecimalFormats.put(qnDecimalFormat, decimalFormat);
×
2939
    }
×
2940

2941
    public Map<String, Sequence> getCachedUriCollectionResults() {
2942
        return cachedUriCollectionResults;
1✔
2943
    }
2944

2945
    /**
2946
     * Add a reference to an additional XQuery Context that
2947
     * was created by the XQuery (owning this XQuery Context)
2948
     * dynamically importing, compiling, and/or evaluating modules.
2949
     *
2950
     * NOTE(AR) - This is needed to ensure that these "imported contexts" are
2951
     * also correctly reset and cleaned up when this XQuery is finished.
2952
     *
2953
     * @param importedContext the dynamically created content for importing a module.
2954
     */
2955
    public void addImportedContext(final XQueryContext importedContext) {
2956
        if (importedContexts == null) {
1✔
2957
            importedContexts = new HashSet<>();
1✔
2958
            importedContextsCleanupTasksFns = new ArrayList<>();
1✔
2959
        }
2960
        importedContexts.add(importedContext);
1✔
2961
        importedContextsCleanupTasksFns.add(importedContext::runCleanupTasks);
1✔
2962
    }
1✔
2963

2964
    /**
2965
     * Save state
2966
     */
2967
    private class SavedState {
1✔
2968
        private Object2ObjectOpenHashMap<String, Module[]> modulesSaved = null;
1✔
2969
        private Object2ObjectOpenHashMap<String, Module[]> allModulesSaved = null;
1✔
2970
        private Map<String, String> staticNamespacesSaved = null;
1✔
2971
        private Map<String, String> staticPrefixesSaved = null;
1✔
2972

2973
        void save() {
2974
            if (modulesSaved != null) {
1✔
2975
                return;
1✔
2976
            }
2977

2978
            modulesSaved = new Object2ObjectOpenHashMap<>(modules.size(), Hash.VERY_FAST_LOAD_FACTOR);
1✔
2979
            for (final Object2ObjectMap.Entry<String, Module[]> entry : Object2ObjectMaps.fastIterable(modules)) {
1✔
2980
                final Module[] mods = entry.getValue();
1✔
2981
                modulesSaved.put(entry.getKey(), Arrays.copyOf(mods, mods.length));
1✔
2982
            }
2983

2984
            allModulesSaved = new Object2ObjectOpenHashMap<>(allModules.size());
1✔
2985
            for (final Object2ObjectMap.Entry<String, Module[]> entry : Object2ObjectMaps.fastIterable(allModules)) {
1✔
2986
                final Module[] mods = entry.getValue();
1✔
2987
                allModulesSaved.put(entry.getKey(), Arrays.copyOf(mods, mods.length));
1✔
2988
            }
2989

2990
            staticNamespacesSaved = new HashMap<>(staticNamespaces);
1✔
2991
            staticPrefixesSaved = new HashMap<>(staticPrefixes);
1✔
2992
        }
1✔
2993

2994
        void restore() {
2995
            if (modulesSaved == null) {
1✔
2996
                return;
1✔
2997
            }
2998

2999
            modules = modulesSaved;
1✔
3000
            modulesSaved = null;
1✔
3001
            allModules = allModulesSaved;
1✔
3002
            allModulesSaved = null;
1✔
3003
            staticNamespaces = staticNamespacesSaved;
1✔
3004
            staticNamespacesSaved = null;
1✔
3005
            staticPrefixes = staticPrefixesSaved;
1✔
3006
            staticPrefixesSaved = null;
1✔
3007
        }
1✔
3008
    }
3009

3010
    /**
3011
     * Before a dynamic import, make sure relevant parts of the current context a saved
3012
     * to the stack. This is important for util:import-module. The context will be restored
3013
     * during {@link #reset()}.
3014
     */
3015
    public void saveState() {
3016
        savedState.save();
1✔
3017
    }
1✔
3018

3019
    @Override
3020
    public boolean optimizationsEnabled() {
3021
        return enableOptimizer;
1✔
3022
    }
3023

3024
    @Override
3025
    public void addOption(final String name, final String value) throws XPathException {
3026
        if (staticOptions == null) {
1✔
3027
            staticOptions = new ArrayList<>();
1✔
3028
        }
3029
        addOption(staticOptions, name, value);
1✔
3030
    }
1✔
3031

3032
    @Override
3033
    public void addDynamicOption(final String name, final String value) throws XPathException {
3034
        if (dynamicOptions == null) {
×
3035
            dynamicOptions = new ArrayList<>();
×
3036
        }
3037
        addOption(dynamicOptions, name, value);
×
3038
    }
×
3039

3040
    private void addOption(final List<Option> options, final String name, final String value) throws XPathException {
3041
        final QName qn;
3042
        try {
3043
            qn = QName.parse(this, name, defaultFunctionNamespace);
1✔
3044
        } catch (final QName.IllegalQNameException e) {
1✔
3045
            throw new XPathException(rootExpression, ErrorCodes.XPST0081, "No namespace defined for prefix " + name);
×
3046
        }
3047

3048
        final Option option = new Option(rootExpression, qn, value);
1✔
3049
        // if the option exists, remove it first
3050
        options.remove(option);
1✔
3051
        //add option
3052
        options.add(option);
1✔
3053

3054
        // check predefined options
3055
        if (Option.PROFILE_QNAME.compareTo(qn) == 0) {
1!
3056
            // configure profiling
3057
            profiler.configure(option);
×
3058

3059
        } else if (Option.TIMEOUT_QNAME.compareTo(qn) == 0) {
1!
3060
            watchdog.setTimeoutFromOption(option);
×
3061

3062
        } else if (Option.OUTPUT_SIZE_QNAME.compareTo(qn) == 0) {
1✔
3063
            watchdog.setMaxNodesFromOption(option);
1✔
3064

3065
        } else if (Option.OPTIMIZE_QNAME.compareTo(qn) == 0) {
1✔
3066
            final String[] params = option.tokenizeContents();
1✔
3067
            if (params.length > 0) {
1!
3068
                final String[] param = Option.parseKeyValuePair(params[0]);
1✔
3069
                if (param != null && "enable".equals(param[0])) {
1!
3070
                    enableOptimizer = "yes".equals(param[1]);
1✔
3071
                }
3072
            }
3073
        }
1✔
3074
        //TODO : not sure how these 2 options might/have to be related
3075
        else if (Option.OPTIMIZE_IMPLICIT_TIMEZONE.compareTo(qn) == 0) {
1✔
3076
            //TODO : error check
3077
            final Duration duration = TimeUtils.getInstance().newDuration(option.getContents());
1✔
3078
            implicitTimeZone = new SimpleTimeZone((int) duration.getTimeInMillis(new Date()), "XQuery context");
1✔
3079

3080
        } else if (Option.CURRENT_DATETIME.compareTo(qn) == 0) {
1✔
3081
            //TODO : error check
3082
            final DateTimeValue dtv = new DateTimeValue(option.getContents());
1✔
3083
            calendar = (XMLGregorianCalendar) dtv.calendar.clone();
1✔
3084
        }
3085
    }
1✔
3086

3087
    @Override
3088
    public Option getOption(final QName qname) {
3089
        if (dynamicOptions != null) {
1!
3090
            for (final Option option : dynamicOptions) {
×
3091
                if (qname.equals(option.getQName())) {
×
3092
                    return option;
×
3093
                }
3094
            }
3095
        }
3096

3097
        if (staticOptions != null) {
1✔
3098
            for (final Option option : staticOptions) {
1✔
3099
                if (qname.equals(option.getQName())) {
1✔
3100
                    return option;
1✔
3101
                }
3102
            }
3103
        }
3104

3105
        return null;
1✔
3106
    }
3107

3108
    @Override
3109
    public Pragma getPragma(final String name, final String contents) throws XPathException {
3110
        final QName qname;
3111
        try {
3112
            qname = QName.parse(this, name);
1✔
3113
        } catch (final QName.IllegalQNameException e) {
1✔
3114
            throw new XPathException(rootExpression, ErrorCodes.XPST0081, "No namespace defined for prefix " + name);
×
3115
        }
3116

3117
        final String ns = qname.getNamespaceURI();
1✔
3118
        if (ns.isEmpty()) {
1!
3119
            throw new XPathException(rootExpression, ErrorCodes.XPST0081,
×
3120
                    "The namespace URI for Pragma ('" + name + "') is empty");
×
3121
        }
3122

3123
        if (!Namespaces.EXIST_NS.equals(ns)) {
1!
3124
            // TODO(JL): should we throw or a least warn here?
3125
            return null;
×
3126
        }
3127

3128
        final String sanitizedContents = StringValue.trimWhitespace(contents);
1✔
3129

3130
        return switch(qname.getLocalPart()) {
1!
3131
            case Optimize.OPTIMIZE_PRAGMA_LOCAL_NAME -> new Optimize(rootExpression, this, qname, sanitizedContents, true);
1✔
3132
            case TimePragma.TIME_PRAGMA_LOCAL_NAME, TimePragma.DEPRECATED_TIMER_PRAGMA_LOCAL_NAME -> new TimePragma(rootExpression, qname, sanitizedContents);
×
3133
            case ProfilePragma.PROFILING_PRAGMA_LOCAL_NAME -> new ProfilePragma(rootExpression, qname, sanitizedContents);
×
3134
            case ForceIndexUse.FORCE_INDEX_USE_PRAGMA_LOCAL_NAME -> new ForceIndexUse(rootExpression, qname, sanitizedContents);
1✔
3135
            case NoIndexPragma.NO_INDEX_PRAGMA_LOCAL_NAME -> new NoIndexPragma(rootExpression, qname, sanitizedContents);
×
3136
            default -> null;
×
3137
        };
3138
    }
3139

3140
    @Override
3141
    public DocumentImpl storeTemporaryDoc(final org.exist.dom.memtree.DocumentImpl doc) throws XPathException {
3142
        try {
3143
            final DocumentImpl targetDoc = getBroker().storeTempResource(doc);
×
3144

3145
            if (targetDoc == null) {
×
3146
                throw new XPathException(rootExpression, "Internal error: failed to store temporary doc fragment");
×
3147
            }
3148
            LOG.warn("Stored: {}: {}", targetDoc.getDocId(), targetDoc.getURI(), new Throwable());
×
3149
            return targetDoc;
×
3150
        } catch (final EXistException | LockException | PermissionDeniedException e) {
×
3151
            throw new XPathException(rootExpression, TEMP_STORE_ERROR, e);
×
3152
        }
3153
    }
3154

3155
    @Override
3156
    public void setAttribute(final String attribute, final Object value) {
3157
        attributes.put(attribute, value);
1✔
3158
    }
1✔
3159

3160
    @Override
3161
    public Object getAttribute(final String attribute) {
3162
        return attributes.get(attribute);
1✔
3163
    }
3164

3165
    /**
3166
     * Load the default prefix/namespace mappings table and set up internal functions.
3167
     *
3168
     * @param config the configuration if available
3169
     */
3170
    @SuppressWarnings("unchecked")
3171
    void loadDefaults(@Nullable final Configuration config) {
3172
        loadDefaultNS();
1✔
3173

3174
        if (config == null) {
1✔
3175
            return;
1✔
3176
        }
3177

3178
        // Switch: enable optimizer
3179
        final String queryRewritingOption = config.getProperty(PROPERTY_ENABLE_QUERY_REWRITING, "no");
1✔
3180
        this.enableOptimizer = "yes".equals(queryRewritingOption);
1✔
3181

3182
        // Switch: Backward compatibility
3183
        final String backwardsCompatOption = config.getProperty(PROPERTY_XQUERY_BACKWARD_COMPATIBLE, "yes");
1✔
3184
        this.backwardsCompatible = "yes".equals(backwardsCompatOption);
1✔
3185

3186
        // Switch: raiseErrorOnFailedRetrieval
3187
        this.raiseErrorOnFailedRetrieval =
1✔
3188
                config.getProperty(PROPERTY_XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL, Boolean.FALSE);
1✔
3189

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

3194
        if (builtInModules == null) {
1✔
3195
            return;
1✔
3196
        }
3197

3198
        // Iterate on all map entries
3199
        for (final Map.Entry<String, Class<Module>> entry : builtInModules.entrySet()) {
1✔
3200
            addBuiltInModuleOrDeclareNamespace(config, entry);
1✔
3201
        }
3202
    }
1✔
3203

3204
    @SuppressWarnings("unchecked")
3205
    private void addBuiltInModuleOrDeclareNamespace(final Configuration config, final Map.Entry<String, Class<Module>> entry) {
3206
        final String namespaceURI = entry.getKey();
1✔
3207
        // first check if the module has already been loaded in the parent context
3208
        final Module[] modules = getModules(namespaceURI);
1✔
3209

3210
        // we have to instantiate the module class
3211
        final Class<Module> moduleClass = entry.getValue();
1✔
3212
        Module foundModule = null;
1✔
3213
        if (modules != null) {
1✔
3214
            for (final Module module : modules) {
1!
3215
                if (moduleClass.equals(module.getClass())) {
1!
3216
                    foundModule = module;
1✔
3217
                    break;
1✔
3218
                }
3219
            }
3220
        }
3221

3222
        if (foundModule == null) {
1✔
3223
            // Module does not exist yet, instantiate
3224
            instantiateModule(namespaceURI, moduleClass,
1✔
3225
                    (Map<String, Map<String, List<? extends Object>>>) config.getProperty(PROPERTY_MODULE_PARAMETERS));
1✔
3226
            return;
1✔
3227
        }
3228

3229
        if (getPrefixForURI(namespaceURI) == null && !foundModule.getDefaultPrefix().isEmpty()) {
1!
3230
            // make sure the namespaces of default modules are known,
3231
            // even if they were imported in a parent context
3232
            try {
3233
                declareNamespace(foundModule.getDefaultPrefix(), foundModule.getNamespaceURI());
1✔
3234

3235
            } catch (final XPathException e) {
1✔
3236
                LOG.warn("Internal error while loading default modules: {}", e.getMessage(), e);
×
3237
            }
3238
        }
3239
    }
1✔
3240

3241
    /**
3242
     * Load default namespaces, e.g. xml, xsi, xdt, fn, local, exist and dbgp.
3243
     */
3244
    private void loadDefaultNS() {
3245
        try {
3246
            // default namespaces
3247
            staticNamespaces.put(XML_NS_PREFIX, XML_NS);
1✔
3248
            staticPrefixes.put(XML_NS, XML_NS_PREFIX);
1✔
3249
            declareNamespace("xs", Namespaces.SCHEMA_NS);
1✔
3250
            declareNamespace("xsi", Namespaces.SCHEMA_INSTANCE_NS);
1✔
3251

3252
            //required for backward compatibility
3253
            declareNamespace("xdt", Namespaces.XPATH_DATATYPES_NS);
1✔
3254
            declareNamespace("fn", Namespaces.XPATH_FUNCTIONS_NS);
1✔
3255
            declareNamespace("local", Namespaces.XQUERY_LOCAL_NS);
1✔
3256
            declareNamespace(Namespaces.W3C_XQUERY_XPATH_ERROR_PREFIX, Namespaces.W3C_XQUERY_XPATH_ERROR_NS);
1✔
3257

3258
            //*not* as standard NS
3259
            declareNamespace(Namespaces.EXIST_NS_PREFIX, Namespaces.EXIST_NS);
1✔
3260
            declareNamespace(Namespaces.EXIST_JAVA_BINDING_NS_PREFIX, Namespaces.EXIST_JAVA_BINDING_NS);
1✔
3261
            declareNamespace(Namespaces.EXIST_XQUERY_XPATH_ERROR_PREFIX, Namespaces.EXIST_XQUERY_XPATH_ERROR_NS);
1✔
3262

3263
            //TODO : include "err" namespace ?
3264
            declareNamespace("dbgp", Debuggee.NAMESPACE_URI);
1✔
3265

3266
        } catch (final XPathException e) {
1✔
3267
            //ignored because it should never happen
3268
            if (LOG.isDebugEnabled()) {
×
3269
                LOG.debug(e);
×
3270
            }
3271
        }
3272
    }
1✔
3273

3274
    @Override
3275
    public void registerUpdateListener(final UpdateListener listener) {
3276
        if (updateListener == null) {
1✔
3277
            updateListener = new ContextUpdateListener();
1✔
3278
            final DBBroker broker = getBroker();
1✔
3279
            broker.getBrokerPool().getNotificationService().subscribe(updateListener);
1✔
3280
        }
3281
        updateListener.addListener(listener);
1✔
3282
    }
1✔
3283

3284
    protected void clearUpdateListeners() {
3285
        if (updateListener != null) {
1✔
3286
            final DBBroker broker = getBroker();
1✔
3287
            broker.getBrokerPool().getNotificationService().unsubscribe(updateListener);
1✔
3288
            updateListener = null;
1✔
3289
        }
3290
    }
1✔
3291

3292
    @Override
3293
    public void checkOptions(final Properties properties) throws XPathException {
3294
        checkLegacyOptions(properties);
1✔
3295
        if (dynamicOptions != null) {
1!
3296
            for (final Option option : dynamicOptions) {
×
3297
                if (Namespaces.XSLT_XQUERY_SERIALIZATION_NS.equals(option.getQName().getNamespaceURI())) {
×
3298
                    SerializerUtils.setProperty(option.getQName().getLocalPart(), option.getContents(), properties,
×
3299
                            inScopeNamespaces::get);
×
3300
                }
3301
            }
3302
        }
3303

3304
        if (staticOptions != null) {
1✔
3305
            for (final Option option : staticOptions) {
1✔
3306
                if (Namespaces.XSLT_XQUERY_SERIALIZATION_NS.equals(option.getQName().getNamespaceURI())
1✔
3307
                        && !properties.containsKey(option.getQName().getLocalPart())) {
1✔
3308
                    SerializerUtils.setProperty(option.getQName().getLocalPart(), option.getContents(), properties,
1✔
3309
                            inScopeNamespaces::get);
1✔
3310
                }
3311
            }
3312
        }
3313
    }
1✔
3314

3315
    /**
3316
     * Legacy method to check serialization properties set via option exist:serialize.
3317
     *
3318
     * @param properties the serialization properties
3319
     * @throws XPathException if there is an unknown serialization property
3320
     */
3321
    private void checkLegacyOptions(final Properties properties) throws XPathException {
3322
        final Option pragma = getOption(Option.SERIALIZE_QNAME);
1✔
3323
        if (pragma == null) {
1✔
3324
            return;
1✔
3325
        }
3326

3327
        final String[] contents = pragma.tokenizeContents();
1✔
3328

3329
        for (final String content : contents) {
1✔
3330
            final String[] pair = Option.parseKeyValuePair(content);
1✔
3331

3332
            if (pair == null) {
1!
3333
                throw new XPathException(rootExpression, "Unknown parameter found in " + pragma.getQName().getStringValue()
×
3334
                        + ": '" + content + "'");
×
3335
            }
3336

3337
            if (LOG.isDebugEnabled()) {
1!
3338
                LOG.debug("Setting serialization property from pragma: {} = {}", pair[0], pair[1]);
×
3339
            }
3340

3341
            properties.setProperty(pair[0], pair[1]);
1✔
3342
        }
3343
    }
1✔
3344

3345
    @Override
3346
    public void setDebuggeeJoint(final DebuggeeJoint joint) {
3347
        //XXX: if (debuggeeJoint != null) ???
3348
        debuggeeJoint = joint;
×
3349
    }
×
3350

3351
    @Override
3352
    public DebuggeeJoint getDebuggeeJoint() {
3353
        return debuggeeJoint;
×
3354
    }
3355

3356
    @Override
3357
    public boolean isDebugMode() {
3358
        return debuggeeJoint != null && isVarDeclared(Debuggee.SESSION);
×
3359
    }
3360

3361
    @Override
3362
    public boolean requireDebugMode() {
3363
        return isVarDeclared(Debuggee.SESSION);
1✔
3364
    }
3365

3366
    private Deque<BinaryValue> binaryValueInstances;
3367

3368
    void enterEnclosedExpr() {
3369
        if (binaryValueInstances == null) {
1✔
3370
            return;
1✔
3371
        }
3372
        final Iterator<BinaryValue> it = binaryValueInstances.descendingIterator();
1✔
3373
        while (it.hasNext()) {
1✔
3374
            it.next().incrementSharedReferences();
1✔
3375
        }
3376
    }
1✔
3377

3378
    void exitEnclosedExpr() {
3379
        if (binaryValueInstances == null) {
1✔
3380
            return;
1✔
3381
        }
3382
        final Iterator<BinaryValue> it = binaryValueInstances.iterator();
1✔
3383
        final List<BinaryValue> destroyable = new ArrayList<>();
1✔
3384
        while (it.hasNext()) {
1✔
3385
            try {
3386
                final BinaryValue bv = it.next();
1✔
3387
                bv.close(); // really just decrements a reference
1✔
3388
                if (bv.isClosed()) {
1✔
3389
                    destroyable.add(bv);
1✔
3390
                }
3391
            } catch (final IOException e) {
1✔
3392
                LOG.warn("Unable to close binary reference on exiting enclosed expression: {}", e.getMessage(), e);
×
3393
            }
3394
        }
3395

3396
        // eagerly cleanup those BinaryValues that are not used outside the EnclosedExpr (to release memory)
3397
        for (final BinaryValue bvd : destroyable) {
1✔
3398
            binaryValueInstances.remove(bvd);
1✔
3399
        }
3400
    }
1✔
3401

3402
    @Override
3403
    public void registerBinaryValueInstance(final BinaryValue binaryValue) {
3404
        if (binaryValueInstances == null) {
1✔
3405
            binaryValueInstances = new ArrayDeque<>();
1✔
3406
        }
3407

3408
        if (cleanupTasks.isEmpty() || cleanupTasks.stream().noneMatch(ct -> ct instanceof BinaryValueCleanupTask)) {
1!
3409
            cleanupTasks.add(new BinaryValueCleanupTask());
1✔
3410
        }
3411

3412
        binaryValueInstances.push(binaryValue);
1✔
3413
    }
1✔
3414

3415
    /**
3416
     * Cleanup Task which is responsible for relasing the streams
3417
     * of any {@link BinaryValue} which have been used during
3418
     * query execution
3419
     */
3420
    public static class BinaryValueCleanupTask implements CleanupTask {
1✔
3421
        @Override
3422
        public void cleanup(final XQueryContext context, final Predicate<Object> predicate) {
3423
            if (context.binaryValueInstances == null) {
1!
3424
                return;
×
3425
            }
3426
            final List<BinaryValue> removable = new ArrayList<>();
1✔
3427
            for (final BinaryValue bv : context.binaryValueInstances) {
1✔
3428
                try {
3429
                    if (predicate.test(bv)) {
1✔
3430
                        bv.close();
1✔
3431
                        removable.add(bv);
1✔
3432
                    }
3433
                } catch (final IOException e) {
1✔
3434
                    LOG.error("Unable to close binary value: {}", e.getMessage(), e);
×
3435
                }
3436
            }
3437

3438
            for (final BinaryValue bv : removable) {
1✔
3439
                context.binaryValueInstances.remove(bv);
1✔
3440
            }
3441
        }
1✔
3442
    }
3443

3444
    @Override
3445
    public String getCacheClass() {
3446
        return (String) getConfiguration().getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY);
1✔
3447
    }
3448

3449
    public void destroyBinaryValue(final BinaryValue value) {
3450
        if (binaryValueInstances != null) {
1✔
3451
            binaryValueInstances.remove(value);
1✔
3452
        }
3453
    }
1✔
3454

3455
    public void setXQueryVersion(int version) {
3456
        xqueryVersion = version;
1✔
3457
    }
1✔
3458

3459
    public int getXQueryVersion() {
3460
        return xqueryVersion;
1✔
3461
    }
3462

3463
    @Override
3464
    public Source getSource() {
3465
        return source;
1✔
3466
    }
3467

3468
    @Override
3469
    public void setSource(final Source source) {
3470
        this.source = source;
1✔
3471
    }
1✔
3472

3473
    @Override
3474
    public String getDefaultLanguage() {
3475
        return DefaultLanguage;
1✔
3476
    }
3477

3478
    /**
3479
     * NOTE: the {@link #unsubscribe()} method can be called
3480
     * from {@link org.exist.storage.NotificationService#unsubscribe(UpdateListener)}
3481
     * by another thread, so this class needs to be thread-safe.
3482
     */
3483
    @ThreadSafe
3484
    private static class ContextUpdateListener implements UpdateListener {
1✔
3485
        /*
3486
         * We use Concurrent safe data structures here, so that we don't have
3487
         * to block any calling threads.
3488
         *
3489
         * The AtomicReference enables us to quickly clear the listeners
3490
         * in #unsubscribe() and maintain happens-before integrity whilst
3491
         * unsubscribing them. The CopyOnWriteArrayList allows
3492
         * us to add listeners whilst iterating over a snapshot
3493
         * of existing iterators in other methods.
3494
         */
3495
        private final AtomicReference<List<UpdateListener>> listeners = new AtomicReference<>(new CopyOnWriteArrayList<>());
1✔
3496

3497
        private void addListener(final UpdateListener listener) {
3498
            listeners.get().add(listener);
1✔
3499
        }
1✔
3500

3501
        @Override
3502
        public void documentUpdated(final DocumentImpl document, final int event) {
3503
            listeners.get().forEach(listener -> listener.documentUpdated(document, event));
1✔
3504
        }
1✔
3505

3506
        @Override
3507
        public void unsubscribe() {
3508
            List<UpdateListener> prev = listeners.get();
1✔
3509
            final List<UpdateListener> next = new CopyOnWriteArrayList<>();
1✔
3510
            while (!listeners.compareAndSet(prev, next)) {
1!
3511
                prev = listeners.get();
×
3512
            }
3513

3514
            prev.forEach(UpdateListener::unsubscribe);
1✔
3515
        }
1✔
3516

3517
        @Override
3518
        public void nodeMoved(final NodeId oldNodeId, final NodeHandle newNode) {
3519
            listeners.get().forEach(listener -> listener.nodeMoved(oldNodeId, newNode));
1✔
3520
        }
1✔
3521

3522
        @Override
3523
        public void debug() {
3524
            if (LOG.isDebugEnabled()) {
×
3525
                LOG.debug("XQueryContext: {} document update listeners", listeners.get().size());
×
3526
            }
3527

3528
            listeners.get().forEach(UpdateListener::debug);
×
3529
        }
×
3530
    }
3531

3532
    private final List<CleanupTask> cleanupTasks = new ArrayList<>();
1✔
3533

3534
    public void registerCleanupTask(final CleanupTask cleanupTask) {
3535
        cleanupTasks.add(cleanupTask);
×
3536
    }
×
3537

3538
    public interface CleanupTask {
3539
        void cleanup(final XQueryContext context, final Predicate<Object> predicate);
3540
    }
3541

3542
    @Override
3543
    public void runCleanupTasks(final Predicate<Object> predicate) {
3544
        if (importedContextsCleanupTasksFns != null) {
1✔
3545
            for (final Consumer<Predicate<Object>> importedContextsCleanupTasksFn : importedContextsCleanupTasksFns) {
1✔
3546
                importedContextsCleanupTasksFn.accept(predicate);
1✔
3547
            }
3548
            importedContextsCleanupTasksFns = null;
1✔
3549
        }
3550

3551
        for (final CleanupTask cleanupTask : cleanupTasks) {
1✔
3552
            try {
3553
                cleanupTask.cleanup(this, predicate);
1✔
3554
            } catch (final Throwable t) {
1✔
3555
                LOG.error("Cleaning up XQueryContext: Ignoring: {}", t.getMessage(), t);
×
3556
            }
3557
        }
3558
        // now it is safe to clear the cleanup tasks list as we know they have run
3559
        // do not move this anywhere else
3560
        cleanupTasks.clear();
1✔
3561
    }
1✔
3562

3563
    @Immutable
3564
    public static class HttpContext {
3565
        private final RequestWrapper request;
3566
        private final ResponseWrapper response;
3567
        private final SessionWrapper session;
3568

3569
        public HttpContext(final RequestWrapper request, final ResponseWrapper response, final SessionWrapper session) {
1✔
3570
            this.request = request;
1✔
3571
            this.response = response;
1✔
3572
            this.session = session;
1✔
3573
        }
1✔
3574

3575
        public HttpContext(final RequestWrapper request, final ResponseWrapper response) {
1✔
3576
            this.request = request;
1✔
3577
            this.response = response;
1✔
3578
            this.session = request.getSession(false);
1✔
3579
        }
1✔
3580

3581
        public RequestWrapper getRequest() {
3582
            return request;
1✔
3583
        }
3584

3585
        public ResponseWrapper getResponse() {
3586
            return response;
1✔
3587
        }
3588

3589
        public SessionWrapper getSession() {
3590
            return session;
1✔
3591
        }
3592

3593
        /**
3594
         * Returns a new HttpContext with the new session set.
3595
         * <p>
3596
         * The request and response are referenced from this object.
3597
         *
3598
         * @param newSession the new session to set.
3599
         * @return the new HttpContext.
3600
         */
3601
        public HttpContext setSession(final SessionWrapper newSession) {
3602
            return new HttpContext(request, response, newSession);
1✔
3603
        }
3604
    }
3605

3606
    @Override
3607
    public String toString() {
3608
        return getStringValue();
×
3609
    }
3610

3611
    public String getStringValue() {
3612
        final StringBuilder sb = new StringBuilder();
×
3613
        sb.append('{');
×
3614

3615
        sb.append("dynamicDocuments: {");
×
3616
        if (dynamicDocuments != null) {
×
3617
            for (final String key : dynamicDocuments.keySet()) {
×
3618
                sb.append(key).append("-> ");
×
3619
                sb.append(dynamicDocuments.get(key));
×
3620
            }
3621
        }
3622
        sb.append('}');
×
3623
        sb.append('\n');
×
3624

3625
        sb.append("dynamicTextResources: {");
×
3626
        if (dynamicTextResources != null) {
×
3627
            for (final Map.Entry<Tuple2<String, Charset>,  QuadFunctionE<DBBroker, Txn, String, Charset, Reader, XPathException>> entry : dynamicTextResources.entrySet()) {
×
3628
                sb.append(entry.getKey()).append("-> ").append(entry.getValue());
×
3629
            }
3630
        }
3631
        sb.append('}');
×
3632
        sb.append('\n');
×
3633

3634
        sb.append("dynamicCollections: {");
×
3635
        if (dynamicCollections != null) {
×
3636
            for (final Map.Entry<String,
×
3637
                    TriFunctionE<DBBroker, Txn, String, Sequence, XPathException>> entry : dynamicCollections.entrySet()) {
×
3638
                sb.append(entry.getKey()).append("-> ").append(entry.getValue());
×
3639
            }
3640
        }
3641
        sb.append('}');
×
3642
        sb.append('\n');
×
3643

3644
        sb.append("baseURI: ");
×
3645
        try {
3646
            sb.append(getBaseURI()).append('\n');
×
3647
        } catch (final XPathException e) {
×
3648
            sb.append("?");
×
3649
        }
3650

3651
        sb.append("inScopePrefixes: {");
×
3652
        for (final Map.Entry<String, String> entry : inScopePrefixes.entrySet()) {
×
3653
            sb.append(entry.getKey()).append("-> ").append(entry.getValue());
×
3654
        }
3655
        sb.append('}');
×
3656
        sb.append('\n');
×
3657

3658
        sb.append("inScopeNamespaces: {");
×
3659
        for (final Map.Entry<String, String> entry : inScopeNamespaces.entrySet()) {
×
3660
            sb.append(entry.getKey()).append("-> ").append(entry.getValue());
×
3661
        }
3662
        sb.append('}');
×
3663
        sb.append('\n');
×
3664

3665
        sb.append("modules: {");
×
3666
        for (final Map.Entry<String, Module[]> entry : modules.entrySet()) {
×
3667
            sb.append(entry.getKey()).append("-> ");
×
3668
            for (final Module module : modules.get(entry.getKey())) {
×
3669
                sb.append("namespaceURI: ").append(module.getNamespaceURI()).append('\n');
×
3670
                sb.append("defaultPrefix: ").append(module.getDefaultPrefix()).append('\n');
×
3671
                sb.append("description: ").append(module.getDescription()).append('\n');
×
3672
                for (final Iterator<QName> it = module.getGlobalVariables(); it.hasNext(); ) {
×
3673
                    final QName qName = it.next();
×
3674
                    sb.append(qName).append(';');
×
3675
                }
3676
            }
3677
        }
3678
        sb.append('}');
×
3679
        sb.append('\n');
×
3680

3681
        sb.append("allModules: {");
×
3682
        for (final Map.Entry<String, Module[]> entry : allModules.entrySet()) {
×
3683
            sb.append(entry.getKey()).append("-> ");
×
3684
            for (final Module module : allModules.get(entry.getKey())) {
×
3685
                sb.append("namespaceURI: ").append(module.getNamespaceURI()).append('\n');
×
3686
                sb.append("defaultPrefix: ").append(module.getDefaultPrefix()).append('\n');
×
3687
                sb.append("description: ").append(module.getDescription()).append('\n');
×
3688
                for (final Iterator<QName> it = module.getGlobalVariables(); it.hasNext(); ) {
×
3689
                    final QName qName = it.next();
×
3690
                    sb.append(qName).append(';');
×
3691
                }
3692
            }
3693
        }
3694
        sb.append('}');
×
3695
        sb.append('\n');
×
3696

3697
        sb.append('}');
×
3698

3699
        return sb.toString();
×
3700
    }
3701

3702
    @Immutable
3703
    public static class ModuleVertex {
3704
        private final String namespaceURI;
3705
        private final String location;
3706

3707
        public ModuleVertex(final String namespaceURI) {
×
3708
            this.namespaceURI = namespaceURI;
×
3709
            this.location = null;
×
3710
        }
×
3711

3712
        public ModuleVertex(final String namespaceURI, final String location) {
1✔
3713
            this.namespaceURI = namespaceURI;
1✔
3714
            this.location = location;
1✔
3715
        }
1✔
3716

3717
        @Override
3718
        public boolean equals(final Object o) {
3719
            if (this == o) {
1✔
3720
                return true;
1✔
3721
            }
3722

3723
            if (o == null || getClass() != o.getClass()) {
1!
3724
                return false;
×
3725
            }
3726

3727
            final ModuleVertex that = (ModuleVertex) o;
1✔
3728
            if (!namespaceURI.equals(that.namespaceURI)) {
1✔
3729
                return false;
1✔
3730
            }
3731
            return location.equals(that.location);
1✔
3732
        }
3733

3734
        @Override
3735
        public int hashCode() {
3736
            int result = namespaceURI.hashCode();
1✔
3737
            result = 31 * result + location.hashCode();
1✔
3738
            return result;
1✔
3739
        }
3740

3741
        @Override
3742
        public String toString() {
3743
            return "Module{" +
×
3744
                    "namespaceURI='" + namespaceURI + '\'' +
×
3745
                    "location='" + location + '\'' +
×
3746
                    '}';
3747
        }
3748
    }
3749
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc