The JXPath Package: Referencing Objects Using XPath


The JXPath Package: Referencing Objects Using XPath

XPath is a language used in the XML environment to reference elements within an XML document. The power of XPath is that it is a compact language that makes it very simple to generate complex queries. In XML speak, XPath is executed on the XML Document Object Model (DOM). Let's take a step back to consider this for a moment; it is evident that XPath is executed against objects. The original developers of the JXPath package noticed that XML searches object instances, which is very similar to what generic programs create, so they created a set of classes that use XPath as a generic querying language to navigate complex Java object hierarchies.

With JXPath , it's not essential to know the classes within the JXPath package, but it is imperative to know XPath. However, to understand XPath, we need to show a simple mapping between XML and Java. Listing 7.48 is a sample XML document that will be converted into a Java object hierarchy.

Listing 7.48
start example
 <data> <elements>hello</elements> </data> 
end example
 

Listing 7.49 is the mapped Java class.

Listing 7.49
start example
 public class Data { private String _elements; public String getElements() { return _elements; } } 
end example
 

In Listing 7.49 is the root XML element data , with a child element elements . This parent-child relationship is mapped as a Java Bean in Listing 7.49. The root XML element would be the class Data , and the child element would be the bean property elements . In Listing 7.49, elements could have also represented another Java Bean object.

Granted, there are limits to comparing XML with Java objects. For example, XML attributes and XML documents that represent HTML are not easily represented. However, this is OK, because JXPath is not meant to complement XML or provide XML serialization patterns. JXPath uses the XPath language to simplify object referencing. Therefore, it's not necessary to do referencing like that shown in Listing 7.50.

Listing 7.50
start example
 data.getMyProperty().getFromCollection( 123).getAnotherProperty( ).getYetAnotherProperty().getMyValue(); 
end example
 

Listing 7.50 shows a very long and tedious reference. If you use JXPath , such a reference is not necessary anymore. In addition, using JXPath , you can add filtering code, which simply is not possible in Listing 7.50.

Technical Details for the JXPath Package

Tables 7.4 and 7.5 contain the abbreviated details necessary to use the JXPath package.

Table 7.4: Repository details for the JXPath package.

Item

Details

CVS repository

jakarta-commons

Directory within repository

jxpath

Main packages used

org.apache.commons.jxpath

Table 7.5: Package and class details (legend: [jxpath] = org.apache.commons.jxpath).

Class/Interface

Details

[jxpath].JXPathContext

The core class of the entire JXPath package. From this class all other operations are possible. This class is also the class that most developers will solely interact with.

[jxpath].Pointer

A class that stores a reference to the absolute XPath expression for found elements.

[jxpath].CompiledExpression

A compiled XPath class that is used for XPath expressions that are constantly used.

Starting with the Basics of JXPath

If you want to be able to use the JXPath package, there has to be a hierarchy of objects. Granted, the samples in this chapter will only extend to a couple of layers of object hierarchy; more complexity is assumed in your programs. Otherwise, using the JXPath package is adding complexity when it is not necessary.

Let's look at Listing 7.51. This consists of two Java Beans that we used in Chapter 5 ( parts have been deleted for simplicity).

Listing 7.51
start example
 public class BeanToWrite implements java.io.Serializable { private int _iValue; private String _strValue; public BeanToWrite( int ival, String sval) { _iValue = ival; _strValue = sval; } public int getIntegerValue() { return _iValue; } public String getStringValue() { return _strValue; } } public class ParentBean implements java.io.Serializable { private String _dataMember; private BeanToWrite _bean; public ParentBean() { _bean = new BeanToWrite( 1234, "hello"); _dataMember = "parent"; } public String getDataMember() { return _dataMember; } public BeanToWrite getMyReferenceToAnotherBean() { return _bean; } } 
end example
 

In Listing 7.51, the class BeanToWrite represents a very simple bean that exposes two properties. The class ParentBean is a simple bean that exposes two properties also; one is myReferenceToAnotherBean, which is a reference to the BeanToWrite class instance. The other is the propery getDataMember, which returns a String object instance.

To retrieve the value of the class property BeanToWrite.integerValue, use JXPath . Listing 7.52 is an implementation using JXPath .

Listing 7.52
start example
 BeanToWrite bean = new BeanToWrite( 1234, "hello"); JXPathContext ctxt = JXPathContext.newContext( bean); Integer value = (Integer)ctxt.getValue( "/integerValue"); 
end example
 

