• 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

72.52
/exist-core/src/main/java/org/exist/xquery/functions/util/Eval.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
package org.exist.xquery.functions.util;
25

26
import java.io.IOException;
27
import java.io.InputStreamReader;
28
import java.io.StringWriter;
29
import java.net.MalformedURLException;
30
import java.net.URISyntaxException;
31
import java.net.URL;
32
import java.util.Date;
33
import java.util.Properties;
34
import java.util.SimpleTimeZone;
35

36
import javax.annotation.Nullable;
37
import javax.xml.datatype.Duration;
38

39
import org.exist.Namespaces;
40
import org.exist.dom.persistent.BinaryDocument;
41
import org.exist.dom.persistent.DocumentImpl;
42
import org.exist.dom.persistent.DocumentSet;
43
import org.exist.dom.memtree.NodeImpl;
44
import org.exist.dom.memtree.SAXAdapter;
45
import org.exist.dom.persistent.LockedDocument;
46
import org.exist.security.PermissionDeniedException;
47
import org.exist.source.DBSource;
48
import org.exist.source.FileSource;
49
import org.exist.source.Source;
50
import org.exist.source.SourceFactory;
51
import org.exist.source.StringSource;
52
import org.exist.storage.DBBroker;
53
import org.exist.storage.XQueryPool;
54
import org.exist.storage.lock.Lock.LockMode;
55
import org.exist.util.XMLReaderPool;
56
import org.exist.util.serializer.XQuerySerializer;
57
import org.exist.xmldb.XmldbURI;
58
import org.exist.xquery.*;
59
import org.exist.xquery.functions.fn.FnModule;
60
import org.exist.xquery.functions.fn.FunSerialize;
61
import org.exist.xquery.functions.fn.FunSubSequence;
62
import org.exist.xquery.value.*;
63
import org.w3c.dom.Element;
64
import org.w3c.dom.Node;
65
import org.w3c.dom.NodeList;
66
import org.xml.sax.InputSource;
67
import org.xml.sax.SAXException;
68
import org.xml.sax.XMLReader;
69

70
import static java.nio.charset.StandardCharsets.UTF_8;
71
import static org.exist.xquery.FunctionDSL.*;
72
import static org.exist.xquery.functions.util.UtilModule.functionSignatures;
73

74
/**
75
 * @author wolf
76
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
77
 */
