Applications


Let’s look at how you can use Xindice in the context of the book inventory program you’ve been building up. In the simplest version of that program, you had a servlet that somehow produced XML, which was then processed by a Java servlet filter to perform an XSLT transform to HTML. The XMLServlet worked by reading a known XML file and returning it as the servlet response. The obvious thing to do is to replace the internal logic of XMLServlet with logic that performs a query against an Xindice database, and then return the results of the query as XML. After that, the processing flow will be as before.

Before we look at the code, let’s talk about what you need to have running on the Xindice side. Of course, you need to have the Xindice server running. The default collection used by these programs is /db/books, so you need to create a collection in Xindice. You can do that as follows:

xindiceadmin ac –c /db –n books

Next you need some data. You can put in as much as you want, but for now, it all has to obey the schema you’re using for the books inventory (books.xml). You can add the sample file like this:

xindiceadmin  ad –c /db –n inventory –f <path to books.xml>/books.xml

XMLServlet: Accessing Xindice

Now that you have a running Xindice database containing some data, you’re ready to modify the XMLServlet class. You’ll change the servlet to behave as follows. An HTTP GET causes a default query to be executed over the books collection. The default query is the XPath /, which returns the entire document. An HTTP POST is allowed to supply two parameters: a collection that’s the name of the collection under /db; and query, which is an XPath query. The response is an XML document whose root element is <result>. Each immediate child of <result> is an XMLResource from the Xindice ResultSet:

  1: /*   2:  *    3:  * XMLServlet.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8:    9: package com.sauria.apachexml.ch7;  10: import java.io.IOException;  11: import java.io.PrintWriter;  12:   13: import javax.servlet.ServletException;  14: import javax.servlet.http.HttpServlet;  15: import javax.servlet.http.HttpServletRequest;  16: import javax.servlet.http.HttpServletResponse;  17:   18: import org.xmldb.api.DatabaseManager;  19: import org.xmldb.api.base.Collection;  20: import org.xmldb.api.base.Database;  21: import org.xmldb.api.base.Resource;  22: import org.xmldb.api.base.ResourceIterator;  23: import org.xmldb.api.base.ResourceSet;  24: import org.xmldb.api.base.XMLDBException;  25: import org.xmldb.api.modules.XPathQueryService;  26:   27: public class XMLServlet extends HttpServlet {  28:     String DEFAULT_COLLECTION="books";  29:     String DEFAULT_QUERY="/";

In lines 28-29, you set the default values for the collection and query, which are used by doGet.

The initDB method contains the usual Xindice database driver setup:

 30:   31:     public Database initDB() {  32:         String driver =   33:             "org.apache.xindice.client.xmldb.DatabaseImpl";  34:         Class c = null;  35:         try {  36:             c = Class.forName(driver);  37:         } catch (ClassNotFoundException cnfe) {  38:             cnfe.printStackTrace();  39:         }  40:   41:         Database db = null;  42:         try {  43:             db = (Database) c.newInstance();  44:         } catch (InstantiationException ie) {  45:             ie.printStackTrace();  46:         } catch (IllegalAccessException iae) {  47:             iae.printStackTrace();  48:         }  49:   50:         try {  51:             DatabaseManager.registerDatabase(db);  52:         } catch (XMLDBException xde) {  53:             xde.printStackTrace();  54:         }  55:         return db;  56:     }

The doGet method initializes an Xindice database client and then calls the xindiceQuery method that does all the work. doGet passes the default collection and query values to xindiceQuery. Once the query is complete and the response has been sent, destroyDB is called to unregister the database. These two methods could be beefed up to provide a connection pool for Xindice, but you don’t do that here:

 58:     protected void doGet(HttpServletRequest req,   59:                            HttpServletResponse res)  60:         throws ServletException, IOException {  61:         Database db = initDB();  62:         xindiceQuery(db, res, DEFAULT_COLLECTION, DEFAULT_QUERY);  63:         destroyDB(db);  64:     } 

In preparation for sending output to the response, you obtain a PrintWriter on the HttpServletResponse and set the content type of the response to be XML ("text/xml"):

 65:         66:     private void xindiceQuery(Database db,   67:                                HttpServletResponse response,   68:                                String collection, String query) {  69:         PrintWriter out = null;  70:         try {  71:             out = response.getWriter();  72:             response.setContentType("text/xml");  73:         } catch (IOException ioe) {  74:             ioe.printStackTrace();  75:         }

The code in lines 77-98 disassembles the collection path and sets up the correct Collection. Once you’re using the right Collection, you can call the query method to perform the XPath query and obtain an XML:DB ResourceSet. Now you have to iterate through the ResourceSet and produce XML. This process would be easy, except that each XMLResource in the ResourceSet is given back to you as an XML document, complete with an XML declaration. So, you need to combine all these XML documents into a single document that can be used as the HTTP servlet response. You do this by stripping the XML declaration from each XMLResource and making the remainder of each XMLResource (a root element) a child of a new root element. The root element of the result document is <result>:

 76:           77:         Collection root = null;  78:         try {  79:             root = DatabaseManager.getCollection(  80:                        "xmldb:xindice:///db");  81:         } catch (XMLDBException xde) {  82:             xde.printStackTrace();  83:         }  84:           85:         String collections[] = collection.split("/");  86:         if (collection.equals(""))  87:             collections = new String[0];  88:           89:         Collection current = root;  90:           91:         for (int i = 0; i < collections.length; i++) {  92:             try {  93:                 current =   94:                     current.getChildCollection(collections[i]);  95:             } catch (XMLDBException xde) {  96:                 xde.printStackTrace();  97:             }  98:         }  99:          100:         ResourceSet result = query(current, query); 101:         

Before you can process any of the Resources from the ResourceSet, you need to output the start tag of the root element (line 103):

102:         try { 103:             out.println("<result>");

Next you loop through all the Resources from the ResourceSet and write them to the response. Because each Resource (XMLResource, really) is turned into an XML document, you need to strip the XML declaration from the content returned by the Resource. You do this by looking for the <?xml part of the XML declaration (line 109), looking for the matching ?> (line 110), grabbing everything after the ?> as your real content (line 111), and writing that to the response (line 113).

104:             for (ResourceIterator ri = result.getIterator(); 105:                 ri.hasMoreResources(); 106:                 ) { 107:                 Resource r = ri.nextResource(); 108:                 String content = (String) r.getContent(); 109:                 if (content.startsWith("<?xml")) { 110:                     int pos=(content.indexOf("?>")); 111:                     content = content.substring(pos+3); 112:                 }

After all the Resources have been written, you close the <result> element and flush the entire thing to the response:

113:                 out.println(content); 114:             } 115:             out.println("</result>"); 116:             out.flush();

The doPost method works like doGet, except it tries to get values for the collection and query from the parameters of the HttpServletRequest (lines 127-132). After that, the code that’s executed (lines 134-136) is the same as for doGet:

117:         } catch (XMLDBException xde) { 118:             xde.printStackTrace(); 119:         } 120:          121:     } 122:  123:     protected void doPost(HttpServletRequest req,  124:                             HttpServletResponse res) 125:         throws ServletException, IOException { 126:  127:         String collection = req.getParameter("collection"); 128:         collection = (collection != null) ?  129:                          collection : DEFAULT_COLLECTION; 130:           131:         String query = req.getParameter("query"); 132:         query = (query != null) ? query : DEFAULT_QUERY; 133:          134:         Database db = initDB(); 135:         xindiceQuery(db, res, collection, query); 136:         destroyDB(db); 137:      } 138: 

The query method should be familiar from the earlier example on XPath queries. It obtains the XPathQueryService, sets up the namespace mappings, and executes the query:

139:     private ResourceSet query(Collection c,  140:                                String queryString) { 141:         ResourceSet rs = null; 142:         try { 143:             XPathQueryService qs = (XPathQueryService) 144:                 c.getService("XPathQueryService","1.0"); 145:             qs.clearNamespaces(); 146:             qs.setNamespace("tns", 147:              "http://sauria.com/schemas/apache-xml-book/books"); 148:             qs.setNamespace("xsi", 149:              "http://www.w3.org/2001/XMLSchema-instance"); 150:             qs.setNamespace("", 151:              "http://sauria.com/schemas/apache-xml-book/books"); 152:             rs = qs.query(queryString); 153:         } catch (XMLDBException xde) { 154:             xde.printStackTrace(); 155:         } 156:         return rs; 157:     } 158: 

The destroyDB method unregisters the Database from the DatabaseManager. It’s just cleaning up nicely:

159:     public void destroyDB(Database db) { 160:         try { 161:             DatabaseManager.deregisterDatabase(db); 162:         } catch (XMLDBException xmldb) { 163:             xmldb.printStackTrace(); 164:         } 165:     } 166: }

At last you have an XMLServlet that’s talking to a real XML datasource, not just delivering the contents of a single file. The new improved XMLServlet is also able to process queries, due to Xindice’s XPath querying capabilities. However, those very query capabilities necessitate some additional changes in your application. The original application presented in Chapter 2 could rely on the fact that the schema of the data being returned by the XMLServlet was unchanging. You always got back an instance of your book schema. With the Xindice-powered XMLServlet, that has changed. The reason is that XPath queries can return elements that aren’t top-level elements from the book schema. You get results that are a bunch of <author> elements, or <isbn> elements, or <year> elements. Any program that processes the response from XMLServlet must be able to deal with these varying elements in the result.

XSLTServletFilter

In Chapter 2, the XSLTServletFilter processed the response from XMLServlet using XSLT to transform the XML into HTML. You need to modify XSLTServletFilter so that it can deal with different XML documents. You’ll handle this in a pretty straightforward fashion. The filter gets to look at the request before it’s passed to a servlet and can modify the response after the servlet has produced it. You’ll take advantage of this privilege to peek at the parameters in the request. In particular, the filter will look at the query parameter in the request and do some simple pattern-matching to select an XSLT spreadsheet based on the contents of the query string. The code is fairly simple in that it only looks for a single string in the XPath query. You could build code that takes apart an XPath query so it could perform more precise selection of stylesheets based on the contents of the query. The tradeoff is that you might end up having a large number of stylesheets to choose from (and to write and maintain). An alternative approach would be to generate the stylesheet to be used. This would reduce the number of stylesheets but introduce additional complexity because you want a human to be able to specify a portion of the stylesheet and have the filter generate the rest. You could do this using XSLT on a programmer-provided stylesheet. The approach demonstrated here illustrates the point that the filter needs to be able to accommodate multiple XML result documents:

  1: /*   2:  *    3:  * XSLTServletFilter.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9: import java.io.IOException;  10: import java.io.PrintWriter;  11: import java.io.StringReader;  12: import java.io.Writer;  13: import java.util.Collections;  14: import java.util.HashMap;  15: import java.util.Map;  16:   17: import javax.servlet.Filter;  18: import javax.servlet.FilterChain;  19: import javax.servlet.FilterConfig;  20: import javax.servlet.ServletContext;  21: import javax.servlet.ServletException;  22: import javax.servlet.ServletRequest;  23: import javax.servlet.ServletResponse;  24: import javax.servlet.http.HttpServletRequest;  25: import javax.servlet.http.HttpServletResponse;  26: import javax.xml.transform.Source;  27: import javax.xml.transform.Templates;  28: import javax.xml.transform.Transformer;  29: import javax.xml.transform.TransformerConfigurationException;  30: import javax.xml.transform.TransformerFactory;  31: import javax.xml.transform.stream.StreamResult;  32: import javax.xml.transform.stream.StreamSource;  33:   34: public class XSLTServletFilter implements Filter {  35:     FilterConfig config = null;  36:     Map transletCache = null;  37:   38:     public void init(FilterConfig fc) throws ServletException {  39:         config = fc;  40:           41:         ServletContext ctx = fc.getServletContext();   42:           43:         transletCache =   44:             (Map) ctx.getAttribute("transletCache");  45:           46:         if (transletCache == null) {  47:             transletCache =   48:                 Collections.synchronizedMap(new HashMap());  49:             ctx.setAttribute("transletCache", transletCache);  50:         }       51:     }

The init method is unchanged from the version you saw in Chapter 2. It sets up the translet cache for efficiency.

Here the filter grabs a copy of the query parameter so it can be used for stylesheet selection:

 52:   53:     public void doFilter(  54:         ServletRequest req,  55:         ServletResponse res,  56:         FilterChain chain)  57:         throws IOException, ServletException {  58:         String contentType;  59:         String styleSheet;  60:   61:         HttpServletRequest httpReq = (HttpServletRequest) req;  62:         String query = httpReq.getParameter("query");  63:         query = (query != null) ? query : "";  64:   65:         PrintWriter out = res.getWriter();  66:   67:         BufferedResponseWrapper wrappedResponse =  68:           new BufferedResponseWrapper((HttpServletResponse)res);  69:   70:         chain.doFilter(req, wrappedResponse);  71:         res.setContentType("text/html");  72:   73:         String s = new String(wrappedResponse.getBuffer());  74:         StringReader sr =  75:             new StringReader(s);  76:         Source xmlSource = new StreamSource(sr);  77:   78:         try {  79:             Templates translet = selectTemplates(query);  80:             Writer w = res.getWriter();  81:             StreamResult result = new StreamResult(w);  82:             Transformer xformer = translet.newTransformer();  83:             xformer.transform(xmlSource, result);  84:         } catch (Exception ex) {  85:             out.println(ex.toString());  86:             out.write(wrappedResponse.toString());  87:         }  88:     }  89:   90:     private Templates selectTemplates(String query) {  91:         Templates translet = null;  92:           93:         TransformerFactory xFactory =   94:             TransformerFactory.newInstance();  95:         String styleSheet = selectStyleSheet(query);  96:           97:         translet = (Templates) transletCache.get(styleSheet);  98:         if (translet != null) {  99:             return translet; 100:         } 101:          102:         try { 103:             String stylePath =  104:                 config.getServletContext().getRealPath(styleSheet); 105:             Source styleSource = new StreamSource(stylePath); 106:             translet =  107:                 xFactory.newTemplates(styleSource); 108:         } catch (TransformerConfigurationException e) { 109:             e.printStackTrace(); 110:         } 111:  112:         transletCache.put(styleSheet, translet); 113:         return translet; 114:     }

You modify the selectStyleSheet method to look at the query and return the appropriate stylesheet. If the query contains a reference to the author element, then the filter selects the xindice-author.xslt stylesheet. If it references the isbn element, the filter selects the xindice-isbn.xslt stylesheet. Otherwise, the filter selects the xindice-full.xslt stylesheet:

115:      116:     private String selectStyleSheet(String query) { 117:         if (query.indexOf("author") >= 0)  118:             return "xindice-author.xslt"; 119:         if (query.indexOf("isbn") >= 0)  120:             return "xindice-isbn.xslt"; 121:         return "xindice-full.xslt"; 122:     } 123:      124:     public void destroy() {} 125: }

Deployment Descriptors

Here’s the Web container deployment descriptor (web.xml) for this Web application:

  1: <?xml version="1.0" encoding="ISO-8859-1"?>   2:    3: <!DOCTYPE web-app   4:   PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"   5:   "http://java.sun.com/dtd/web-app_2_3.dtd">   6:    7: <web-app>   8:   <display-name>Professional XML Development with Apache Tools Examples   9:   </display-name>  10:   <description>  11:     Examples from Professional XML Development with Apache Tools.  12:   </description>

You define the XSLTServletFilter as a filter name XSLT Filter:

 12:   13:   <!-- Define servlet-mapped and path-mapped example filters -->  14:   <filter>  15:     <filter-name>XSLT Filter</filter-name>  16:     <filter-class>  17:       com.sauria.apachexml.ch7.XSLTServletFilter  18:     </filter-class>  19:   </filter>

Here you tell the container that XSLT Filter is used on XMLServlet:

 20:   21:   <!-- Define filter mappings for the defined filters -->    22:   23:   <filter-mapping>  24:     <filter-name>XSLT Filter</filter-name>  25:     <servlet-name>XMLServlet</servlet-name>  26:   </filter-mapping>

Next you define XMLServlet and XindiceXSLTServlet, which we’ll discuss in a little while:

 27:   28:   <servlet>  29:     <servlet-name>XMLServlet</servlet-name>  30:     <servlet-class>  31:       com.sauria.apachexml.ch7.XMLServlet  32:     </servlet-class>  33:   </servlet>  34:     35:   <servlet>  36:     <servlet-name>XindiceXSLTServlet</servlet-name>  37:     <servlet-class>  38:       com.sauria.apachexml.ch7.XindiceXSLTServlet  39:     </servlet-class>  40:   </servlet>

Last, you provide URL mappings for the servlets you’ve defined:

 41:       42:   <servlet-mapping>  43:     <servlet-name>XMLServlet</servlet-name>  44:     <url-pattern>/filter/*</url-pattern>  45:   </servlet-mapping>  46:     47:   <servlet-mapping>  48:     <servlet-name>XindiceXSLTServlet</servlet-name>  49:     <url-pattern>/xindice/*</url-pattern>  50:   </servlet-mapping>  51:     52: </web-app>

XSLT Stylesheets

Next we’ll show you the three XSLT stylesheets so you can get feel for how to deal with the different results. The first stylesheet is xindice-author.xslt, which expects an XML document whose content is a <result> root element containing children <author> elements from the book inventory namespace. This stylesheet produces an HTML document that contains an ordered list of the authors’ names (the content of the <author> element):

  1: <?xml version="1.0" encoding="UTF-8"?>   2: <xsl:stylesheet version="1.0"    3:   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   4:   xmlns:books="http://sauria.com/schemas/apache-xml-book/books">   5:   <xsl:output method="html" version="1.0" encoding="UTF-8"    6:               indent="yes"/>   7:      8:   <xsl:template match="result">   9:     <html>  10:     <head>  11:       <title></title>  12:     </head>  13:     <body>  14:       <ol>  15:       <xsl:apply-templates/>  16:       </ol>  17:     </body>  18:     </html>  19:   </xsl:template>

The template in lines 8-19 matches the result element and sets up the HTML skeleton. It uses <apply-templates/> to process its children (<author> elements).

This template generates the HTML <li> items for each author name:

 20:     21:   <xsl:template match="books:author">  22:     <li>  23:       <xsl:value-of select="."/>  24:     </li>  25:   </xsl:template>  26: </xsl:stylesheet>

The second stylesheet is the xindice-isbn.xslt stylesheet. This stylesheet deals with queries regarding ISBN numbers and produces an unordered list of ISBN numbers in the HTML document from a <result> element that has <isbn> elements from the books inventory namespace as children. This stylesheet has the same form as xindice-author.xslt. The difference is the use of <ul> versus <ol>:

  1: <?xml version="1.0" encoding="UTF-8"?>   2: <xsl:stylesheet version="1.0"   3:   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   4:   xmlns:books="http://sauria.com/schemas/apache-xml-book/books">   5:   <xsl:output method="html" version="1.0" encoding="UTF-8"   6:                  indent="yes"/>   7:   <xsl:template match="result">   8:     <html>   9:       <head>  10:         <title></title>  11:       </head>  12:       <body>  13:         <ul>  14:           <xsl:apply-templates/>  15:         </ul>  16:       </body>  17:     </html>  18:   </xsl:template>  19:     20:   <xsl:template match="books:isbn">  21:     <li>  22:       <xsl:value-of select="."/>  23:     </li>  24:   </xsl:template>  25: </xsl:stylesheet>

xindice-full.xslt is the third stylesheet, which is used when the children of the <result> element are <book> elements from the book inventory namespace. In this case, you create a description list using <dl>, make the titles <dt> of the descriptions the <author> children of <book>, and make the descriptions <dd> the <title> children of the <book>. Here you use <dl> instead of <ol>:

  1: <?xml version="1.0" encoding="UTF-8"?>   2: <xsl:stylesheet version="1.0"    3:   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   4:   xmlns:books="http://sauria.com/schemas/apache-xml-book/books">   5:   <xsl:output method="html" version="1.0" encoding="UTF-8"   6:               indent="yes"/>   7:   <xsl:template match="result">   8:    <html>   9:     <head>  10:      <title></title>  11:     </head>  12:     <body>  13:      <dl>  14:       <xsl:apply-templates/>  15:      </dl>  16:     </body>  17:    </html>  18:   </xsl:template>

You don’t want to process all the children of <books:book>, so you use <apply-templates> with select attributes to control which children templates are applied to:

 19:   20:   <xsl:template match="books:book">  21:     <xsl:apply-templates select="books:author"/>  22:     <xsl:apply-templates select="books:title"/>  23:   </xsl:template>

The rest of the stylesheet contains template definitions to generate <dt> elements for the <author> children and <dd> elements for the title children:

 24:     25:   <xsl:template match="books:title">  26:     <dd>  27:       <xsl:value-of select="."/>  28:     </dd>  29:   </xsl:template>  30:     31:   <xsl:template match="books:author">  32:     <dt>  33:       <xsl:value-of select="."/>  34:     </dt>  35:   </xsl:template>  36: </xsl:stylesheet>

The following listing shows a simple HTML form you can use to enter the collection name and an XPath query, so you can verify that the POST behavior is correct:

  1: <html>   2:  <head></head>   3:  <body>   4:  <form name="queryForm" method="POST" action="filter">   5:   Enter name of collection to query:<br>   6:   <input type="text" name="collection"><br>   7:   Enter a XPath Query<br>   8:   <input type="text" name="query"><br><br>   9:   <input type="submit" name="submit">  10:  </form>  11:  <body>  12: </html>

When you combine all the artifacts that we’ve seen in this section, you get a system that can issue XPath queries against an Xindice database and product HTML output as a result of using XSLT transforms on the Xindice results.

A SAX-based Version

It’s good to see that you can build a modular system using something like XMLServlet and XSLTServletFilter. The problem with a system like this from an XML point of view is that the servlet filter API only allows you to move text results through the filter chain. This is inefficient because each filter needs text as input and must provide text as output. You know you can do better than that in the XML world, either by using a set of SAX filters as a pipeline or by passing a DOM tree between methods. So, let’s look at how you can implement the same solution using a SAX filter technique. This should remove most of the additional parsing and serialization that happens with the filter approach. Start by adding functionality to the XMLServlet:

  1: /*   2:  *    3:  * XindiceXSLTServlet.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8:    9: package com.sauria.apachexml.ch7;  10: import java.io.IOException;  11: import java.io.PrintWriter;  12:   13: import javax.servlet.ServletContext;  14: import javax.servlet.ServletException;  15: import javax.servlet.http.HttpServlet;  16: import javax.servlet.http.HttpServletRequest;  17: import javax.servlet.http.HttpServletResponse;  18: import javax.xml.transform.Source;  19: import javax.xml.transform.TransformerConfigurationException;  20: import javax.xml.transform.TransformerFactory;  21: import javax.xml.transform.sax.SAXResult;  22: import javax.xml.transform.sax.SAXTransformerFactory;  23: import javax.xml.transform.sax.TransformerHandler;  24: import javax.xml.transform.stream.StreamResult;  25: import javax.xml.transform.stream.StreamSource;  26:   27: import org.xmldb.api.DatabaseManager;  28: import org.xmldb.api.base.Collection;  29: import org.xmldb.api.base.Database;  30: import org.xmldb.api.base.ResourceIterator;  31: import org.xmldb.api.base.ResourceSet;  32: import org.xmldb.api.base.XMLDBException;  33: import org.xmldb.api.modules.XMLResource;  34: import org.xmldb.api.modules.XPathQueryService;  35:   36: public class XindiceXSLTServlet extends HttpServlet {  37:     String DEFAULT_COLLECTION="books";  38:     String DEFAULT_QUERY="//tns:author";  39:   40:     public Database initDB() {  41:         String driver =   42:             "org.apache.xindice.client.xmldb.DatabaseImpl";  43:         Class c = null;  44:         try {  45:             c = Class.forName(driver);  46:         } catch (ClassNotFoundException cnfe) {  47:             cnfe.printStackTrace();  48:         }  49:   50:         Database db = null;  51:         try {  52:             db = (Database) c.newInstance();  53:         } catch (InstantiationException ie) {  54:             ie.printStackTrace();  55:         } catch (IllegalAccessException iae) {  56:             iae.printStackTrace();  57:         }  58:   59:         try {  60:             DatabaseManager.registerDatabase(db);  61:         } catch (XMLDBException xde) {  62:             xde.printStackTrace();  63:         }  64:         return db;  65:     }  66:   67:     protected void doGet(HttpServletRequest req,   68:                           HttpServletResponse res)  69:         throws ServletException, IOException {  70:               71:         Database db = initDB();  72:         xindiceQuery(db, res, DEFAULT_COLLECTION, DEFAULT_QUERY);  73:         destroyDB(db);  74:     }  75:  76:     private void xindiceQuery(Database db,  77:                                HttpServletResponse response,   78:                                String collection, String query) {  79:         PrintWriter out = null;  80:         response.setContentType("text/html");  81:           82:         Collection root = null;  83:         try {  84:             String uri = "xmldb:xindice:///db";  85:             uri += "/"+collection;  86:             root = DatabaseManager.getCollection(uri);  87:         } catch (XMLDBException xde) {  88:             xde.printStackTrace();  89:         }  90:                 91:         ResourceSet result = query(root, query);  92:         

The initDB, destroyDB, doGet, doPost, and selectStylesheet methods are the same as in XMLServlet. The difference sets in after you’ve processed the query and need to transform the results. You need to iterate over the Resources in the ResourceSet and obtain their content. Because you’re trying to avoid extra serialization and parsing, you get the content as a SAX event stream. This means you iterate over a set of SAX event streams, which you’ll output to the servlet response. The challenge is finding a way to take multiple SAX streams and merge them into a single stream.

Because you know you’ll be using SAX, you need to use a JAXP SAXTransformer and associated classes like SAXTransformerFactory. In lines 94-104, you create a TransformerFactory and downcast it to obtain a SAXTransformerFactory:

 93:         try {  94:             TransformerFactory xf =   95:                 TransformerFactory.newInstance();  96:             SAXTransformerFactory sxf = null;  97:                   98:             if (!xf.getFeature(SAXResult.FEATURE)) {  99:                 System.out.println("Bad factory"); 100:                 return; 101:             } 102:              103:             sxf = (SAXTransformerFactory) xf; 104:             TransformerHandler serializer = null;

You need a Transformer (actually, a TransformerHandler) that uses a stylesheet to transform the Xindice output and then writes the transformed output into the servlet response:

105:              106:             try { 107:                 Source styleSheet = selectStyleSheet(query); 108:                 serializer =  109:                     sxf.newTransformerHandler(styleSheet);

In lines 114-121, you set up the output stage of the transformer. You create a new StreamResult that wraps the Servlet’s response OutputStream (line 117) and sets the Result of the serializer to go to this StreamResult (line 121):

110:             } catch (TransformerConfigurationException e) { 111:                 e.printStackTrace(); 112:             } 113:              114:             StreamResult output = null; 115:             try { 116:                 output =  117:                     new StreamResult(response.getOutputStream()); 118:             } catch (IOException ioe) { 119:                 ioe.printStackTrace(); 120:             } 121:             serializer.setResult(output);

You solve the problem of merging multiple SAX streams into a single SAX stream by defining an XMLFilter to do the work for you. The tricky part about merging is that each of the input SAX streams will try to fire all the SAX events for an XML document. In particular, it will fire events for startDocument and endDocument, which will cut off the ability to merge any additional streams. You need a filter that swallows the startDocument and endDocument callbacks, so that even though they’re fired by the input SAX stream, they aren’t output by the serializer. You also need a way to manually fire the startDocument and endDocument events on the serializer; otherwise, you’ll have a non-well-formed XML document. The class XPathResultHandler takes care of this for you. In line 124, you set the destination of the XPathResultHandler to be the TransformerHandler:

122:  123:             XPathResultHandler filter = new XPathResultHandler(); 124:             filter.setContentHandler(serializer);

XPathResultHandler’s start method causes a startDocument event to be fired on the destination of the filter. Now the TransformationHandler thinks you have started parsing a new XML document:

125:             filter.start();

As you iterate over the XMLResources in the ResourceSet, you obtain the content of each as a SAX stream, passing in the XPathResultHandler as the ContentHandler to use. This allows Xindice to fire all the SAX events for the XMLResource without the problematic startDocument and endDocument events being passed to the TransformerHandler:

126:              127:             for (ResourceIterator ri = result.getIterator(); 128:                 ri.hasMoreResources(); 129:                 ) { 130:                 XMLResource r = (XMLResource) ri.nextResource(); 131:                 r.getContentAsSAX(filter);

The end method of XPathResultHandler causes an endDocument event to be fired on the destination of the filter, telling the TransformationHandler that you’re done parsing the XML document:

132:             } 133:              134:             filter.end(); 135:         } catch (XMLDBException xde) { 136:             xde.printStackTrace(); 137:         } 138:     } 139:  140:     private Source selectStyleSheet(String query) { 141:         String file = "xindice-full.xslt"; 142:         if (query.indexOf("author") >= 0)  143:             file = "xindice-author.xslt"; 144:         if (query.indexOf("isbn") >= 0)  145:             file = "xindice-isbn.xslt"; 146:  147:         ServletContext ctx = getServletContext(); 148:         Source result = new StreamSource(ctx.getRealPath(file)); 149:         return result; 150:     } 151:  152:     protected void doPost(HttpServletRequest req,  153:                             HttpServletResponse res) 154:         throws ServletException, IOException { 155:  156:         String collection = req.getParameter("collection"); 157:         collection = (collection != null) ?  158:                          collection : DEFAULT_COLLECTION; 159:           160:         String query = req.getParameter("query"); 161:         query = (query != null) ? query : DEFAULT_QUERY; 162:          163:         Database db = initDB(); 164:         xindiceQuery(db, res, collection, query); 165:         destroyDB(db); 166:      } 167:  168:     private ResourceSet query(Collection c,  169:                                String queryString) { 170:         ResourceSet rs = null; 171:         try { 172:             XPathQueryService qs = (XPathQueryService) 173:                 c.getService("XPathQueryService","1.0"); 174:             qs.clearNamespaces(); 175:             qs.setNamespace("tns", 176:              "http://sauria.com/schemas/apache-xml-book/books"); 177:             qs.setNamespace("xsi", 178:                "http://www.w3.org/2001/XMLSchema-instance"); 179:             qs.setNamespace("", 180:              "http://sauria.com/schemas/apache-xml-book/books"); 181:             rs = qs.query(queryString); 182:         } catch (XMLDBException xde) { 183:             xde.printStackTrace(); 184:         } 185:         return rs; 186:     } 187:  188:     public void destroyDB(Database db) { 189:         try { 190:             DatabaseManager.deregisterDatabase(db); 191:         } catch (XMLDBException xmldb) { 192:             xmldb.printStackTrace(); 193:         } 194:     } 195: }

XPathResultHandler

Here’s the code for XPathResultHandler. It’s very short because it extends the SAX XMLFilterImpl helper class, which by default passes every event from one side of the filter to the other:

  1: /*   2:  *    3:  * XPathResultHandler.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch7;   9:   10: import org.xml.sax.ContentHandler;  11: import org.xml.sax.SAXException;  12: import org.xml.sax.helpers.AttributesImpl;  13: import org.xml.sax.helpers.XMLFilterImpl;  14:   15: public class XPathResultHandler extends XMLFilterImpl {  16:   17:       18:     public void endDocument() throws SAXException {  19:     }  20:   21:     public void startDocument() throws SAXException {  22:     }

Overriding endDocument and startDocument provides the event-swallowing behavior you need for this application. All other events are passed straight through.

In the start method, you need to get the ContentHandler that’s been registered as the destination of the filter and call startDocument. You also need to provide the wrapping <result> element for all the children (XMLResources), so you call startElement manually as well. Note that you have to supply an instance of AttributesImpl in order for this to work:

 23:   24:     public void start() {  25:         try {  26:             ContentHandler h = super.getContentHandler();  27:             h.startDocument();  28:             h.startElement(null,"result","result",  29:                            new AttributesImpl());

Likewise, in the end element, you get the destination ContentHandler, call endElement to close the <result> element, and then fire endDocument to signal that you’re done:

 30:         } catch (SAXException se) {  31:             se.printStackTrace();  32:         }  33:     }  34:       35:     public void end() {  36:         try {  37:             ContentHandler h = super.getContentHandler(); 38:             h.endElement(null,"result","result");  39:             h.endDocument();  40:         } catch (SAXException se) {  41:             se.printStackTrace();  42:         }  43:     }  44: }

Storing XML data is still a relatively new area, and there are lots of proposals for how it should be done. We hope this tour of Xindice and Native XML Databases has given you some ideas for ways you can use native XML storage capabilities in your own applications. Xindice’s ability to query more than a single document at once and its use of XPath as an XML-aware query language make it a good fit for applications when you’re planning to store your data as XML.




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