In Listing 7.52, the class JXPathContext exposes the static method newContext , which is used to generate a query tree. The first parameter of the method newContext is a reference to the Java Bean instance that will be queried. The query tree based on the Java Bean instance is stored in the variable ctxt . Then, to query the Java Bean instance, the method getValue is used. The method getValue has one parameter, which is the XPath statement. In Listing 7.52, the XPath statement is used to find the value of the property integerValue . Let's look to Listings 7.48 and 7.49; there, the property integerValue is mapped as a child element for the XPath parser. The return value from the method getValue is an object that represents what is being searched. In the case of the XPath query, it is safe to cast to the class Integer because the property integerValue is an integer value. Otherwise, the cast might cause an exception.

Listing 7.53 is a bit more complex in that the class ParentBean is used as the basis of a query.

Listing 7.53
start example
 ParentBean bean = new ParentBean(); JXPathContext ctxt = JXPathContext.newContext( bean); Integer value = (Integer)ctxt.getValue( "/myReferenceToAnotherBean/integerValue"); 
end example
 

In Listing 7.53, the same code as in Listing 7.52 is executed, except that the XPath query is referencing the properties myReferenceToAnotherBean.integerValue . If you look back at Listing 7.51, the same value could be returned using a property reference notation, as shown in Listing 7.54.

Listing 7.54
start example
 ParentBean bean; int value = bean.getMyReferenceToAnotherBean().getIntegerValue(); 
end example
 

XPath, for those of you who do not know anything about it, is a querying language that can be frustrating at times because of its syntax. The simplest way to understand XPath is to consider an XPath expression like a directory location. For example, Listing 7.55 is a command to move up the directory.

Listing 7.55
start example
 cd .. 
end example
 

Another example, shown in Listing 7.56, is a command to navigate into a subdirectory.

Listing 7.56
start example
 cd items 
end example
 

Using XPath is just like navigating a directory, except that XPath has more sophisticated navigation commands. Each time you try to navigate an XPath query, the context or location is altered . The difference in using XPath is that you do not need to use the cd command when navigating directories. Listing 7.57 shows how complicated XPath can get.

Listing 7.57
start example
 child::elements[ child::items != "me"] 
end example
 

In Listing 7.57, the XPath is saying to make sure that all XML child element items are not equal to the value of dumdum. Listing 7.57 is an example of using concepts such as axis and predicates.

Navigating the Hierarchy

When you reference nodes in an XPath query, the position in the hierarchy changes. In XPath, you typically navigate by using one of the notations shown in Listing 7.58.