78
public class Eval extends BasicFunction {
79

80
    private static final String evalArgumentText = "The expression to be evaluated.  If it is of type xs:string, the function " +
81
            "tries to execute this string as the query. If the first argument is of type xs:anyURI, " +
82
            "the function will try to load the query from the resource to which the URI resolves. " +
83
            "If the URI has no scheme, it is assumed that the query is stored in the db and the " +
84
            "URI is interpreted as a database path. This is the same as calling " +
85
            "util:eval(xs:anyURI('xmldb:exist:///db/test/test.xq')). " +
86
            //TODO : to be discussed ; until now, it's been used with a null context
87
            "The query inherits the current execution context, i.e. all " +
88
            "namespace declarations and variable declarations are visible from within the " +
89
            "inner expression. " +
90
            "The function returns an empty sequence if a whitespace string is passed.";
91

92
    private static final String contextArgumentText = "The query inherits the context described by the XML fragment in this parameter. " +
93
            "It should have the format:\n" +
94
            "<static-context>\n" +
95
            "\t<output-size-limit value=\"-1\"/>\n" +
96
            "\t<unbind-namespace uri=\"http://exist.sourceforge.net/NS/exist\"/>\n" +
97
            "\t<current-dateTime value=\"dateTime\"/>\n" +
98
            "\t<implicit-timezone value=\"duration\"/>\n" +
99
            "\t<variable name=\"qname\">variable value</variable>\n" +
100
            "\t<default-context>explicitly provide default context here</default-context>\n" +
101
            "\t<mapModule namespace=\"uri\" uri=\"uri_to_module\"/>\n" +
102
            "</static-context>.\n";
103

104
    private static final FunctionParameterSequenceType FS_PARAM_EXPRESSION = param(
1✔
105
            "expression", Type.ITEM, evalArgumentText);
1✔
106

107
    private static final FunctionParameterSequenceType FS_PARAM_INLINE_CONTEXT = optParam(
1✔
108
            "inline-context", Type.ITEM, "The inline context");
1✔
109

110
    private static final FunctionParameterSequenceType FS_PARAM_EVAL_CONTEXT_ITEM = optParam(
1✔
111
            "eval-context-item", Type.ITEM, "the context item against which the expression will be evaluated");
1✔
112

113
    private static final FunctionParameterSequenceType FS_PARAM_CONTEXT = optParam(
1✔
114
            "context", Type.NODE, contextArgumentText);
1✔
115

116
    private static final FunctionParameterSequenceType FS_PARAM_CACHE = param(
1✔
117
            "cache-flag", Type.BOOLEAN, "The flag for whether the compiled query should be cached. The cached query" +
1✔
118
                    "will be globally available within the db instance.");
119

120
    private static final FunctionParameterSequenceType FS_PARAM_PASS = param(
1✔
121
            "pass", Type.BOOLEAN, "Passes on the original error info (line and column number). By default, this option is false"
1✔
122
    );
123

124
    private static final FunctionParameterSequenceType FS_PARAM_EXTERNAL_VARIABLE = optManyParam(
1✔
125
            "external-variable", Type.ANY_TYPE, "External variables to be bound for the query that is being " +
1✔
126
                    "evaluated. Should be alternating variable QName and value.");
127

128
    private static final FunctionParameterSequenceType FS_PARAM_DEFAULT_SERIALISATION_PARAMS = optParam(
1✔
129
            "default-serialization-params", Type.ITEM, "The default parameters for serialization, these may" +
1✔
130
            "be overridden by any settings within the XQuery Prolog of the $expression.");
131

132
    private static final FunctionParameterSequenceType FS_PARAM_STARTING_LOC = optParam(
1✔
133
            "starting-loc", Type.DOUBLE, "the starting location within the results to return the values from"
1✔
134
    );
135

136
    private static final FunctionParameterSequenceType FS_PARAM_LENGTH = optParam(
1✔
137
            "length", Type.DOUBLE, "the number of items from $starting-loc to return the values of"
1✔
138
    );
139

140
    private static final FunctionReturnSequenceType RETURN_NODE_TYPE = returnsOptMany(
1✔
141
            Type.NODE, "the results of the evaluated XPath/XQuery expression");
1✔
142

143
    private static final FunctionReturnSequenceType RETURN_ITEM_TYPE = returnsOptMany(
1✔
144
            Type.ITEM, "the results of the evaluated XPath/XQuery expression");
1✔
145

146
    private static final String FS_EVAL_DESCRIPTION = "Dynamically evaluates an XPath/XQuery expression.";
147

148
    private static final String FS_EVAL_NAME = "eval";
149
    static final FunctionSignature[] FS_EVAL = functionSignatures(
1✔
150
            FS_EVAL_NAME,
1✔
151
            FS_EVAL_DESCRIPTION,
1✔
152
            RETURN_NODE_TYPE,
1✔
153
            arities(
1✔
154
                    arity(FS_PARAM_EXPRESSION),
1✔
155
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_CACHE),
1✔
156
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_CACHE, FS_PARAM_EXTERNAL_VARIABLE),
1✔
157
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_CACHE, FS_PARAM_EXTERNAL_VARIABLE, FS_PARAM_PASS)
1✔
158
            )
159
    );
160

161
    private static final String FS_EVAL_WITH_CONTEXT_NAME = "eval-with-context";
162
    static final FunctionSignature[] FS_EVAL_WITH_CONTEXT = functionSignatures(
1✔
163
            FS_EVAL_WITH_CONTEXT_NAME,
1✔
164
            FS_EVAL_DESCRIPTION,
1✔
165
            RETURN_NODE_TYPE,
1✔
166
            arities(
1✔
167
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_CONTEXT, FS_PARAM_CACHE),
1✔
168
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_CONTEXT, FS_PARAM_CACHE, FS_PARAM_EVAL_CONTEXT_ITEM),
1✔
169
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_CONTEXT, FS_PARAM_CACHE, FS_PARAM_EVAL_CONTEXT_ITEM, FS_PARAM_PASS)
1✔
170
            )
