XQJ Tutorial Part X: XML Pipelines



In this chapter, we'll show you how to create a pipeline of XQueries and how to integrate XQuery with JAXP-based XML processors.

What Is an XML Pipeline?

An XML pipeline is a sequence of XML processes, also called transformations; in an XML pipeline, the result of one transformation is passed as input to the next one. You can find an interesting discussion about XML pipelines in Norman Walsh’s essay Why Pipelines?

The transformations that come most easily to mind are XSLT transformations, XML Schema validation, simple XML parsing, and so on. But of course, a transformation can also be an XQuery. This chapter is about integrating XQuery and XQJ in an XML pipeline.

A Simple XML Pipeline

Let’s start with an XML pipeline, where the result of a first XQuery is passed to a second. Given that a query execution results in an XQSequence object, and an XQSequence object can be bound to an external variable, pipelining two XQueries is rather simple.

...
XQExpression xqe1 = xqc.createExpression();
XQSequence xqs1;
xqs1 = xqe1.executeQuery("doc('orders.xml')//orders");
XQExpression xqe2 = xqc.createExpression
xqe2.bindSequence(xqs1);

XQSequence xqs2 = xqe2.executeQuery(
"declare variable $orders as element(*,xs:untyped) external; " +
"for $order in $orders where $order/@status = 'closed' " +
"return <closed_order id = '{$order/@id}'>{ " +
" $order/* </closed_order>";
xqs2.writeSequence(System.out, null);
xqe2.close();
xqe1.close();
...

In this example, both queries in the pipeline are executed in the context of a single XQConnection object. But this is not required; it is entirely possible to have two different connection objects, even from different XQJ implementations.

The application writer shouldn’t be concerned with how the query result from the first XQuery is passed to the second XQuery. The mechanism used to pass the query result — whether DOM, SAX, StAX, serialized XML or some other (proprietary) mechanism, for example — is an implementation detail. The application can assume that the most appropriate mechanism is used.

Using XQuery Results with Other XML Processors

Let’s now look at how an XQuery can be integrated in a pipeline with other XML processors. We'll present two scenarios. First, one in which the result of an XQuery is passed to an XSLT transformation; and then, vice versa — an XQuery processes the results of an XSLT transformation.

On the Java platform, XSLT transformations are processed through the JAXP API. JAXP makes use of the Source and Result interfaces to query XML documents and to handle transformation results. As XQJ has built-in support for these interfaces, it is possible to integrate both JAXP and XQJ in a pipeline.

Passing XSLT Results to XQuery

The following example invokes an XSLT transformation, passing the transformation results as input to an XQuery. Under the covers, SAX events are pushed to the XQuery engine. SAX (or StAX) is preferable to passing a serialized XML stream, in terms of both performance and scalability. (DOM trees, while useful in some cases, should usually be your last choice for this type of query processing.)

First, an XMLFilter for the XLST transformation is created. A SAXSource is created as well, and it serves as the input for the XSLT Transformation. It is this SAXSource that is bound to the external variable of the XQuery. Subsequently the XQuery is executed, and finally the application consumes the results of the XQuery:

...
// Build an XMLFilter for the XSLT transformation
SAXTransformerFactory stf;
stf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
XMLFilter stage1;
stage1 = stf.newXMLFilter(new StreamSource("stage1.xsl"));

// Create a SAX source, the input for the XSLT transformation
SAXSource saxSource;
saxSource = new SAXSource(stage1,new InputSource("input.xml"));

// Create the XQuery expression
XQPreparedExpression stage2;
stage2 = xqc.prepareExpression(new FileInputStream("stage2.xquery"));

// Bind the input document as a Source object to stage2
stage2.bindDocument(new QName("var"),saxSource);

// Execute the query (stage2)
XQSequence result = stage2.executeQuery();

// Process query results
result.writeSequenceToResult(
new StreamResult(System.out));
...

As you might have noticed in this example, the pipeline is activated starting from the end. Under the covers, the XQJ implementation invokes the parse() method on the SAXSource object bound to the external variable. This, in turn, starts invoking SAX callbacks to the XSLT transformation, which ultimately performs callbacks to the XQJ implementation. These callbacks represent the result of the XSLT transformation, allowing the XQuery to yield results back to the application.

Passing XQuery Results to XSLT

Let's now have a closer look at a pipeline in which the results of an XQuery are passed to an XSLT transformation.

First we present a utility class, XQJFilter, an XMLFilter based on an XQJ XQPreparedExpression object:

public class XQJFilter extends XMLFilterImpl {
XQPreparedExpression _expression;

public XQJFilter(XQPreparedExpression expression) {
_expression = expression;
}
public void parse(InputSource source) throws SAXException {
try {
XQSequence xqs = _expression.executeQuery();
Result result = new SAXResult(this.getContentHandler());
xqs.writeSequenceToResult(result);
xqs.close();
} catch (XQException e) {
throw new SAXException(e);
}
}
}

Next we’ll use this XQJFilter implementation to build the pipeline:

...
// Create an XQuery expression
XQPreparedExpression xqp;
xqp = xqc.prepareExpression(new FileInputStream ("query.xq"));
// Create the XQJFilter, the first stage in our pipeline
XQJFilter stage1 = new XQJFilter(xqp);
// Create an XMLFilter for the XSLT transformation, the 2nd stage

SAXTransformerFactory stf;
stf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
XMLFilter stage2 = stf.newXMLFilter(new StreamSource("stage2.xsl"));
stage2.setParent(stage1);
// Make sure to capture the SAX events as result of the pipeline
stage2.setContentHandler(...);
// Activate the pipeline
stage2.parse("");
...

As with the previous example, the pipeline here is also activated at the end, by calling the parse() method on the second stage (stage2.parse).