Listing 7.58
start example
 *or/itemor /*[ 1] or//item[ 1] 
end example
 

The XPath notation examples defined in Listing 7.58 are called abbreviated XPath syntax. The abbreviated XPath syntax notations shown are explained as follows :

  • * : This matches all subelements specified at the current context.

  • / : This selects a new context. In Listing 7.52 and 7.53, the slash was used to select both the root context and the child context. If the XPath query does not start with a slash, this implies that the current context is selected. In the case of Listing 7.52 and 7.53, this would mean the root context.

  • // : This selects all nodes in the current context. In the case of the class ParentBean , this would mean listing all of the properties that the classes ParentBean and BeanToWrite expose.

  • [ ] : This specifies a predicate from where the thus-far selected nodes are filtered according to the rules indicated. inside the brackets

  • . : This selects the current context (this is usually not referenced).

  • .. : This selects the parent element instead of changing the location to a subelement.

Instead of using the abbreviated XPath syntax, Listing 7.59 illustrates standard XPath syntax using axis specifiers.

Listing 7.59
start example
 child::* or child::item or child::*[ 1] or descendant::item[ 1] 
end example
 

In XPath terms, the axis specifier is the identifier before the double colon in Listing 7.59. Like in the abbreviated syntax specification, the wild cards and predicates still apply. The full axis specifiers are defined as follows:

  • child : This references all of the children of the current context.

  • descendant : This references all of the children of the current context (except children of children are included).

  • parent : This references the parent of the current context.

  • ancestor : This references the parents of the current context and which includes the parent of the parent.

  • ancestor-or-self : This references the current context and the parents of the current context, which includes the parent of the parent.

  • following-sibling : This references all of the XML nodes that follow the current context.

  • preceding -sibling : This references all of the XML nodes that precede current context.

  • following : This references all of the XML nodes after the current context.

  • preceding : This references all of the XML nodes before the current context.

  • attribute : This references a specific attribute from the current context.

  • namespace : This contains the namespace nodes of the current context.

  • self : This references the current context.

  • descendant-or-self : This references the current context and all descendants.

  • ancestor-or-self : This references the current context and all ancestors .

The XPath queries illustrated in Listing 7.53 and 7.54 could be rewritten as shown in Listing 7.60.

Listing 7.60
start example
 /child::integerValue /child::myReferenceToAnotherBean/child::integerValue 
end example
 

Defining the Classes to Be Searched

The XPath searches referenced here are based on Listing 7.61, which are a number of classes that reference other classes.

Note

Don't worry about the naming convention too much. The classes Data , Child , Elements , and Sub are not logical identifiers in usage terms. They are named as such to illustrate specific XPath constructs.

Listing 7.61
start example
 public class Data { private Child _child; private Elements _elements; private String _unique; public Data() { _child = new Child(); _elements = new Elements(); _unique = "a unique string property"; } public Child getChild() { return _child; } public Elements getElements() { return _elements; } public String getUnique() { return _unique; } } public class Child { private String _elements; public Child() { _elements = "a string in Child"; } public String getElements() { return _elements; } } public class Elements { private String _elements; private Sub _sub; public Elements() { _elements = "a string in Elements"; _sub = new Sub(); } public String getElements() { return _elements; } public Sub getSub() { return _sub; } } public class Sub { private String _elements; public Sub() { _elements = "a string in Sub"; } public String getElements() { return _elements; } } 
end example
 

Based on Listing 7.61, a generic XPath query and display routine are shown in Listing 7.62.

Listing 7.62
start example
 private void iterate( JXPathContext ctxt, String xpath) { Iterator iter = ctxt.iterate( xpath); while( iter.hasNext()) { Object obj = iter.next(); System.out.println( "Object is " + obj.toString()); } } 
end example
 

In Listing 7.62, the JXPathContext class instance would contain a parse tree of an instance of the class Data from Listing 7.61. An XPath query is executed using the method iterate. However, a single object is not returned from the method; rather, an instance of an iterator is returned. The iterator can be used to go through the collection and inspect each individual found element.

The next set of examples will show a query and will display the output from Listing 7.62 in one listing context. Listing 7.63 is a simple example.

Listing 7.63
start example
 Query: child::* Object is com.devspace.jseng.collections.Child@89cf1e Object is com.devspace.jseng.collections.Elements@982589 Object is a unique string property 
end example
 

In Listing 7.63, the query is a simple iteration of all the child elements of the class Data . Notice that the results contain a reference to the object exposed by the properties. The last output is a text and not an object reference because the object is a string. The same result would be returned if the XPath queries were changed to the various combinations shown in Listing 7.64.

Listing 7.64
start example
 /child::* 
end example
 

Listing 7.65 illustrates a query that iterates the child nodes and then finds specific child properties.

Listing 7.65
start example
 Query: /child::*/child::elements Object is a string in Child Object is a string in Elements 
end example
 

The XPath illustrated in Listing 7.65 is drill-down example query. The first part of the query (between the two slashes ) instructs the XPath query processor to generate a collection of all properties from the class Data . Looking back to the results of Listing 7.63, this would mean that the collection has three elements. Then, the second part of the query (after the second slash) instructs the XPath query processor to retrieve all properties that are identified by the name elements . The result would be two string objects. It is important to remember that the XPath always returns a reference to an object that represents the value of the property. This means that if an object is returned, it is not possible to know who the parent or owner of the object is, since it was picked from the hierarchy.

Listing 7.66 shows a similar query to Listing 7.65.

Listing 7.66
start example
 Query: /child::*/descendant::elements Object is a string in Child Object is a string in Elements Object is a string in Sub 
end example
 

The queries in Listing 7.65 and 7.66 look similar, but there is a big difference in selection. The axis specifier child in Listing 7.65 says to search only the objects in the collection. On the other hand, the axis specifier descendant in Listing 7.66 says to search the objects in the collection and the objects that they reference. The descendent specifier is a sort of recursion that says, "In the selected directories, find something including all subdirectories." This accounts for the difference because the class Data references the class Elements , which references the class Sub .

Listing 7.67 is a query where mentally you expect one answer, but in fact another answer is output.

Listing 7.67
start example
 Query: /child::*/child::*[1] Object is a string in Child Object is a string in Elements Object is 97 
end example
 

In Listing 7.67, the query says, for the main class Data select all properties, which resulted in the output of Listing 7.63. Then, from that collection return the first element, which, according to Listing 7.64, would be the properties of the class Child . Listing 7.67 outputs the desired properties of the class Child , but it also outputs some other pieces of data because the XPath does not say what is requested . The XPath says, get all of the child properties and from those properties retrieve the first property of each element. The number 97 would seem a bit out of place, but it is the first property of the class String . Listing 7.68 is the query that was originally requested.

Listing 7.68
start example
 Query: /child::child/child::*[1] Object is a string in Child 
end example
 

Another interesting query is shown in Listing 7.69. This shows how to select one set of properties, but display the values that follow the selection.

Listing 7.69
start example
 Query: /child::child/following::elements Object is a string in Elements Object is a string in Sub Object is com.devspace.jseng.collections.Elements@982589 
end example
 

In Listing 7.69, the query selects the class Child , but the axis specifier following says select the properties following the class Child . That would then select the property elements from the classes Data , Element , and Sub . The axis specifiers descendant , preceding , and following are powerful specifiers used to select properties located on another property. If you're unsure what each specifier selects, experiment and go back to the Navigating the Hierarchy section for details.

Figuring Out the Location

One of the problems with XPath is that it's difficult to figure out what the parent node of a result is. Using the XML DOM, this is not that difficult because each resulting node has references to its parent, child, and siblings. To get around this problem, the JXPath package has exposed the notion of pointers. Pointers are XPath references that specify the location of a found element. Using Listing 7.61 as a basis, Listing 7.70 shows how to find the paths of found element properties.

Listing 7.70
start example
 Data bean = new Data(); JXPathContext ctxt = JXPathContext.newContext( bean); Pointer ptr = ctxt.getPointer( "//elements"); System.out.println( "Pointer: " + ptr.asPath()); Iterator iterPtr = ctxt.iteratePointers( "//elements"); while( iterPtr.hasNext()) { Object obj = iterPtr.next(); System.out.println( "Pointer is " + obj.toString()); } 
end example
 

In Listing 7.70, two methods are used to retrieve the found references from the XPath query. The method getPointer retrieves an individual reference regardless of the number of references that actually exist. If no references exist, then a null is returned. If one or more references exist, then the first reference in the collection is returned. The returned value is a Pointer class instance. Using the method asPath , the textual value of the XPath reference is retrieved. If there are multiple found references, an iterator is retrieved using the method iteratorPointers . Using the iterator, you can manually go through each found XPath reference.

Collections and XPath Referencing

All of the JXPath package sample queries shown used a simple notation where the object hierarchy was completely composed of single objects referencing other single objects. This is not the typical case; more likely, the object hierarchies will include references to arrays and collections. When using JXPath to query object hierarchies that include arrays and collections, you don't need to change the program structure. It is only necessary to change the query as shown in Listing 7.71.

Listing 7.71
start example
 BeanProperties bean = new BeanProperties(); JXPathContext ctxt = JXPathContext.newContext( bean); Object item = ctxt.getValue("/child::beanArray[ 1]"); 
end example
 

In Listing 7.71, the array property beanArray is referenced using the array predicate in the XPath expression. The XPath expression will return only one object, and that is assigned to the variable item . JXPath supports the types Vector , Collection , List , and Set .

Assigning Properties

You can also assign properties using JXPath . The code used to assign a property is similar to that for getting the properties. Listing 7.72 shows how to assign a property that was retrieved in the Listing 7.53.

Listing 7.72
start example
 ParentBean bean = new ParentBean(); JXPathContext ctxt = JXPathContext.newContext( bean); ctxt.setValue( "/myReferenceToAnotherBean/integerValue", new Integer( 4567)); 
end example
 

In Listing 7.72, the property is assigned using the method setValue , which has two parameters. The first parameter is the XPath query used to find the individual properties. The second parameter identifies the object that will be assigned to the properties found. This second parameter is part of a potential problem. If an XPath query finds multiple items, then each of those found items will be assigned the same object. The problem with this is that the program might assume that each assigned property has a unique value. In the case of Listing 7.72, this is not a problem because the property is a primitive.

Compiling an XPath Expression

In all of the examples shown thus far, the XPath query was dynamically compiled. It is possible with JXPath to precompile the expression for use at a later time. Listing 7.73 shows how to create and use a precompiled XPath query.

Listing 7.73
start example
 Data bean = new Data(); JXPathContext ctxt = JXPathContext.newContext( bean); CompiledExpression compiled = ctxt.compile( "//elements"); Iterator iter = compiled.iterate( ctxt); 
end example
 

In Listing 7.73, the XPath is compiled using the method compile. The returned CompiledExpression class instance exposes the same method as the class JXPathContext . The difference, though, is that the parse tree is already compiled. To execute a query, you need to associate the compiled expression with a bean context. (Note that what was compiled was not the bean context, but the XPath expression.) The method iterate creates an iterator that contains the found items.




Applied Software Engineering Using Apache Jakarta Commons
Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
ISBN: 1584502460
EAN: 2147483647
Year: 2002
Pages: 109

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net