Development Techniques


Now that you can create collections and populate them with data, let’s look at how you gain access to that data from a Java program. We’ll do this by looking at the way Xindice implements the various features of the XML:DB API. Remember that in order to run any of these examples, you need to start a copy of the Xindice server.

XML:DB API

First we’ll look at how you can create a new Collection using the XML:DB APIs and the Xindice database driver.

Creating Collections

CreateCollectionTree creates a tree of Collections rooted at /db (the Xindice database root). You specify the tree by providing a UNIX-style directory path that corresponds to the Collection tree. The string "product/inventory/books" causes the creation of three Collections: books, nested inside inventory, nested inside product, which is under the Xindice root /db. This example demonstrates the basics of creating a Collection and using a service:

  1: /*   2:  *    3:  * CreateCollectionTree.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9:   10: import org.xmldb.api.DatabaseManager;  11: import org.xmldb.api.base.Collection;  12: import org.xmldb.api.base.Database;  13: import org.xmldb.api.base.XMLDBException;  14: import org.xmldb.api.modules.CollectionManagementService;  15:   16: public class CreateCollectionTree {  17:   18:     public static void main(String[] args) {  19:         String driver =   20:             "org.apache.xindice.client.xmldb.DatabaseImpl";  21:         Class c = null;  22:         try {  23:             c = Class.forName(driver);  24:         } catch (ClassNotFoundException cnfe) {  25:             cnfe.printStackTrace();  26:         }  27:   28:         Database db = null;  29:         try {  30:             db = (Database) c.newInstance();  31:         } catch (InstantiationException ie) {  32:             ie.printStackTrace();  33:         } catch (IllegalAccessException iae) {  34:             iae.printStackTrace();  35:         }

The usage model for Xindice is similar to the usage model for JDBC, version one. First, you supply the fully qualified classname of an NXD database driver (as a String). The documentation of the database will tell you the name of this class. For Xindice, the class is org.apache.xindice.client.xmldb .DatabaseImpl. You then use reflection to load the driver by calling Class.forName to get the java.lang.Class object for the database driver. Finally, you get a class that implements the Database interface by calling the Class.newInstance method on the database driver Class object.

Before you can perform any database operations, you need to register the Database object with the XML:DB Database manager. Once the database is registered, you can obtain the root Collection by calling the DatabaseManager’s getCollection method with an XML:DB URI:

 36:   37:         Collection root = null;  38:         try {  39:             DatabaseManager.registerDatabase(db);  40:             root = DatabaseManager.getCollection(  41:                        "xmldb:xindice:///db");  42:         } catch (XMLDBException xde) {  43:             xde.printStackTrace();  44:         }  45:         

The format of an XML:DB is the URI scheme (xmldb) followed by a colon, followed by a vendor string (in this case, "xindice"), followed by ://. The next position in the URI allows you to specify a hostname and portname, just like an HTTP URI. This allows you to access a database running on a different machine and/or port than the current machine. If you want to use the default values (as you’re doing in this example), you can omit the hostname:portname—the host will default to the localhost, and the port will default to 4080. After the hostname:portname (even if it’s empty), you provide a / and the path to the Collection you’re interested in.

Next you split the supplied pathname into an array that contains the Collections that need to be created:

 46:         String collections[] = args[0].split("/");  47:         

You’re ready to iterate over the array of Collections:

 48:         Collection currentCollection = root;  49:         

To create or remove Collections, you need to use a CollectionManagementService. If you recall, the XML:DB Core API doesn’t provide a standard facility for creating or removing Collections, but it does provide a standard service for the purpose. The service manages the child Collections for a particular Collection. Because you’re creating a hierarchy of Collections, you need to obtain the CollectionManagementService instance for each Collection that you create in order to create children in that Collection:

 50:         for (int i = 0; i < collections.length; i++) {  51:             CollectionManagementService cms = null;  52:             try {  53:                 cms =  54:                     (CollectionManagementService)   55:                      currentCollection.getService(  56:                         "CollectionManagementService",  57:                         "1.0");  58:             } catch (XMLDBException xde) {  59:                 xde.printStackTrace();  60:             }  61: 

Here you get all the children of the current Collection to check whether a Collection with the desired name is already a child of the current Collection. If it is, you make that child the current Collection and proceed.

 62:             Collection child = null;  63:             try {  64:                 child =   65:                     currentCollection.getChildCollection(  66:                         collections[i]);  67:             } catch (XMLDBException xde) {  68:                 xde.printStackTrace();  69:             }  70:             if (child != null) {  71:                currentCollection = child;  72:                continue;  73:             }

If a child Collection with the requested name doesn’t exist, then you use the createCollection method on the CollectionManagementService to create one with the right name:

 74:   75:             try {  76:                 currentCollection =   77:                     cms.createCollection(collections[i]);  78:             } catch (XMLDBException xde) {  79:                 xde.printStackTrace();  80:             }  81:                   82:         }          83:     }  84: }

Navigating Collections

The next example finds the first Collection that has a particular name. It does this by performing a recursive depth-first search of the entire tree of Collections, starting at the database root. This example shows how to perform navigation within the Collection tree:

  1: /*   2:  *    3:  * FindCollection.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9:   10: import org.xmldb.api.DatabaseManager;  11: import org.xmldb.api.base.Collection;  12: import org.xmldb.api.base.Database;  13: import org.xmldb.api.base.XMLDBException;  14:   15: public class FindCollection {  16:     public static void main(String[] args) {  17:         String driver =   18:             "org.apache.xindice.client.xmldb.DatabaseImpl";  19:         Class c = null;  20:         try {  21:             c = Class.forName(driver);  22:         } catch (ClassNotFoundException cnfe) {  23:             cnfe.printStackTrace();  24:         }  25:           26:         Database db = null;  27:         try {  28:             db = (Database) c.newInstance();  29:         } catch (InstantiationException ie) {  30:             ie.printStackTrace();  31:         } catch (IllegalAccessException iae) {  32:             iae.printStackTrace();  33:         }  34:           35:         Collection root = null;  36:         try {  37:             DatabaseManager.registerDatabase(db);  38:             root = DatabaseManager.getCollection(  39:                       "xmldb:xindice:///db");  40:         } catch (XMLDBException xde) {  41:             xde.printStackTrace();  42:         }

Before you can perform any database operations, you need to load the database driver and initialize the DatabaseManager. After that has been completed, you’re ready to go searching for the Collection. The name of the Collection to find is taken from the command-line arguments.

The work of finding the desired connection is the findCollection method’s job. It checks to see whether its Collection argument has the desired name. If not, it searches the children of its argument to see if one of them matches. This process continues recursively until all the Collections have been checked or a match is found:

 43:           44:         try {  45:             Collection result = findCollection(root, args[0]);  46:             if (result != null)  47:                 System.out.println("Found: "+result.getName());  48:         } catch (XMLDBException xde) {  49:             xde.printStackTrace();  50:         }  51:     }  52:     

This is the base case of the recursion. If the current Collection has the desired name, then it’s returned as the result of findCollection. If it doesn’t match, you proceed:

 53:     public static Collection findCollection(Collection c,   54:                                              String name)   55:         throws XMLDBException {  56:         if (c.getName().equals(name))  57:             return c;

In preparation for searching the children, you call listChildCollections to obtain an array containing the names of all the child Collections:

 58:         String collectionIds[] = c.listChildCollections();

You then loop over the array of child names, obtain the corresponding Collection via getChildCollection, and call findCollection using the child Collection as the argument to findCollection. If a Collection with the right name is found by searching a child Collection, then it’s returned as the result of findCollection:

 59:         for (int i = 0; i < collectionIds.length; i++) {  60:             String childName = collectionIds[i];  61:             Collection child = c.getChildCollection(childName);  62:             Collection result= findCollection(child, name);  63:             if (result != null)  64:                 return result;  65:         }  66:         return null;  67:     }  68: }

Deleting Collections

In the next example, we’ll tackle the problem of deleting a subtree of the Collection tree. In addition to deleting Collections, we’ll also see how to enumerate and delete the resources stored in a Collection:

  1: /*   2:  *    3:  * DeleteCollectionTree.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9:   10: import org.xmldb.api.DatabaseManager;  11: import org.xmldb.api.base.Collection;  12: import org.xmldb.api.base.Database;  13: import org.xmldb.api.base.Resource;  14: import org.xmldb.api.base.XMLDBException;  15: import org.xmldb.api.modules.CollectionManagementService;  16:   17: public class DeleteCollectionTree {  18:   19:     public static void main(String[] args) {  20:         String driver =   21:             "org.apache.xindice.client.xmldb.DatabaseImpl";  22:         Class c = null;  23:         try {  24:             c = Class.forName(driver);  25:         } catch (ClassNotFoundException cnfe) {  26:             cnfe.printStackTrace();  27:         }  28:   29:         Database db = null;  30:         try {  31:             db = (Database) c.newInstance();  32:         } catch (InstantiationException ie) {  33:             ie.printStackTrace();  34:         } catch (IllegalAccessException iae) {  35:             iae.printStackTrace();  36:         }  37:   38:         Collection root = null;  39:         try {  40:             DatabaseManager.registerDatabase(db);  41:             root = DatabaseManager.getCollection(  42:                        "xmldb:xindice:///db");  43:         } catch (XMLDBException xde) {  44:             xde.printStackTrace();  45:         }

As usual, you begin with the database driver initialization and registration.

The subtree to delete is specified as a UNIX like path, separated by /. You split this into an array of pathnames so the XML:DB methods can use it. To delete a Collection, you need to use the CollectionManagementService. As you’ve already seen, this service provides methods that operate on the current Collection, so you need to navigate to the Collection that’s the root of the subtree you’re interested in deleting:

 46:        47:         String collections[] = args[0].split("/");  48:         Collection current = root;  49:         

You iterate over the list of Collection names and call getChildCollection on the Collection names to get to the correct location:

 50:         for (int i = 0; i < collections.length; i++) {  51:             try {  52:                 current = current.getChildCollection(  53:                             collections[i]);  54:             } catch (XMLDBException xde) {  55:                 xde.printStackTrace();  56:             }  57:         }

Once you’ve reached that location, you can call the deleteChildren method on that Collection, which does the work of deleting the subtree:

 58:           59:         deleteChildren(current);  60:     }  61:     

Before you can delete a Collection, you need to delete any child Collections and delete any resources the Collection contains. In line 65, you get an array containing the names of each child Collection, and in lines 69-78 you iterate over that array and remove all the child collections:

 62:     public static void deleteChildren(Collection c) {  63:         String children[] = null;  64:         try {  65:             children = c.listChildCollections();  66:         } catch (XMLDBException e) {  67:             e.printStackTrace();  68:         }  69:         for (int i = 0; i < children.length; i++) {  70:             String childName = children[i];  71:             Collection child = null;  72:             try {  73:                 child = c.getChildCollection(childName);  74:             } catch (XMLDBException e1) {  75:                 e1.printStackTrace();  76:             }  77:             deleteChildren(child);  78:        }  79: 

Once all the child Collections have been removed, you can remove any documents in the Collections:

 80:         deleteDocuments(c);

Now the Collection is completely empty, and you can remove it. To remove the Collection itself, you need to get the CollectionManagementService for its parent and then remove the Collection from the parent by supplying its name:

 81:         try {  82:             CollectionManagementService cms;  83:             Collection parent = c.getParentCollection();  84:             cms =  85:                 (CollectionManagementService) parent.getService(  86:                     "CollectionManagementService",  87:                     "1.0");  88:             cms.removeCollection(c.getName());  89:         } catch (XMLDBException e2) {  90:             e2.printStackTrace();  91:         }

To remove all the documents in a Collection, you call listResources to get a list of the names of all the Resources in the Collection. Then you iterate over that list and remove each Resource by name:

 92:     }  93:   94:     private static void deleteDocuments(Collection c) {  95:         String docs[];  96:         try {  97:             docs = c.listResources();  98:             for (int i = 0; i < docs.length; i++) {  99:                 Resource r; 100:                 r = c.getResource(docs[i]); 101:                 c.removeResource(r); 102:             } 103:         } catch (XMLDBException e) { 104:             e.printStackTrace(); 105:         } 106:          107:     } 108: }

Working with Resources

In the last example, you got your first taste of working with Resources—they’re what you want to work with, because they contain the XML data. In the next few examples, we’ll look at how to create Resources from existing XML data and how to retrieve the contents of Resources in the database.

String-based I/O

The first of these examples loads the XML data from a file into a Resource and then prints the data from that Resource onto the standard output. It takes three command-line arguments: a UNIX style path that specifies the Collection in which the Resource should be created, the name of a file containing the XML data you want to put into the Resource, and the name you want to give the newly created Resource:

  1: /*   2:  *    3:  * LoadDumpMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9:   10: import java.io.BufferedReader;  11: import java.io.FileNotFoundException;  12: import java.io.FileReader;  13: import java.io.IOException;  14:   15: import org.xmldb.api.DatabaseManager;  16: import org.xmldb.api.base.Collection;  17: import org.xmldb.api.base.Database;  18: import org.xmldb.api.base.Resource;  19: import org.xmldb.api.base.XMLDBException;  20:   21: public class LoadDumpMain {  22:   23:     public static void main(String[] args) {  24:         String driver =   25:             "org.apache.xindice.client.xmldb.DatabaseImpl";  26:         Class c = null;  27:         try {  28:             c = Class.forName(driver);  29:         } catch (ClassNotFoundException cnfe) {  30:             cnfe.printStackTrace();  31:         }  32:   33:         Database db = null;  34:         try {  35:             db = (Database) c.newInstance();  36:         } catch (InstantiationException ie) {  37:             ie.printStackTrace();  38:         } catch (IllegalAccessException iae) {  39:             iae.printStackTrace();  40:         }  41:   42:         Collection root = null;  43:         try {  44:             DatabaseManager.registerDatabase(db);  45:             root = DatabaseManager.getCollection(  46:                        "xmldb:xindice:///db");  47:         } catch (XMLDBException xde) {  48:             xde.printStackTrace();  49:         }  50:   51:         String collections[] = args[0].split("/");  52:         Collection current = root;  53:   54:         for (int i = 0; i < collections.length; i++) {  55:             try {  56:                 current =   57:                     current.getChildCollection(collections[i]);  58:             } catch (XMLDBException xde) {  59:                 xde.printStackTrace();  60:             }  61:         }

The code up to this point should be familiar; it initializes the database and navigates to the correct Collection. Next you call a pair of methods that do the work of loading and dumping the document:

 62:   63:         loadDocument(current, args[1], args[2]);  64:         dumpDocument(current, args[2]);  65:     }  66:   67:     private static void loadDocument(  68:         Collection c,  69:         String file,  70:         String name) {  71:         Resource document = null;  72:         try {  73:             document = c.createResource(name, "XMLResource");

You need to create an XMLResource (in the right Collection) with the right name so that you can store your XML data in it. You have to specify the type of resource as a String argument to the createResource method ("XMLResource" in this case).

In lines 78-94 you create a FileReader to access the XML file and use the readLine method of BufferedReader to read all the lines of the XML file into a StringBuffer:

 74:         } catch (XMLDBException xde) {  75:             xde.printStackTrace();  76:         }  77:           78:         BufferedReader br = null;  79:         try {  80:             br = new BufferedReader(new FileReader(file));  81:         } catch (FileNotFoundException fnfe) {  82:             fnfe.printStackTrace();  83:         }  84:               85:         StringBuffer data = new StringBuffer();  86:         String line;  87:           88:         try {  89:             while ((line = br.readLine()) != null) {  90:                 data.append(line);   91:             }  92:         } catch (IOException ioe) {  93:             ioe.printStackTrace();  94:         }  95:         

The setContent method on XMLResource is happy to accept a String containing an XML document, which you can easily provide by calling toString on the StringBuffer. After that, you call the storeResource method on your Collection and pass it the filled-in XMLResource to make the data persistent:

 96:         try {  97:             document.setContent(data.toString());  98:             c.storeResource(document);

The contents of dumpDocument are straightforward. Given the correct Collection and the name of a Resource, you call getResource with the name to get the Resource you want. As long as the Resource exists, you can call the getContent method. The getContent method on XMLResource returns the contents of the Resource as a String. All you have to do is print that String on the standard output:

 99:         } catch (XMLDBException xde) { 100:             xde.printStackTrace(); 101:         } 102:     } 103:  104:     private static void dumpDocument(Collection c,  105:                                        String name) { 106:         try { 107:             Resource document = c.getResource(name); 108:              109:             if (document != null) 110:                 System.out.println(document.getContent()); 111:         } catch (XMLDBException xde) { 112:             xde.printStackTrace(); 113:         } 114:  115:     } 116:  117: }

SAX-based I/O

The standard getContent and setContent methods are convenient if you have XML data lying around in files or if you construct XML data as character strings. However, you’ll probably want to use Xindice as part of a much larger system that is processing XML data. If this is the case, it’s quite likely that some other part of the system will already have parsed the XML data, which means your system is probably dealing with an internal representation of XML as either a SAX event stream or a DOM tree. It would be horribly inefficient to convert a SAX event stream or a DOM tree back into an XML document so that you could store it in Xindice, and then have to parse documents stored in Xindice back into a SAX event stream or a DOM tree. In the next two examples, you’ll take LoadDumpMain and modify it to work in a system where either SAX or DOM is the preferred representation for XML. Let’s start with the SAX version:

  1: /*   2:  *    3:  * LoadDumpSAXMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9:   10: import java.io.IOException;  11:   12: import javax.xml.parsers.ParserConfigurationException;  13: import javax.xml.parsers.SAXParserFactory;  14: import javax.xml.transform.TransformerConfigurationException;  15: import javax.xml.transform.TransformerFactory;  16: import javax.xml.transform.sax.SAXResult;  17: import javax.xml.transform.sax.SAXTransformerFactory;  18: import javax.xml.transform.sax.TransformerHandler;  19: import javax.xml.transform.stream.StreamResult;  20:   21: import org.xml.sax.ContentHandler;  22: import org.xml.sax.SAXException;  23: import org.xml.sax.XMLReader;  24: import org.xmldb.api.DatabaseManager;  25: import org.xmldb.api.base.Collection;  26: import org.xmldb.api.base.Database;  27: import org.xmldb.api.base.XMLDBException;  28: import org.xmldb.api.modules.XMLResource;  29:   30: public class LoadDumpSAXMain {  31:   32:     public static void main(String[] args) {  33:         String driver =   34:             "org.apache.xindice.client.xmldb.DatabaseImpl";  35:         Class c = null;  36:         try {  37:             c = Class.forName(driver);  38:         } catch (ClassNotFoundException cnfe) {  39:             cnfe.printStackTrace();  40:         }  41:   42:         Database db = null;  43:         try {  44:             db = (Database) c.newInstance();  45:         } catch (InstantiationException ie) {  46:             ie.printStackTrace();  47:         } catch (IllegalAccessException iae) {  48:             iae.printStackTrace();  49:         }  50:   51:         Collection root = null;  52:         try {  53:             DatabaseManager.registerDatabase(db);  54:             root = DatabaseManager.getCollection(  55:                       "xmldb:xindice:///db");  56:         } catch (XMLDBException xde) {  57:             xde.printStackTrace();  58:         }  59:   60:         String collections[] = args[0].split("/");  61:         Collection current = root;  62:   63:         for (int i = 0; i < collections.length; i++) {  64:             try {  65:                 current =   66:                     current.getChildCollection(collections[i]);  67:             } catch (XMLDBException xde) {  68:                 xde.printStackTrace();  69:             }  70:         }  71:   72:         loadDocument(current, args[1], args[2]);  73:         dumpDocument(current, args[2]);  74:     }  75:  

The differences from LoadDumpMain are in the implementation of the loadDocument and dumpDocument methods. First you create your XML resource, in line 83:

 76:     private static void loadDocument(  77:         Collection c,  78:         String file,  79:         String name) {  80:         XMLResource document = null;  81:         try {  82:             document = (XMLResource)  83:                 c.createResource(name, "XMLResource");  84:         } catch (XMLDBException xde) {  85:             xde.printStackTrace();  86:         }  87:         

If you’re familiar with SAX, the only mildly trick part is at line 90:

 88:             89:         try {  90:             ContentHandler h = document.setContentAsSAX();

To set the contents of an XMLResource using SAX, you call the method setContentAsSax. This method takes no arguments, which isn’t typical of a method that’s supposed to set the contents of an object. But that’s because of the way SAX works. In SAX, all the work is done by event handlers, which means the work of putting XML data into the XMLResource has to be done by an event handler. So, setContentAsSAX returns a SAX event handler, which you can then plug into a SAXParser or XMLReader. When you tell that instance to parse, the event handler populates the Resource.

In lines 92-97, that’s exactly what you do. You use the JAXP SAXParserFactory to get a SAXParser, which you treat as an XMLReader. The content handler you got from setContentHandlerAsSAX is set as this parser’s content handler. After that, you call parse, and the XMLResource is loaded:

 91:               92:             SAXParserFactory spf = SAXParserFactory.newInstance();  93:             spf.setNamespaceAware(true);  94:   95:             XMLReader r = spf.newSAXParser().getXMLReader();  96:             r.setContentHandler(h);  97:             r.parse(file);

You call storeResource to finish the process and make the XMLResource persistent:

 98:               99:             c.storeResource(document); 100:         } catch (XMLDBException xde) { 101:             xde.printStackTrace(); 102:         } catch (ParserConfigurationException pce) { 103:             pce.printStackTrace(); 104:         } catch (SAXException se) { 105:             se.printStackTrace(); 106:         } catch (IOException ioe) { 107:             ioe.printStackTrace(); 108:         } 109:     } 110: 

The internals of dumpDocument have undergone a major overhaul as well:

111:     private static void dumpDocument(Collection c,  112:                                        String name) { 113:         try { 114:             XMLResource document = (XMLResource)  115:                 c.getResource(name); 116:             

After you get the XMLResource, the code starts to look different. Again, things may seem a little backward due to SAX’s event handling model. You need to display the XML stored in the Resource on the standard output. So, your content handler needs to be able to do that. You leverage the serialization /identity transformations available via JAXP’s Transformer (XSLT library) to get a SAX event handler that serializes a SAX event stream into an XML document. The Resource will call the content handler.

You create the JAXP TransformerFactory in lines 118-120 and downcast it to a SAXTransformerFactory (which you need in order to obtain handlers that you can plug into SAX). You use that factory to create a TransformerHandler, which you can use as a content handler for the Resource:

117:             if (document != null){ 118:                 TransformerFactory xf =  119:                     TransformerFactory.newInstance(); 120:                 SAXTransformerFactory sxf = null; 121:                  122:                 if (!xf.getFeature(SAXResult.FEATURE)) { 123:                     System.out.println("Bad factory"); 124:                     return; 125:                 } 126:                 sxf = (SAXTransformerFactory) xf; 127:                 TransformerHandler serializer =  128:                     sxf.newTransformerHandler();

Because the output of the TransformerHandler goes to the standard output, you create a JAXP StreamResult around System.out and make that StreamResult the destination for the TransformerHandler:

129:                 StreamResult result =  130:                     new StreamResult(System.out); 131:                 serializer.setResult(result);

Calling the getContentAsSAX method and supplying your serializer causes the Resource to generate SAX events and invoke the callback methods on the TransformerHandler, which prints the document to standard output:

132:  133:                 document.getContentAsSAX((ContentHandler)  134:                                           serializer); 135:             } 136:         } catch (XMLDBException e) { 137:             e.printStackTrace(); 138:         } catch (TransformerConfigurationException tce) { 139:             tce.printStackTrace(); 140:         } 141:  142:     } 143:  144: }

There appear to be a few bugs with the SAX functionality of XMLResource in Xindice 1.0. Some fixes for these bugs have been checked into the Xindice CVS, but they haven’t been rolled into a 1.0 series release. At the time of this writing, the Xindice developers are putting together a 1.1 series release.

DOM-based I/O

As you might expect from your previous examinations of SAX versus DOM, the DOM version is more straightforward:

  1: /*   2:  *    3:  * LoadDumpDOMMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9:   10: import java.io.IOException;  11:   12: import javax.xml.parsers.DocumentBuilder;  13: import javax.xml.parsers.DocumentBuilderFactory;  14: import javax.xml.parsers.ParserConfigurationException;  15: import javax.xml.transform.Transformer;  16: import javax.xml.transform.TransformerConfigurationException;  17: import javax.xml.transform.TransformerException;  18: import javax.xml.transform.TransformerFactory;  19: import javax.xml.transform.dom.DOMResult;  20: import javax.xml.transform.dom.DOMSource;  21: import javax.xml.transform.stream.StreamResult;  22:   23: import org.w3c.dom.Document;  24: import org.w3c.dom.Node;  25: import org.xml.sax.SAXException;  26: import org.xmldb.api.DatabaseManager;  27: import org.xmldb.api.base.Collection;  28: import org.xmldb.api.base.Database;  29: import org.xmldb.api.base.XMLDBException;  30: import org.xmldb.api.modules.XMLResource;  31:   32: public class LoadDumpDOMMain {  33:   34:     public static void main(String[] args) {  35:         String driver =   36:             "org.apache.xindice.client.xmldb.DatabaseImpl";  37:         Class c = null;  38:         try {  39:             c = Class.forName(driver);  40:         } catch (ClassNotFoundException cnfe) {  41:             cnfe.printStackTrace();  42:         }  43:   44:         Database db = null;  45:         try {  46:             db = (Database) c.newInstance();  47:         } catch (InstantiationException ie) {  48:             ie.printStackTrace();  49:         } catch (IllegalAccessException iae) {  50:             iae.printStackTrace();  51:         }  52:   53:         Collection root = null;  54:         try {  55:             DatabaseManager.registerDatabase(db);  56:             root = DatabaseManager.getCollection(  57:                       "xmldb:xindice:///db");  58:         } catch (XMLDBException xde) {  59:             xde.printStackTrace();  60:         }  61:   62:         String collections[] = args[0].split("/");  63:         Collection current = root;  64:   65:         for (int i = 0; i < collections.length; i++) {  66:             try {  67:                 current =   68:                     current.getChildCollection(collections[i]);  69:             } catch (XMLDBException xde) {  70:                 xde.printStackTrace();  71:             }  72:         }  73:   74:         loadDocument(current, args[1], args[2]);  75:         dumpDocument(current, args[2]);  76:     }  77:   78:     private static void loadDocument(  79:         Collection c,  80:         String file,  81:         String name) {  82:         XMLResource document = null;  83:         try {  84:             document = (XMLResource)  85:                 c.createResource(name, "XMLResource");  86:         } catch (XMLDBException xde) {  87:             xde.printStackTrace();  88:         }  89:         

After creating the XMLResource, you’re ready to load the document. You use the JAXP DocumentBuilderFactory to get a namespace-aware DocumentBuilder. Asking this DocumentBuilder to parse the file gives you a document you can pass to the setContentsAsDOM method, which is expecting a Node:

 90:             91:         try {  92:             DocumentBuilderFactory dbf =   93:                 DocumentBuilderFactory.newInstance();  94:             dbf.setNamespaceAware(true);  95:   96:             DocumentBuilder b = dbf.newDocumentBuilder();  97:             Document d = b.parse(file);

Once the content of the Resource has been set from the DOM tree, you invoke storeResource to write the Resource to disk:

 98:   99:             document.setContentAsDOM(d);             100:             c.storeResource(document); 101:         } catch (XMLDBException xde) { 102:             xde.printStackTrace(); 103:         } catch (ParserConfigurationException pce) { 104:             pce.printStackTrace(); 105:         } catch (SAXException se) { 106:             se.printStackTrace(); 107:         } catch (IOException ioe) { 108:             ioe.printStackTrace(); 109:         } 110:     } 111: 

Dumping the document is also easier. You obtain a DOM tree from the XMLResource and then use a JAXP identity transform to serializer the DOM tree to standard output:

112:     private static void dumpDocument(Collection c,  113:                                        String name) { 114:         try { 115:             XMLResource document = (XMLResource)  116:                 c.getResource(name); 117:              118:             if (document != null){ 119:                 TransformerFactory xf =  120:                     TransformerFactory.newInstance(); 121:                  122:                 if (!xf.getFeature(DOMResult.FEATURE)) { 123:                     System.out.println("Bad factory"); 124:                     return; 125:                 } 126:                 Transformer serializer = xf.newTransformer();

You can use the regular TransformerFactory to get a regular Transformer for use as a serializer. Now you have to set up the arguments for the transformer.

You obtain the DOM tree from the XMLResource and use it to create a DOMSource that serves as the input for the Transformer. A StreamResult wrapped around System.out serves as the output for the Transformer. Calling the Transformer’s transform method displays the XML content of the Resource on the standard output:

127:                 Node root = document.getContentAsDOM(); 128:                 DOMSource outputSource = new DOMSource(root); 129:                 StreamResult result =  130:                     new StreamResult(System.out); 131:                 serializer.transform(outputSource, result); 132:             } 133:         } catch (XMLDBException e) { 134:             e.printStackTrace(); 135:         } catch (TransformerConfigurationException tce) { 136:             tce.printStackTrace(); 137:         } catch (TransformerException te) { 138:             te.printStackTrace(); 139:         } 140:  141:     } 142:  143: }

XPath-based Querying

The XML:DB API doesn’t mandate a particular language for querying an NXD, although it does provide for an XPath-based query service (XPathQueryService) as part of the XML:DB Core Level 1 specification. Depending on your point of view, this is a good or bad thing. The positive point of view says that it’s too early in the evolution of querying XML documents to settle on a single language for querying them. The negative point of view says that having multiple query languages is harder for developers because they may have to learn several languages. This point of view also says that the presence of multiple query languages makes it confusing for those trying to select a technology to work with.

The next example shows how to use XML:DB’s Core Level 1 XPath-based query service, XPathQueryService. Most of the hard work is done by the XPath engine, which leaves you to get the service and use it. XPathQueryMain takes two command-line arguments: a collection to query and the XPath expression for the query. If you need to use namespaces, for now you need to change the values hardwired in the code. We leave the exercise of parsing namespace prefix and URI pairs from the command line up to you.

  1: /*   2:  *    3:  * XPathQueryMain.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9:   10: import org.xmldb.api.DatabaseManager;  11: import org.xmldb.api.base.Collection;  12: import org.xmldb.api.base.Database;  13: import org.xmldb.api.base.Resource;  14: import org.xmldb.api.base.ResourceIterator;  15: import org.xmldb.api.base.ResourceSet;  16: import org.xmldb.api.base.XMLDBException;  17: import org.xmldb.api.modules.XPathQueryService;  18:   19: public class XPathQueryMain {  20:   21:     public static void main(String[] args) {  22:         String driver =   23:             "org.apache.xindice.client.xmldb.DatabaseImpl";  24:         Class c = null;  25:         try {  26:             c = Class.forName(driver);  27:         } catch (ClassNotFoundException cnfe) {  28:             cnfe.printStackTrace();  29:         }  30:   31:         Database db = null;  32:         try {  33:             db = (Database) c.newInstance();  34:         } catch (InstantiationException ie) {  35:             ie.printStackTrace();  36:         } catch (IllegalAccessException iae) {  37:             iae.printStackTrace();  38:         }  39:   40:         Collection root = null;  41:         try {  42:             DatabaseManager.registerDatabase(db);  43:             root = DatabaseManager.getCollection(  44:                       "xmldb:xindice:///db");  45:         } catch (XMLDBException xde) {  46:             xde.printStackTrace();  47:         }  48:   49:         String collections[] = args[0].split("/");  50:         Collection current = root;  51:   52:         for (int i = 0; i < collections.length; i++) {  53:             try {  54:                 current =   55:                     current.getChildCollection(collections[i]);  56:             } catch (XMLDBException xde) {  57:                 xde.printStackTrace();  58:             }  59:         }  60:   61:         ResourceSet result = query(current, args[1]);

The query method executes the XPath query (in args[1]) against the current Collection. The result of the query is an XML:DB ResourceSet. You access it using a ResourceIterator. Here you just print out the content of each matching resource:

 62:           63:         try {  64:             for (ResourceIterator ri = result.getIterator();  65:                   ri.hasMoreResources();) {  66:                 Resource r = ri.nextResource();  67:                 System.out.println(r.getContent());

After you’ve obtained the XPathQueryService, you need to do some work to support namespaces if they will be used in the XPath query (lines 81-87). First you clear any namespace mappings in the service instance. Next you use setNamespace to establish a mapping between namespace prefixes (the first argument) and namespace URIs (the second element). When you call the query method on the service instance, any namespace prefixes in the XPath query are resolved using the mappings installed by setNamespace. Here you include all the namespaces defined for the book inventory schema. The last call to setNamespace, with "" as the first argument, sets the default namespace (lines 86-87):

 68:             }  69:         } catch (XMLDBException xde) {  70:             xde.printStackTrace();  71:         }  72:   73:     }  74:   75:     private static ResourceSet query(Collection c,   76:                                        String queryString) {  77:         ResourceSet rs = null;  78:         try {  79:             XPathQueryService qs = (XPathQueryService)  80:                 c.getService("XPathQueryService","1.0");  81:             qs.clearNamespaces();  82:             qs.setNamespace("tns",  83:                "http://sauria.com/schemas/apache-xml-book/books");  84:             qs.setNamespace("xsi",  85:                "http://www.w3.org/2001/XMLSchema-instance");  86:             qs.setNamespace("",  87:                "http://sauria.com/schemas/apache-xml-book/books");  88:             rs = qs.query(queryString);  89:         } catch (XMLDBException xde) {  90:             xde.printStackTrace();  91:         }  92:         return rs;  93:     }  94: }

Using XUpdate

The last example with the XML:DB API demonstrates how to use XUpdate. The Updater class shows how to use the XUpdateQueryService to modify a document in the database using an XUpdate document. Update takes three command-line arguments: the collection containing the document to update, the name of the document in the collection, and the name of an XUpdate file:

  1: /*   2:  *    3:  * Updater.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9:   10: import java.io.BufferedReader;  11: import java.io.FileNotFoundException;  12: import java.io.FileReader;  13: import java.io.IOException;  14:   15: import org.xmldb.api.DatabaseManager;  16: import org.xmldb.api.base.Collection;  17: import org.xmldb.api.base.Database;  18: import org.xmldb.api.base.XMLDBException;  19: import org.xmldb.api.modules.XMLResource;  20: import org.xmldb.api.modules.XUpdateQueryService;  21:   22: public class Updater {  23:     public static void main(String[] args) {  24:         String id = args[2];  25:         String driver =   26:             "org.apache.xindice.client.xmldb.DatabaseImpl";  27:         Class c = null;  28:         try {  29:             c = Class.forName(driver);  30:         } catch (ClassNotFoundException cnfe) {  31:             cnfe.printStackTrace();  32:         }  33:           34:         Database db = null;  35:         try {  36:             db = (Database) c.newInstance();  37:         } catch (InstantiationException ie) {  38:             ie.printStackTrace();  39:         } catch (IllegalAccessException iae) {  40:             iae.printStackTrace();  41:         }  42:           43:         Collection root = null;  44:         try {  45:             DatabaseManager.registerDatabase(db);  46:             String uri = "xmldb:xindice:///db";  47:             if (args[0] != null)  48:                 uri += "/"+args[0];  49:             root = DatabaseManager.getCollection(uri);  50:         } catch (XMLDBException xde) {  51:             xde.printStackTrace();  52:         }

You use a different way of getting to the starting collection in this example. Rather than parse the path provided on the command line, you modify the URI given to the DatabaseManager. This works just as well as the approach you’ve been using.

In lines 54-69, you read the contents of the XUpdate document into a StringBuffer because the XUpdateQueryService needs a String containing the XUpdate command:

 53:   54:         StringBuffer updateBuffer = new StringBuffer();  55:         BufferedReader br;  56:         try {  57:             br = new BufferedReader(new FileReader(args[1]));  58:             String line;  59:               60:             while ((line = br.readLine()) != null) {  61:                 updateBuffer.append(line);  62:             }  63:         } catch (FileNotFoundException fnfe) {  64:             fnfe.printStackTrace();  65:         } catch (IOException ioe) {  66:             ioe.printStackTrace();  67:         }  68:           69:         String update = updateBuffer.toString();

When you’re at the correct collection, you can get an instance of the XQueryUpdateService for that collection and pass the document ID and the XUpdate command as arguments to the updateResource method. This method returns a count of the modified nodes:

 70:         try {  71:             XUpdateQueryService s = (XUpdateQueryService)   72:                 root.getService("XUpdateQueryService", "1.0");  73:             long count = s.updateResource(id, update);

As a form of sanity checking, you print out the update count and the content of the updated Resource:

 74:             System.out.println(count);  75:             XMLResource document =   76:                 (XMLResource) root.getResource(id);  77:             System.out.println(document.getContent());  78:         } catch (XMLDBException xde) {  79:             xde.printStackTrace();  80:         }  81:     }  82: } 

This concludes our tour of examples through the XML:DB APIs. They include quite a bit of functionality. Xindice supports the XML:DB Core Level 1 API and some additional services: the CollectionManagerService and the XUpdateQueryService.




Professional XML Development with Apache Tools. Xerces, Xalan, FOP, Cocoon, Axis, Xindice
Professional XML Development with Apache Tools: Xerces, Xalan, FOP, Cocoon, Axis, Xindice (Wrox Professional Guides)
ISBN: 0764543555
EAN: 2147483647
Year: 2003
Pages: 95

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