XQJ Tutorial Part VIII: Binding External Variables



XQJ Part III: Querying Data from XML Files or Java XML APIs, shows through some simple examples how to bind a value to an external variable declared in your query. This section contains more details on that subject.

The XQuery Data Model

XQuery operates on the abstract, logical structure of XML, known as the XQuery Data Model (XDM). As such, in XQuery the value bound to an external variable is an XDM instance by definition. So, how do you convert a Java object in your Java application into an XDM instance? XQJ defines this mapping and glues it all together. Let's take a look at a simple example:

...
XQPreparedExpression xqp;
XQSequence xqs;
xqp = xqc.prepareExpression(
"declare variable $id as xs:integer external; " +
"doc('orders.xml')//order[id=$id]");
xqp.bindObject(new QName("id"),new Integer(174), null);
xqs = xqp.executeQuery();
...

The bindObject() Method

The bindObject() method is defined in the XQDynamicContext interface. It provides a number of methods to bind values to external variables. As XQDynamicContext is both the base for XQExpression and XQPreparedExpression, both expression implementations support binding values to external variables.

The first argument to the bindObject() method is a QName, which identifies the external variable in your XQuery. The second argument is the Java object to be bound. XQJ defines a mapping of Java objects to XDM instances; here are a few examples:

Java Type XQuery Type
java.lang.Integer xs:int
java.lang.BigInteger xs:integer
java.lang.BigDecimal xs:decimal
java.lang.String xs:untypedAtomic
org.w3c.dom.Document untyped document node
org.w3c.dom.Element untyped element node

The third argument, for which null is specified in the example above, allows you to override the default mapping. This is shown next. (The createAtomicType() method defined on XQDataFactory is introduced in XQJ Part IX: Creating XDM Instances, but for now it’s sufficient to know that it returns an XQItemType object representing the specified atomic type):

...
XQItemType xsinteger;
xsinteger = xqc.createAtomicType(XQItemType.XQBASETYPE_INTEGER);

XQPreparedExpression xqp;
XQSequence xqs;

xqp = xqc.prepareExpression(
"declare variable $v1 external; " +
"declare variable $v2 external; " +
"$v1 instance of xs:integer, "+
"$v1 instance of xs:int, "+
"$v2 instance of xs:integer, "+
"$v2 instance of xs:int");
xqp.bindObject(new QName("v1"),new Integer(174), null);
xqp.bindObject(new QName("v2"),new Integer(174), xsinteger);
...

This example yields a sequence of four xs:boolean instances:

true, true, true, false

A Java Integer is by default mapped to xs:int. xs:int extends by restriction xs:integer; as such the first two 'instance of' expressions evaluate to true. The second external variable is bound with an xs:integer instance — the application explicitly specifies to create such an XDM instance. As such, the last 'instance of' evaluates to false, as xs:integer is not extending xs:int.

Note that various error conditions can occur during the binding process:

  • The conversion from Java to XDM instance can fail. For example, a java.lang.Integer object with the value 10000 is converted into a xs:byte. As 10000 is out of bounds of the xs:byte value space, an error will be reported.
  • Once converted into an XDM instance, the binding can still fail in cases where the external variable declaration includes a declared type. In such a scenario, the XDM instance must match the declared type according to the rules of SequenceType matching. For example, a java.lang.Integer is bound and converted into an xs:integer instance, but the external variable is declared as xs:string.

Binding Atomic Values

We have introduced the bindObject() method through some examples, but XQDynamicContext has many more bind methods. bindAtomicValue() accepts a java.lang.String and will convert it to the specified type according to the casting rules from xs:string; essentially, the specified string must be in the lexical space of the specified atomic type. In the following example, the Java String "123" is converted into xs:string, xs:integer, and xs:double instances and bound to the external variables $v1, $v2, and $v3:

...
xqp.bindAtomicValue(new QName("v1"), "123",
xqc.createAtomicType(XQItemType.XQBASETYPE_STRING));
xqp.bindAtomicValue(new QName("v2"), "123",
xqc.createAtomicType(XQItemType.XQBASETYPE_INTEGER));
xqp.bindAtomicValue(new QName("v3"), "123",
xqc.createAtomicType(XQItemType.XQBASETYPE_DOUBLE));
...

In contrast, the following two bindAtomicValue() invocations will fail. The first because "abc" is not in the value spaces of xs:integer. The second one because no type has been specified as third parameter; unlike with bindObject(), bindAtomicValue() has no default mapping and a XQItemType must be specified as third argument:

...
xqp.bindAtomicValue(new QName("e"), "abc",
xqc.createAtomicType(XQItemType.XQBASETYPE_INTEGER));
xqp.bindAtomicValue(new QName("e"), "123", null);
...

bind() Methods for Java Primitive Types

XQDynamicContext also provides bindXXX() methods for each of the Java primitive types:

  • boolean
  • byte
  • double
  • float
  • int
  • long
  • short

For example, binding an xs:integer instance 123 to the external variable $v. The default mapping for int is xs:int; as such we specify the type as third parameter:

...
xqp.bindInt(new QName("v"), 123,
xqc.createAtomicType(XQItemType.XQBASETYPE_INTEGER));IntDate
...

Binding DOM, SAX, and StAX

Binding a DOM node is also possible; basically it is equivalent to bindObject, with the restriction that the argument must be a DOM node and as such the XDM instance is always a node, never an atomic value. Of course, in addition to DOM, the SAX and StAX APIs are supported through XQDynamicContext.

Let’s read an XML document, foo.xml, through DOM, and StAX, and each time bind it to an external variable, $v.

The DOM version:

...
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);

DocumentBuilder parser = factory.newDocumentBuilder();
Document domDocument = parser.parse("foo.xml");
xqp.bindNode(new QName("e"), domDocument,null);
...

The StAX version:

...
XMLInputFactory factory = XMLInputFactory.newInstance();
FileInputStream doc = new FileInputStream("foo.xml");
XMLStreamReader reader = factory.createXMLStreamReader(doc);
xqp.bindDocument(new QName("e"), reader, null);
...

But of course, this is something you only want to use in specific scenarios. The simple use case of binding an XML file can be easily accomplished in a single line. The XQJ implementation will make sure that the XML file is parsed and queried:

...
xqp.bindDocument(new QName("e"), new FileInputStream("foo.xml"));
...

An XQItem or a complete XQSequence can also be bound to an external variable. That is described in more details in XQJ Part X: XML Pipelines, along with XQJ support for the JAXP Source and Result interfaces.