171
    );
172

173
    private static final String FS_EVAL_INLINE_NAME = "eval-inline";
174
    static final FunctionSignature[] FS_EVAL_INLINE = functionSignatures(
1✔
175
            FS_EVAL_INLINE_NAME,
1✔
176
            FS_EVAL_DESCRIPTION,
1✔
177
            RETURN_ITEM_TYPE,
1✔
178
            arities(
1✔
179
                    arity(FS_PARAM_INLINE_CONTEXT, FS_PARAM_EXPRESSION),
1✔
180
                    arity(FS_PARAM_INLINE_CONTEXT, FS_PARAM_EXPRESSION, FS_PARAM_CACHE),
1✔
181
                    arity(FS_PARAM_INLINE_CONTEXT, FS_PARAM_EXPRESSION, FS_PARAM_CACHE, FS_PARAM_PASS)
1✔
182
            )
183
    );
184

185
    private static final String FS_EVAL_AND_SERIALIZE_NAME = "eval-and-serialize";
186
    static final FunctionSignature[] FS_EVAL_AND_SERIALIZE = functionSignatures(
1✔
187
            FS_EVAL_AND_SERIALIZE_NAME,
1✔
188
            "Dynamically evaluates an XPath/XQuery expression and serializes the results",
1✔
189
            RETURN_ITEM_TYPE,
1✔
190
            arities(
1✔
191
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_DEFAULT_SERIALISATION_PARAMS),
1✔
192
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_DEFAULT_SERIALISATION_PARAMS, FS_PARAM_STARTING_LOC),
1✔
193
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_DEFAULT_SERIALISATION_PARAMS, FS_PARAM_STARTING_LOC, FS_PARAM_LENGTH),
1✔
194
                    arity(FS_PARAM_EXPRESSION, FS_PARAM_DEFAULT_SERIALISATION_PARAMS, FS_PARAM_STARTING_LOC, FS_PARAM_LENGTH, FS_PARAM_PASS)
1✔
195
            )
196
    );
1✔
197

198
    public Eval(final XQueryContext context, final FunctionSignature signature) {
199
        super(context, signature);
1✔
200
    }
1✔
201

202
    @Override
203
    public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
204
        final boolean isEvalDisabled = ((UtilModule) getParentModule()).isEvalDisabled();
1✔
205
        if (isEvalDisabled) {
1!
206
            throw new XPathException(this, "util:eval has been disabled by the eXist administrator in conf.xml");
×
207
        }
208

209
        return doEval(context, contextSequence, args);
1✔
210
    }
211

212
    private Sequence doEval(final XQueryContext evalContext, final Sequence contextSequence, final Sequence args[])
