All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.s9api.XQueryEvaluator Maven / Gradle / Ivy

There is a newer version: 10.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.s9api;

import net.sf.saxon.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.event.Builder;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.expr.instruct.UserFunction;
import net.sf.saxon.lib.TraceListener;
import net.sf.saxon.om.*;
import net.sf.saxon.query.DynamicQueryContext;
import net.sf.saxon.query.XQueryExpression;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.SequenceExtent;

import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * An XQueryEvaluator represents a compiled and loaded query ready for execution.
 * The XQueryEvaluator holds details of the dynamic evaluation context for the query.
 * 

*

An XQueryEvaluator must not be used concurrently in multiple threads. * It is safe, however, to reuse the object within a single thread to run the same * query several times. Running the query does not change the context * that has been established.

*

*

An XQueryEvaluator is always constructed by running the Load * method of an {@link net.sf.saxon.s9api.XQueryExecutable}.

*

*

An XQueryEvaluator is itself a Iterable. This makes it possible to * evaluate the results in a for-each expression.

*

*

An XQueryEvaluator is itself a Destination. This means it is possible to use * one XQueryEvaluator as the destination to receive the results of another transformation, * this providing a simple way for transformations to be chained into a pipeline. When the query is executed * this way, {@link #setDestination(Destination)} must be called to provide a destination for the result of this query. * Note however that a when the input to a query is supplied in this way, it will always be built as a tree in * memory, rather than the transformation being streamed.

*/ public class XQueryEvaluator implements Iterable, Destination { private Processor processor; private XQueryExpression expression; private DynamicQueryContext context; private Controller controller; // used only when making direct calls to global functions private Destination destination; private Set updatedDocuments; /*@Nullable*/ private Builder sourceTreeBuilder; /** * Protected constructor * * @param processor the Saxon processor * @param expression the XQuery expression */ protected XQueryEvaluator(Processor processor, XQueryExpression expression) { this.processor = processor; this.expression = expression; this.context = new DynamicQueryContext(expression.getExecutable().getConfiguration()); } /** * Set the schema validation mode for the transformation. This indicates how source documents * loaded specifically for this transformation will be handled. This applies to the * principal source document if supplied as a SAXSource or StreamSource, and to all * documents loaded during the transformation using the doc(), document(), * or collection() functions. * * @param mode the validation mode. Passing null causes no change to the existing value. * Passing Validation.DEFAULT resets to the initial value, which determines * the validation requirements from the Saxon Configuration. */ public void setSchemaValidationMode(ValidationMode mode) { if (mode != null) { context.setSchemaValidationMode(mode.getNumber()); } } /** * Get the schema validation mode for the transformation. This indicates how source documents * loaded specifically for this transformation will be handled. This applies to the * principal source document if supplied as a SAXSource or StreamSource, and to all * documents loaded during the transformation using the doc(), document(), * or collection() functions. * * @return the validation mode. */ public ValidationMode getSchemaValidationMode() { return ValidationMode.get(context.getSchemaValidationMode()); } /** * Set the source document for the query. *

*

If the source is an instance of {@link net.sf.saxon.om.NodeInfo}, the supplied node is used * directly as the context node of the query.

*

*

If the source is an instance of {@link javax.xml.transform.dom.DOMSource}, the DOM node identified * by the DOMSource is wrapped as a Saxon node, and this is then used as the context item

*

*

In all other cases a new Saxon tree is built, by calling * {@link net.sf.saxon.s9api.DocumentBuilder#build(javax.xml.transform.Source)}, and the document * node of this tree is then used as the context item for the query.

* * @param source the source document to act as the initial context item for the query. */ public void setSource(Source source) throws SaxonApiException { if (source instanceof NodeInfo) { setContextItem(new XdmNode((NodeInfo) source)); } else if (source instanceof DOMSource) { setContextItem(processor.newDocumentBuilder().wrap(source)); } else { setContextItem(processor.newDocumentBuilder().build(source)); } } /** * Set the initial context item for the query * * @param item the initial context item, or null if there is to be no initial context item */ public void setContextItem(XdmItem item) { if (item != null) { context.setContextItem((Item) item.getUnderlyingValue()); } } /** * Get the initial context item for the query, if one has been set * * @return the initial context item, or null if none has been set. This will not necessarily * be the same object as was supplied, but it will be an XdmItem object that represents * the same underlying node or atomic value. */ public XdmItem getContextItem() { return (XdmItem) XdmValue.wrap(context.getContextItem()); } /** * Set the value of external variable defined in the query * * @param name the name of the external variable, as a QName * @param value the value of the external variable, or null to clear a previously set value */ public void setExternalVariable(QName name, XdmValue value) { context.setParameterValue(name.getClarkName(), (value == null ? null : value.getUnderlyingValue())); } /** * Get the value that has been set for an external variable * * @param name the name of the external variable whose value is required * @return the value that has been set for the external variable, or null if no value has been set */ public XdmValue getExternalVariable(QName name) { Object oval = context.getParameter(name.getClarkName()); if (oval == null) { return null; } if (oval instanceof Sequence) { return XdmValue.wrap((Sequence) oval); } throw new IllegalStateException(oval.getClass().getName()); } /** * Set an object that will be used to resolve URIs used in * fn:doc() and related functions. * * @param resolver An object that implements the URIResolver interface, or * null. */ public void setURIResolver(URIResolver resolver) { context.setURIResolver(resolver); } /** * Get the URI resolver. * * @return the user-supplied URI resolver if there is one, or the * system-defined one otherwise */ public URIResolver getURIResolver() { return context.getURIResolver(); } /** * Set the error listener. The error listener receives reports of all run-time * errors and can decide how to report them. * * @param listener the ErrorListener to be used */ public void setErrorListener(ErrorListener listener) { context.setErrorListener(listener); } /** * Get the error listener. * * @return the ErrorListener in use */ public ErrorListener getErrorListener() { return context.getErrorListener(); } /** * Set a TraceListener which will receive messages relating to the evaluation of all expressions. * This option has no effect unless the query was compiled to enable tracing. * * @param listener the TraceListener to use */ public void setTraceListener(TraceListener listener) { context.setTraceListener(listener); } /** * Get the registered TraceListener, if any * * @return listener the TraceListener in use, or null if none has been set */ public TraceListener getTraceListener() { return context.getTraceListener(); } /** * Set the destination for output from the fn:trace() function. * By default, the destination is System.err. If a TraceListener is in use, * this is ignored, and the trace() output is sent to the TraceListener. * * @param stream the PrintStream to which trace output will be sent. If set to * null, trace output is suppressed entirely. It is the caller's responsibility * to close the stream after use. * @since 9.1 */ public void setTraceFunctionDestination(PrintStream stream) { context.setTraceFunctionDestination(stream); } /** * Get the destination for output from the fn:trace() function. * * @return the PrintStream to which trace output will be sent. If no explicitly * destination has been set, returns System.err. If the destination has been set * to null to suppress trace output, returns null. * @since 9.1 */ public PrintStream getTraceFunctionDestination() { return context.getTraceFunctionDestination(); } /** * Set the destination to be used for the query results * * @param destination the destination to which the results of the query will be sent */ public void setDestination(Destination destination) { this.destination = destination; } /** * Perform the query. *

*

  • In the case of a non-updating query, the results are sent to the * registered Destination.
  • *
  • In the case of an updating query, all updated documents will be available after query * execution as the result of the {@link #getUpdatedDocuments} method.
  • *
* * @throws net.sf.saxon.s9api.SaxonApiException * if any dynamic error occurs during the query * @throws IllegalStateException if this is a non-updating query and no Destination has been * supplied for the query results */ public void run() throws SaxonApiException { try { if (expression.isUpdateQuery()) { Set docs = expression.runUpdate(context); updatedDocuments = new HashSet(); for (MutableNodeInfo doc : docs) { updatedDocuments.add((XdmNode) XdmNode.wrapItem(doc)); } } else { if (destination == null) { throw new IllegalStateException("No destination supplied"); } Result receiver; if (destination instanceof Serializer) { //receiver = ((Serializer) destination).getResult(); //context.set receiver = ((Serializer) destination).getReceiver(expression.getExecutable()); } else { receiver = destination.getReceiver(expression.getExecutable().getConfiguration()); } expression.run(context, receiver, null); destination.close(); } } catch (TransformerException e) { throw new SaxonApiException(e); } } /** * Perform the query, sending the results to a specified destination. *

*

This method must not be used with an updating query.

*

*

This method is designed for use with a query that produces a single node (typically * a document node or element node) as its result. If the query produces multiple nodes, * the effect depends on the kind of destination. For example, if the result is an * XdmDestination, only the last of the nodes will be accessible.

* * @param destination The destination where the result document will be sent * @throws net.sf.saxon.s9api.SaxonApiException * if any dynamic error occurs during the query * @throws IllegalStateException if this is an updating query */ public void run(Destination destination) throws SaxonApiException { if (expression.isUpdateQuery()) { throw new IllegalStateException("Query is updating"); } try { Receiver receiver; if (destination instanceof Serializer) { receiver = ((Serializer) destination).getReceiver(expression.getExecutable()); } else { receiver = destination.getReceiver(expression.getExecutable().getConfiguration()); } expression.run(context, receiver, null); } catch (TransformerException e) { throw new SaxonApiException(e); } } /** * Perform the query, returning the results as an XdmValue. This method * must not be used with an updating query * * @return an XdmValue representing the results of the query * @throws SaxonApiException if the query fails with a dynamic error * @throws IllegalStateException if this is an updating query */ public XdmValue evaluate() throws SaxonApiException { if (expression.isUpdateQuery()) { throw new IllegalStateException("Query is updating"); } try { SequenceIterator iter = expression.iterator(context); Sequence result = SequenceExtent.makeSequenceExtent(iter); return XdmValue.wrap(result); } catch (XPathException e) { throw new SaxonApiException(e); } } /** * Evaluate the XQuery expression, returning the result as an XdmItem (that is, * a single node or atomic value). * * @return an XdmItem representing the result of the query, or null if the query * returns an empty sequence. If the expression returns a sequence of more than one item, * any items after the first are ignored. * @throws SaxonApiException if a dynamic error occurs during the query evaluation. * @since 9.2 */ public XdmItem evaluateSingle() throws SaxonApiException { try { SequenceIterator iter = expression.iterator(context); Item next = iter.next(); return (next == null ? null : (XdmItem)XdmValue.wrap(next)); } catch (XPathException e) { throw new SaxonApiException(e); } } /** * Evaluate the query, and return an iterator over its results. *

This method must not be used with an updating query.

* * @throws SaxonApiUncheckedException if a dynamic error is detected while constructing the iterator. * It is also possible for an SaxonApiUncheckedException to be thrown by the hasNext() method of the * returned iterator if a dynamic error occurs while evaluating the result sequence. * @throws IllegalStateException if this is an updating query */ public Iterator iterator() throws SaxonApiUncheckedException { if (expression.isUpdateQuery()) { throw new IllegalStateException("Query is updating"); } try { return new XdmSequenceIterator(expression.iterator(context)); } catch (XPathException e) { throw new SaxonApiUncheckedException(e); } } /** * Return a Receiver which can be used to supply the principal source document for the transformation. * This method is intended primarily for internal use, though it can also * be called by a user application that wishes to feed events into the query engine. *

*

Saxon calls this method to obtain a Receiver, to which it then sends * a sequence of events representing the content of an XML document. This method is provided so that * XQueryEvaluator implements Destination, allowing one transformation * to receive the results of another in a pipeline.

*

*

Note that when an XQueryEvaluator is used as a Destination, the initial * context node set on that XQueryEvaluator (using {@link #setSource(javax.xml.transform.Source)}) is ignored.

* * @param config The Saxon configuration. This is supplied so that the destination can * use information from the configuration (for example, a reference to the name pool) * to construct or configure the returned Receiver. * @return the Receiver to which events are to be sent. * @throws SaxonApiException if the Receiver cannot be created * @throws IllegalStateException if no Destination has been supplied */ public Receiver getReceiver(Configuration config) throws SaxonApiException { if (destination == null) { throw new IllegalStateException("No destination has been supplied"); } if (controller == null) { controller = expression.newController(); context.initializeController(controller); try { controller.defineGlobalParameters(); } catch (XPathException e) { throw new SaxonApiException(e); } } sourceTreeBuilder = controller.makeBuilder(); Receiver stripper = controller.makeStripper(sourceTreeBuilder); if (controller.getExecutable().stripsInputTypeAnnotations()) { stripper = controller.getConfiguration().getAnnotationStripper(stripper); } return stripper; } /** * Close this destination, allowing resources to be released. Used when this XQueryEvaluator is acting * as the destination of another transformation or query. Saxon calls this method when it has finished writing * to the destination. */ public void close() throws SaxonApiException { if (sourceTreeBuilder != null) { DocumentInfo doc = (DocumentInfo) sourceTreeBuilder.getCurrentRoot(); setSource(doc); sourceTreeBuilder = null; if (doc == null) { throw new SaxonApiException("No source document has been built by the previous pipeline stage"); } run(destination); destination.close(); } } /** * After executing an updating query using the {@link #run()} method, iterate over the root * nodes of the documents updated by the query. *

*

The results remain available until a new query is executed. This method returns the results * of the most recently executed query. It does not consume the results.

* * @return an iterator over the root nodes of documents (or other trees) that were updated by the query * @since 9.1 */ public Iterator getUpdatedDocuments() { return updatedDocuments.iterator(); } /** * Call a global user-defined function in the compiled query. *

* If this is called more than once (to evaluate the same function repeatedly with different arguments, * or to evaluate different functions) then the sequence of evaluations uses the same values of global * variables including external variables (query parameters); the effect of any changes made to query parameters * between calls is undefined. * * @param function The name of the function to be called * @param arguments The values of the arguments to be supplied to the function. These * must be of the correct type as defined in the function signature (there is no automatic * conversion to the required type). * @throws SaxonApiException if no function has been defined with the given name and arity; * or if any of the arguments does not match its required type according to the function * signature; or if a dynamic error occurs in evaluating the function. * @since 9.3 */ public XdmValue callFunction(QName function, XdmValue[] arguments) throws SaxonApiException { final UserFunction fn = expression.getStaticContext().getUserDefinedFunction( function.getNamespaceURI(), function.getLocalName(), arguments.length); if (fn == null) { throw new SaxonApiException("No function with name " + function.getClarkName() + " and arity " + arguments.length + " has been declared in the query"); } try { // TODO: use the same controller in other interfaces such as run(), and expose it in a trapdoor API if (controller == null) { controller = expression.newController(); context.initializeController(controller); controller.defineGlobalParameters(); } else { context.initializeController(controller); controller.defineGlobalParameters(); } Sequence[] vr = new Sequence[arguments.length]; for (int i = 0; i < arguments.length; i++) { net.sf.saxon.value.SequenceType type = fn.getParameterDefinitions()[i].getRequiredType(); vr[i] = arguments[i].getUnderlyingValue(); if (!type.matches(vr[i], controller.getConfiguration())) { throw new SaxonApiException("Argument " + (i + 1) + " of function " + function.getClarkName() + " does not match the required type " + type.toString()); } } Sequence result = fn.call(vr, controller); return XdmValue.wrap(result); } catch (XPathException e) { throw new SaxonApiException(e); } } /** * Get the underlying dynamic context object. This provides an escape hatch to the underlying * implementation classes, which contain methods that may change from one release to another. * * @return the underlying object representing the dynamic context for query execution */ public DynamicQueryContext getUnderlyingQueryContext() { return context; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy