12.4. Applying XPath Queries to Complex Object Graphs

Problem

You need to use XPath expressions to select objects from a complex object graph referencing the contents of a Map and using expressions with variable references.

Solution

Use Jakarta Commons JXPath to select objects from a collection using XPath queries. The following example uses Commons Digester to parse an XML file into an object graph, and selects Planet objects with a radius greater than 5000:

import org.apache.commons.digester.Digester;
import org.apache.commons.digester.xmlrules.DigesterLoader;
import org.apache.commons.jxpath.JXPathContext;

List planets = new ArrayList( );

// Parse Planet XML into a List of Planet objects
InputStream input = getClass( ).getResourceAsStream("./planets.xml");
URL rules = getClass( ).getResource("./planet-digester-rules.xml");
Digester digester = DigesterLoader.createDigester(rules);
digester.push(planets);
digester.parse( input );

// Select all planets with a radius greater than 5000
System.out.println( "Planet Name where radius > 5000");
JXPathContext context = JXPathContext.newContext( planets );
Iterator iterator = context.iterate(".[@radius > 5000]/name");
while( iterator.hasNext( ) ) {
    Object o  = (Object) iterator.next( );
    System.out.println( "Object: " + o);
}

The Planet objects are filtered and the names of planets with sufficient radii are printed to the console:

Planet Name where radius > 5000
Object: Venus
Object: Saturn

This object graph was created from an XML document that contains a list of planets and their physical properties including mass, radius, atmospheric composition, lunar population, and orbital distance and period. Commons Digester (from Chapter 6) is used to parse this XML document to a list of Planet objects. The following XML document, planets.xml, was parsed in the previous example:

<planets>
    <planet name="Venus" mass="4.869e+24" radius="6051.8">
        <orbit distance="108200000" period="224.701"/>
        <atmosphere meanTemp="482" pressure="92">
            <component symbol="CO2" percentage="96"/>
            <component symbol="N" percentage="3"/>
            <component symbol="Other" percentage="1"/>
        </atmosphere>
    </planet>
    <planet name="Mars" mass="6.421e+23" radius="3397.2">
        <orbit distance="227940000" period="686.98"/>
        <atmosphere meanTemp="-63" pressure="0.007">
            <component symbol="CO2" percentage="95.32"/>
            <component symbol="N2" percentage="2.7"/>
            <component symbol="Ar" percentage="1.6"/>
        </atmosphere>
        <moon>Phobos</moon>
        <moon>Deimos</moon>
    </planet>
    <planet name="Saturn" mass="5.688e+26" radius="60268">
        <orbit distance="1429400000" period="29.458"/>
        <atmosphere meanTemp="-125" pressure="1.4">
            <component symbol="H" percentage="97"/>
            <component symbol="He" percentage="3"/>
        </atmosphere>
        <moon>Pan</moon>
        <moon>Atlas</moon>
        <moon>Prometheus</moon>
        <moon>Pandora</moon>
    <moon>Epimetheus</moon>
    <moon>Janus</moon>
    <moon>Mimas</moon>
  </planet>
</planets>

To parse this XML document, the following Digester rules are passed to DigesterLoader.createDigester() . This digester rule-set creates a Planet, Orbit, and Atmosphere object for each planet. Moon objects are created and added to Planet objects using the addMoon( ) method on Planet. Individual compounds in an atmosphere are added to an Atmosphere’s component Map using addComponent( ) . The following XML document contains the contents of the planet-digester-rules.xml file used in the previous example:

<digester-rules>
    <pattern value="planets/planet">
        <object-create-rule classname="Planet"/>
        <set-properties-rule/>
        <pattern value="orbit">
            <object-create-rule classname=" Orbit"/>
            <set-properties-rule/>
            <set-next-rule methodname="setOrbit" paramtype=" Orbit"/>
        </pattern>
        <pattern value="atmosphere">
            <object-create-rule classname="Atmosphere"/>
            <set-properties-rule/>
            <pattern value="component">
                <call-method-rule methodname="addComponent" paramcount="2"
                              paramtypes="java.lang.String,java.lang.Double"/>
                <call-param-rule attrname="symbol" paramnumber="0"/>    
                <call-param-rule attrname="percentage" paramnumber="1"/>
            </pattern>
            <set-next-rule methodname="setAtmosphere"         
                           paramtype=" Atmosphere"/>
        </pattern>
        <call-method-rule pattern="moon" methodname="addMoon" 
                          paramtypes="java.lang.String" paramcount="0"/>    
        <set-next-rule methodname="add" paramtype="java.lang.Object"/>
    </pattern>
</digester-rules>

Discussion

XPath expressions can be parameterized by referencing a variable name: $variable. The following example references a variable $moonName, which is populated by calling declareVariable( ) on context.getVariables( ):

System.out.println( "Planet Name where a moon is named Deimos");
context.getVariables( ).declareVariable("moonName", "Deimos");
iterator = context.iterate("./moons[. = $moonName]/../name");
while( iterator.hasNext( ) ) {
    String name  = (String) iterator.next( );
    System.out.println( "Planet Namet: " + name);
}

This example selects the name of a planet with a moon named “Deimos.” The results of the previous example are printed below:

Planet Name where a moon is named Deimos
Planet Namet: Mars

Planet.getAtmosphere( ).getComponents( ) returns a Map with element symbols as keys. The following example selects every planet with more than a 2% Helium atmosphere:

System.out.println( "Planet where Helium percentage greater than 2");
iterator = context.iterate("./atmosphere/components/He[.>2]/../../..");
while( iterator.hasNext( ) ) {
    Planet p  = (Planet) iterator.next( );
    System.out.println( "Planet: " + p.getName( ));
}

To select every planet with more than a 2% Helium atmosphere, the XPath expression in the previous example references a specific key in the components Map as if it were a nested element. components/He[.>2] will evaluate to true if getComponents( ).get("He") is a number larger than 2. The previous code determines that Saturn is the only one of these three planets with more than 2% Helium:

Planet where Helium percentage greater than 2
Planet: Saturn

The following example prints a list of each moon and the corresponding planet using a reference to a variable in an XPath expression:

System.out.println( "All of the Moon Names");
iterator = context.iterate("./moons");
while( iterator.hasNext( ) ) {
    String moon  = (String) iterator.next( );
    context.getVariables( ).declareVariable("moonName", moon);
    String planet = 
        (String) context.getValue("./moons[. = $moonName]/../name");
    System.out.println( "Moon: " + moon + ", 			Planet: " + planet);
}

The previous example shows that a JXPathContext can be reused. This example iterates through each moon and finds the name of the planet corresponding to each moon using the results of the first expression to populate a variable in the second expression. The XPath expression, ./moons[. = $moonName]/../name, contains a reference to the variable $moonName, which is set by passing a variable name and value to the declareVariable( ) method on JXPathContext. This example prints each moon and planet to the console as follows:

All of the Moon Names
Moon: Phobos,          Planet: Mars
Moon: Deimos,          Planet: Mars
Moon: Pan,             Planet: Saturn
Moon: Atlas,           Planet: Saturn
Moon: Prometheus,      Planet: Saturn
Moon: Pandora,         Planet: Saturn
Moon: Epimetheus,      Planet: Saturn
Moon: Janus,           Planet: Saturn
Moon: Mimas,           Planet: Saturn

See Also

There is much more to JXPath including object creation and the ability to set properties using XPath expressions; for more information about using JXPath to access maps, collections, servlet contexts, and DOM/JDOM Documents, see the JXPath User’s Guide (http://jakarta.apache.org/commons/jxpath/users-guide.html).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.9.124