213
            throws XPathException {
214
        if (evalContext.getProfiler().isEnabled()) {
1!
215
            evalContext.getProfiler().start(this);
×
216
            evalContext.getProfiler().message(this, Profiler.DEPENDENCIES, "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
×
217
            if (contextSequence != null) {
×
218
                evalContext.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT SEQUENCE", contextSequence);
×
219
            }
220
        }
221

222
        int argCount = 0;
1✔
223
        Sequence exprContext = null;
1✔
224
        if (isCalledAs(FS_EVAL_INLINE_NAME)) {
1✔
225
            // the current expression context
226
            exprContext = args[argCount++];
1✔
227
        }
228

229
        // get the query expression
230
        final Item expr = args[argCount++].itemAt(0);
1✔
231
        final Source querySource;
232
        if (Type.subTypeOf(expr.getType(), Type.ANY_URI)) {
1✔
233
            querySource = loadQueryFromURI(expr);
1✔
234
        } else {
1✔
235
            final String queryStr = expr.getStringValue();
1✔
236
            if (queryStr.trim().isEmpty()) {
1!
237
                return new EmptySequence();
×
238
            }
239
            querySource = new StringSource(queryStr);
1✔
240
        }
241

242
        final NodeValue contextInit;
243
        if (isCalledAs(FS_EVAL_WITH_CONTEXT_NAME)) {
1✔
244
            // set the context initialization param for later use
245
            contextInit = (NodeValue) args[argCount++].itemAt(0);
1✔
246
        } else {
1✔
247
            contextInit = null;
1✔
248
        }
249

250
        // should the compiled query be cached?
251
        final boolean cache;
252
        if (isCalledAs(FS_EVAL_AND_SERIALIZE_NAME)) {
1✔
253
            cache = true;
1✔
254
        } else if (argCount < getArgumentCount()) {
1✔
255
            cache = ((BooleanValue) args[argCount++].itemAt(0)).effectiveBooleanValue();
1✔
256
        } else {
1✔
257
            cache = false;
1✔
258
        }
259

260
        // save some context properties
261
        evalContext.pushNamespaceContext();
1✔
262

263
        final LocalVariable mark = evalContext.markLocalVariables(false);
1✔
264

265
        // save the static document set of the current context, so it can be restored later
266
        final DocumentSet oldDocs = evalContext.getStaticDocs();
1✔
267
        if (exprContext != null) {
1✔
268
            evalContext.setStaticallyKnownDocuments(exprContext.getDocumentSet());
1✔
269
        }
270

271
        if (evalContext.isProfilingEnabled(2)) {
1!
272
            evalContext.getProfiler().start(this, "eval: " + expr);
×
273
        }
274

275
        // fixme! - hook for debugger here /ljo
276

277
        final XQuery xqueryService = evalContext.getBroker().getBrokerPool().getXQueryService();
1✔
278
        final XQueryContext innerContext;
279
        final Sequence initContextSequence;
280
        if (contextInit != null) {
1✔
281
            // eval-with-context: initialize a new context
282
            innerContext = new XQueryContext(context.getBroker().getBrokerPool());
1✔
283
            initContextSequence = initContext(contextInit.getNode(), innerContext);
1✔
284
        } else {
1✔
285
            // use the existing outer context
286
            // TODO: check if copying the static context would be sufficient???
287
            innerContext = evalContext.copyContext();
1✔
288
            innerContext.setShared(true);
1✔
289
            //innerContext = context;
290
            initContextSequence = null;
1✔
291
        }
292

293
        //set module load path
294
        if (Type.subTypeOf(expr.getType(), Type.ANY_URI)) {
1✔
295
            String uri = null;
1✔
296

297
            if (querySource instanceof DBSource) {
1!
298
                final XmldbURI documentPath = ((DBSource)querySource).getDocumentPath();
1✔
299
                uri = XmldbURI.EMBEDDED_SERVER_URI.append(documentPath).removeLastSegment().toString();
1✔
300
            } else if (querySource instanceof FileSource) {
1!
301
                uri = ((FileSource) querySource).getPath().getParent().toString();
×
302
            }
303

304
            if (uri != null) {
1!
305
                innerContext.setModuleLoadPath(uri);
1✔
306
            }
307
        }
308

309
        //bind external vars?
310
        if (isCalledAs(FS_EVAL_NAME) && getArgumentCount() >= 3) {
1✔
311
            final Sequence externalVars = args[argCount++];
1✔
312
            for (int i = 0; i < externalVars.getItemCount(); i++) {
1✔
313
                final Item varName = externalVars.itemAt(i);
1✔
314
                if (varName.getType() == Type.QNAME) {
1!
315
                    final Item varValue = externalVars.itemAt(++i);
1✔
316
                    innerContext.declareVariable(((QNameValue) varName).getQName(), varValue);
1✔
317
                }
318
            }
319
        }
320

321
        // determine if original line/column number are passed on
322
        final boolean pass;
323
        if (isCalledAs(FS_EVAL_NAME) && getArgumentCount() == 4) {
1✔
324
            pass = args[3].itemAt(0).toJavaObject(Boolean.class);
1✔
325
        } else if (isCalledAs(FS_EVAL_WITH_CONTEXT_NAME) && getArgumentCount() == 5) {
1!
326
            pass = args[4].itemAt(0).toJavaObject(Boolean.class);
×
327
        } else if (isCalledAs(FS_EVAL_INLINE_NAME) && getArgumentCount() == 4) {
1!
328
            pass = args[3].itemAt(0).toJavaObject(Boolean.class);
×
329
        } else if (isCalledAs(FS_EVAL_AND_SERIALIZE_NAME) && getArgumentCount() == 5) {
1!
330
            pass = args[4].itemAt(0).toJavaObject(Boolean.class);
×
331
        } else {
×
332
            // default
333
            pass = false;
1✔
334
        }
335

336
        // fixme! - hook for debugger here /ljo
337
        try {
338
            if (isCalledAs(FS_EVAL_WITH_CONTEXT_NAME) && getArgumentCount() >= 4) {
1✔
339
                final Item contextItem = args[argCount++].itemAt(0);
1✔
340
                if (contextItem != null) {
1!
341
                    //TODO : sort this out
342
                    if (exprContext != null) {
1!
343
                        LOG.warn("exprContext and contextItem are not null");
×
344
                    }
345
                    exprContext = contextItem.toSequence();
1✔
346
                }
347
            }
348

349

350
            if (initContextSequence != null) {
1✔
351
                exprContext = initContextSequence;
1✔
352
            }
353

354
            Sequence result = null;
1✔
355
            try {
356
                if (!isCalledAs(FS_EVAL_AND_SERIALIZE_NAME)) {
1✔
357
                    result = execute(evalContext.getBroker(), xqueryService, querySource, innerContext, exprContext,
1✔
358
                            cache, null);
1✔
359

360
                    return result;
1✔
361

362
                } else {
363
                    // get the default serialization options
364
                    final Properties defaultOutputOptions;
365
                    if (getArgumentCount() >= 2 && !args[1].isEmpty()) {
1!
366
                        defaultOutputOptions = FunSerialize.getSerializationProperties(this, args[1].itemAt(0));
1✔
367
                    } else {
1✔
368
                        defaultOutputOptions = new Properties();
1✔
369
                    }
370

371
                    // execute the query, XQuery prolog serialization options are collected into `xqueryOutputProperties`
372
                    final Properties xqueryOutputProperties = new Properties();
1✔
373
                    result = execute(evalContext.getBroker(), xqueryService, querySource, innerContext, exprContext,
1✔
374
                            cache, xqueryOutputProperties);
1✔
375

376
                    // do we need to subsequence the results?
377
                    if (getArgumentCount() > 2) {
1✔
378
                        result = FunSubSequence.subsequence(result,
1✔
379
                                ((DoubleValue)getArgument(2).eval(contextSequence, null).convertTo(Type.DOUBLE)),
1✔
380
                                getArgumentCount() == 3 ? null : ((DoubleValue)getArgument(3).eval(contextSequence, null).convertTo(Type.DOUBLE))
1!
381
                        );
382
                    }
383

384
                    // override the default options with the ones from the xquery prolog
385
                    final Properties serializationProperties = new Properties();
1✔
386
                    serializationProperties.putAll(defaultOutputOptions);
1✔
387
                    serializationProperties.putAll(xqueryOutputProperties);
1✔
388

389
                    // serialize the results
390
                    try(final StringWriter writer = new StringWriter()) {
1✔
391
                        final XQuerySerializer xqSerializer = new XQuerySerializer(
1✔
392
                                context.getBroker(), serializationProperties, writer);
1✔
393

394
                        final Sequence seq;
395
                        if (xqSerializer.normalize()) {
1✔
396
                            // TODO(JL): should this not be changed to DEFAULT_ITEM_SEPARATOR
397
                            seq = FunSerialize.normalize(this, context, result, null);
1✔
398
                        } else {
1✔
399
                            seq = result;
1✔
400
                        }
401

402
                        xqSerializer.serialize(seq);
1✔
403

404
                        return new StringValue(this, writer.toString());
1✔
405

406
                    } catch (final IOException | SAXException e) {
×
407
                        throw new XPathException(this, FnModule.SENR0001, e.getMessage());
×
408
                    }
409
                }
410
            } finally {
411
                cleanup(evalContext, innerContext, oldDocs, mark, expr, result);
1✔
412
            }
413

414
        } catch (final XPathException e) {
1✔
415
            try {
416
                e.prependMessage("Error while evaluating expression: " + querySource.getContent() + ". ");
1✔
417
            } catch (final IOException e1) {
1✔
418
            }
419

420
            if (!pass) {
1✔
421
                e.setLocation(line, column);
1✔
422
            }
423

424
            throw e;
1✔
425
        }
426
    }
427

428
    private void cleanup(final XQueryContext evalContext, final XQueryContext innerContext, final DocumentSet oldDocs,
429
            final LocalVariable mark, final Item expr, final Sequence resultSequence) {
430
        if (innerContext != evalContext) {
1!
431
            evalContext.addImportedContext(innerContext);
1✔
432
        }
433

434
        if (oldDocs != null) {
1!
435
            evalContext.setStaticallyKnownDocuments(oldDocs);
×
436
        }
437

438
        evalContext.popLocalVariables(mark);
1✔
439
        evalContext.popNamespaceContext();
1✔
440

441
        if (evalContext.isProfilingEnabled(2)) {
1!
442
            evalContext.getProfiler().end(this, "eval: " + expr, resultSequence);
×
443
        }
444
    }
1✔
445

446
    private Sequence execute(final DBBroker broker, final XQuery xqueryService, final Source querySource,
447
            final XQueryContext innerContext, final Sequence exprContext, final boolean cache,
448
            @Nullable final Properties outputProperties) throws XPathException {
449

450
        CompiledXQuery compiled = null;
1✔
451
        final XQueryPool pool = broker.getBrokerPool().getXQueryPool();
1✔
452

453
        try {
454
            compiled = cache ? pool.borrowCompiledXQuery(broker, querySource) : null;
1✔
455
            if (compiled == null) {
1✔
456
                compiled = xqueryService.compile(innerContext, querySource);
1✔
457
            } else {
1✔
458
                compiled.getContext().updateContext(innerContext);
1✔
459
                compiled.getContext().prepareForReuse();
1✔
460
            }
461

462
            Sequence sequence = xqueryService.execute(broker, compiled, exprContext, outputProperties, false);
1✔
463
            ValueSequence newSeq = new ValueSequence();
1✔
464
            newSeq.keepUnOrdered(unordered);
1✔
465
            boolean hasSupplements = false;
1✔
466
            for (int i = 0; i < sequence.getItemCount(); i++) {
1✔
467
                //if (sequence.itemAt(i) instanceof StringValue) {
468
                if (Type.subTypeOf(sequence.itemAt(i).getType(), Type.STRING)) {
1✔
469
                    newSeq.add(new StringValue(this, ((StringValue) sequence.itemAt(i)).getStringValue(true)));
1✔
470
                    hasSupplements = true;
1✔
471
                } else {
1✔
472
                    newSeq.add(sequence.itemAt(i));
1✔
473
                }
474
            }
475

476
            if (hasSupplements) {
1✔
477
                sequence = newSeq;
1✔
478
            }
479

480
            return sequence;
1✔
481

482
        } catch (final IOException | PermissionDeniedException ioe) {
×
483
            throw new XPathException(this, ioe);
×
484
        } finally {
485
            if (compiled != null) {
1✔
486
                compiled.getContext().runCleanupTasks();
1✔
487
                if (cache) {
1✔
488
                    pool.returnCompiledXQuery(querySource, compiled);
1✔
489
                } else {
1✔
490
                    compiled.reset();
1✔
491
                }
492
            }
493
        }
494
    }
495

496
    /**
497
     * @param expr
498
     * @throws XPathException
499
     * @throws NullPointerException
500
     * @throws IllegalArgumentException
501
     */
502
    private Source loadQueryFromURI(final Item expr) throws XPathException, NullPointerException, IllegalArgumentException {
503
        final String location = expr.getStringValue();
1✔
504
        Source querySource = null;
1✔
505
        if (location.indexOf(':') < 0 || location.startsWith(XmldbURI.XMLDB_URI_PREFIX)) {
1!
506
            try {
507
                XmldbURI locationUri = XmldbURI.xmldbUriFor(location);
1✔
508

509
                // If location is relative (does not contain any / and does
510
                // not start with . or .. then the path of the module need to
511
                // be added.
512
                if (location.indexOf('/') < 0 || location.startsWith(".")) {
1!
513
                    final XmldbURI moduleLoadPathUri = XmldbURI.xmldbUriFor(context.getModuleLoadPath());
×
514
                    locationUri = moduleLoadPathUri.resolveCollectionPath(locationUri);
×
515
                }
516

517
                try(final LockedDocument lockedSourceDoc = context.getBroker().getXMLResource(locationUri.toCollectionPathURI(), LockMode.READ_LOCK)) {
1✔
518
                    final DocumentImpl sourceDoc = lockedSourceDoc == null ? null : lockedSourceDoc.getDocument();
1!
519
                    if (sourceDoc == null) {
1!
520
                        throw new XPathException(this, "source for module " + location + " not found in database");
×
521
                    }
522
                    if (sourceDoc.getResourceType() != DocumentImpl.BINARY_FILE ||
1!
523
                            !"application/xquery".equals(sourceDoc.getMetadata().getMimeType())) {
1!
524
                        throw new XPathException(this, "source for module " + location + " is not an XQuery or " +
×
525
                        "declares a wrong mime-type");
526
                    }
527
                    querySource = new DBSource(context.getBroker().getBrokerPool(), (BinaryDocument) sourceDoc, true);
1✔
528
                } catch (final PermissionDeniedException e) {
×
529
                    throw new XPathException(this, "permission denied to read module source from " + location);
×
530
                }
531
            } catch (final URISyntaxException e) {
×
532
                throw new XPathException(this, e);
×
533
            }
534
        } else {
535
            // No. Load from file or URL
536
            try {
537
                //TODO: use URIs to ensure proper resolution of relative locations
538
                querySource = SourceFactory.getSource(context.getBroker(), context.getModuleLoadPath(), location, true);
×
539
                if (querySource == null) {
×
540
                    throw new XPathException(this, "source for query at " + location + " not found");
×
541
                }
542
            } catch (final MalformedURLException e) {
×
543
                throw new XPathException(this, "source location for query at " + location + " should be a valid URL: " +
×
544
                        e.getMessage());
×
545
            } catch (final IOException e) {
×
546
                throw new XPathException(this, "source for query at " + location + " not found: " +
×
547
                        e.getMessage());
×
548
            } catch (final PermissionDeniedException e) {
×
549
                throw new XPathException(this, "Permission denied to access query at " + location + " : " +
×
550
                        e.getMessage());
×
551
            }
552
        }
553
        return querySource;
1✔
554
    }
555

556
    /**
557
     * Read to optional static-context fragment to initialize
558
     * the context.
559
     *
560
     * @param root
561
     * @param innerContext
562
     * @throws XPathException
563
     */
564
    private Sequence initContext(final Node root, final XQueryContext innerContext) throws XPathException {
565
        final NodeList cl = root.getChildNodes();
1✔
566
        Sequence result = null;
1✔
567
        for (int i = 0; i < cl.getLength(); i++) {
1✔
568
            final Node child = cl.item(i);
1✔
569
            //TODO : more check on attributes existence and on their values
570
            if (child.getNodeType() == Node.ELEMENT_NODE && "variable".equals(child.getLocalName())) {
1!
571
                final Element elem = (Element) child;
1✔
572
                final String qname = elem.getAttribute("name");
1✔
573
                final String source = elem.getAttribute("source");
1✔
574
                NodeValue value;
575
                if (!source.isEmpty()) {
1!
576
                    // load variable contents from URI
577
                    value = loadVarFromURI(source);
×
578
                } else {
×
579
                    value = (NodeValue) elem.getFirstChild();
1✔
580
                }
581
                final String type = elem.getAttribute("type");
1✔
582
                if ((!type.isEmpty()) && Type.subTypeOf(Type.getType(type), Type.ANY_ATOMIC_TYPE)) {
1!
583
                    innerContext.declareVariable(qname, value.atomize().convertTo(Type.getType(type)));
×
584
                } else {
×
585
                    innerContext.declareVariable(qname, value);
1✔
586
                }
587
            } else if (child.getNodeType() == Node.ELEMENT_NODE && "output-size-limit".equals(child.getLocalName())) {
1!
588
                final Element elem = (Element) child;
×
589
                //TODO : error check
590
                innerContext.getWatchDog().setMaxNodes(Integer.parseInt(elem.getAttribute("value")));
×
591
            } else if (child.getNodeType() == Node.ELEMENT_NODE && "timeout".equals(child.getLocalName())) {
1!
592
                final Element elem = (Element) child;
1✔
593
                //TODO : error check
594
                innerContext.getWatchDog().setTimeout(Long.parseLong(elem.getAttribute("value")));
1✔
595
            } else if (child.getNodeType() == Node.ELEMENT_NODE && "current-dateTime".equals(child.getLocalName())) {
1!
596
                final Element elem = (Element) child;
×
597
                //TODO : error check
598
                final DateTimeValue dtv = new DateTimeValue(this, elem.getAttribute("value"));
×
599
                innerContext.setCalendar(dtv.calendar);
×
600
            } else if (child.getNodeType() == Node.ELEMENT_NODE && "implicit-timezone".equals(child.getLocalName())) {
1!
601
                final Element elem = (Element) child;
×
602
                //TODO : error check
603
                final Duration duration = TimeUtils.getInstance().newDuration(elem.getAttribute("value"));
×
604
                innerContext.setTimeZone(new SimpleTimeZone((int) duration.getTimeInMillis(new Date()), "XQuery context"));
×
605
            } else if (child.getNodeType() == Node.ELEMENT_NODE && "unbind-namespace".equals(child.getLocalName())) {
1!
606
                final Element elem = (Element) child;
×
607
                //TODO : error check
608
                if (!elem.getAttribute("uri").isEmpty()) {
×
609
                    innerContext.removeNamespace(elem.getAttribute("uri"));
×
610
                }
611
            } else if (child.getNodeType() == Node.ELEMENT_NODE && "staticallyKnownDocuments".equals(child.getLocalName())) {
1!
612
                final Element elem = (Element) child;
×
613
                //TODO : iterate over the children
614
                NodeValue value = (NodeValue) elem.getFirstChild();
×
615
                final XmldbURI[] pathes = new XmldbURI[1];
×
616
                //TODO : aggregate !
617
                //TODO : cleanly seperate the statically know docollection and documents
618
                pathes[0] = XmldbURI.create(value.getStringValue());
×
619
                innerContext.setStaticallyKnownDocuments(pathes);
×
620
            } /*else if (child.getNodeType() == Node.ELEMENT_NODE &&        "mapModule".equals(child.getLocalPart())) {
×
621
                                Element elem = (Element) child;
622
                                //TODO : error check
623
                                if (elem.getAttribute("namespace") != null && elem.getAttribute("uri") != null) {
624
                                        innerContext.mapModule(elem.getAttribute("namespace"),
625
                                                        XmldbURI.create(elem.getAttribute("uri")));
626
                                }
627
                        } */ else if (child.getNodeType() == Node.ELEMENT_NODE && "default-context".equals(child.getLocalName())) {
1!
628
                final Element elem = (Element) child;
1✔
629
                final NodeValue nodevalue = (NodeValue) elem;
1✔
630
                result = nodevalue.toSequence();
1✔
631
            }
632
        }
633

634
        return result;
1✔
635
    }
636

637
    private NodeImpl loadVarFromURI(final String uri) throws XPathException {
638
        XMLReader xr = null;
×
639
        final XMLReaderPool parserPool = context.getBroker().getBrokerPool().getParserPool();
×
640
        try {
641
            final URL url = new URL(uri);
×
642
            final InputStreamReader isr = new InputStreamReader(url.openStream(), UTF_8);
×
643
            final InputSource src = new InputSource(isr);
×
644

645
            xr = parserPool.borrowXMLReader();
×
646
            final SAXAdapter adapter = new SAXAdapter(this, context);
×
647
            xr.setContentHandler(adapter);
×
648
            xr.setProperty(Namespaces.SAX_LEXICAL_HANDLER, adapter);
×
649
            xr.parse(src);
×
650
            isr.close();
×
651
            return adapter.getDocument();
×
652
        } catch (final SAXException | IOException e) {
×
653
            throw new XPathException(this, e);
×
654
        } finally {
655
            if (xr != null) {
×
656
                parserPool.returnXMLReader(xr);
×
657
            }
658
        }
659
    }
660
}
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