Applications


Let’s revisit the mini-application we discussed at the end of Chapter 2. In that example, you wanted to build a Website that was using Xalan/XSLT to format the output of an XML file and produce HTML customized for the browser a person was using to visit the site. Now let’s say you want to replace the HTML output with PDF. You don’t have to tear down the entire system and rebuild it. Instead, you can modify the XSLT servlet filter and create a second filter that takes the output of the XSLT filter and runs it through FOP. Then you can replace the original XSLT filter with the filter chain containing the XSLT/FOP filters. We’ll only cover the classes that are new or have changed (there is one of each).

Here’s the updated XSLT filter:

  1: /*   2:  *    3:  * XSLTServletFilter.java   4:  *    5:  * Example from "Professional XML Development with Apache Tools"   6:  *   7:  */   8: package com.sauria.apachexml.ch3;   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 XSLTFOPServletFilter 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 code in init is almost the same as in Chapter 2. The code for the Xalan SmartTransformerFactory has been omitted because XSLTC isn’t quite up to processing the stylesheets you’re using to generate XSL. So, you revert to using the standard Xalan interpreted mode.

The doFilter method is a little shorter than the one in Chapter 2. Instead of creating a CharacterArrayWriter, you place the output of the XSLT transformation directly onto the OutputStream of the servlet response object passed to the XSLT filter. As you’ll see, this is the response object provided by the FOP filter. By writing the XSLT into the FOP filter’s response, you make the result of the transformation available to the FOP filter:

 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:   63:         PrintWriter out = res.getWriter();  64:   65:         BufferedResponseWrapper wrappedResponse =  66:          new BufferedResponseWrapper((HttpServletResponse)res);  67:   68:         chain.doFilter(req, wrappedResponse);  69:         res.setContentType("application/xml");  70:   71:         String s = new String(wrappedResponse.getBuffer());  72:         StringReader sr =  73:             new StringReader(s);  74:         Source xmlSource = new StreamSource(sr);  75:   76:         try {  77:             Templates translet = getTemplates();   78:             Writer w = res.getWriter();  79:             StreamResult result = new StreamResult(w);  80:             Transformer xformer = translet.newTransformer();  81:             xformer.transform(xmlSource, result);  82:         } catch (Exception ex) {  83:             out.println(ex.toString());  84:             out.write(wrappedResponse.toString());  85:         }  86:     }

The getTemplates method replaces the selectTemplates method because you aren’t selecting the stylesheet based on the user-agent accessing the site. For the sake of brevity, we’re showing the stylesheet hardwired, but you could easily modify it to select the stylesheet based on the URI being accessed in the browser; doing so would allow you to use different stylesheets for different pages on the Website:

 87:   88:     private Templates getTemplates() {  89:         Templates translet = null;  90:           91:         TransformerFactory xFactory =   92:             TransformerFactory.newInstance();  93:         String styleSheet = "books-table.xslt";  94:           95:         translet = (Templates) transletCache.get(styleSheet);  96:         if (translet != null) {  97:             return translet;  98:         }  99:          100:         try { 101:             String stylePath =  102:              config.getServletContext().getRealPath(styleSheet); 103:             Source styleSource = new StreamSource(stylePath); 104:             translet =  105:                 xFactory.newTemplates(styleSource); 106:         } catch (TransformerConfigurationException e) { 107:             e.printStackTrace(); 108:         } 109:  110:         transletCache.put(styleSheet, translet);  111:         return translet; 112:     } 113:      114:     public void destroy() {} 115: }

This XSLT filter can be used with any XML and any XSLT stylesheet. Taking out the user agent logic makes that a bit clearer.

Now let’s look at the FOP filter. This filter expects the servlet response it receives (whether from a servlet or another filter) to contain an XSL document. The filter then runs the XSL document through FOP and puts the rendered output on its own response object for processing:

  1: package com.sauria.apachexml.ch3;   2: import java.io.ByteArrayInputStream;   3: import java.io.IOException;   4: import java.io.InputStream;   5: import java.io.OutputStream;   6:    7: import javax.servlet.Filter;   8: import javax.servlet.FilterChain;   9: import javax.servlet.FilterConfig;  10: import javax.servlet.ServletException;  11: import javax.servlet.ServletRequest;  12: import javax.servlet.ServletResponse;  13: import javax.servlet.http.HttpServletResponse;  14:   15: import org.apache.avalon.framework.logger.ConsoleLogger;  16: import org.apache.avalon.framework.logger.Logger;  17: import org.apache.fop.apps.Driver;  18: import org.apache.fop.apps.FOPException;  19: import org.apache.fop.apps.FormattingResults;  20: import org.apache.fop.messaging.MessageHandler;  21: import org.xml.sax.InputSource;  22:   23: /*  24:  *   25:  * FOPServletFilter.java  26:  *   27:  * Example from "Professional XML Development with Apache Tools"  28:  *  29:  */  30:   31: public class FOPServletFilter implements Filter {  32:     static Driver fopDriver = new Driver();  33:   34:     public void init(FilterConfig fc) throws ServletException {  35:     }  36:   37:     public void doFilter(  38:         ServletRequest request,  39:         ServletResponse response,  40:         FilterChain chain)  41:         throws IOException, ServletException {  42:   43:         BufferedResponseWrapper wrappedResponse =  44:             new BufferedResponseWrapper((HttpServletResponse)   45:                                         response);  46: 

Again, you need a BufferedResponseWrapper to grab the data the previous servlet/filter in the chain wrote to its response.

In this example you hardwire the output format to be PDF. You could look at the URI or headers of the request to select the renderer you want. You also need to set the response’s content-type to the appropriate value:

 47:         chain.doFilter(request, wrappedResponse);  48:         response.setContentType("application/pdf");  49: 

Recall that FOP uses some static configuration variables within its implementation. This makes using it problematic in multithreaded environments such as a servlet. The filter solves this problem by allocating a static instance of the FOP Driver and then synchronizing access to it using the filter itself as a lock:

 50:         synchronized (this) {  51:             fopDriver.reset();

Threading issues aside, you don’t want to create a new instance of the FOP Driver every time the filter runs, because of the cost of instantiating a new Driver object. Instead you call the Driver’s reset method so you can use the single instance over and over.

The wrappedResponse (the response from the previous servlet or filter in the chain) is used as the input to the FOP Driver. You get the byte array buffer and then create the necessary wrapper classes to convert that byte array into a suitable InputSource for FOP. You set the InputSource and OutputStream explicitly because you’re reusing the FOP Driver instance, so supplying these via a constructor won’t work:

 52:   53:             byte buf[] = wrappedResponse.getBuffer();  54:             InputStream bais = new ByteArrayInputStream(buf);  55:   56:             InputSource is = new InputSource(bais);  57:             fopDriver.setInputSource(is);

The remainder of the driver setup should be familiar. You set the OutputStream, turn on any logging you need, set the renderer, and run FOP:

 58:   59:             OutputStream outFile = response.getOutputStream();  60:             fopDriver.setOutputStream(outFile);  61:   62:             Logger logger =   63:                new ConsoleLogger(ConsoleLogger.LEVEL_WARN);  64:             MessageHandler.setScreenLogger(logger);  65:             fopDriver.setLogger(logger);  66:             fopDriver.setRenderer(Driver.RENDER_PDF);  67:             try {  68:                 fopDriver.run();  69:             } catch (IOException ioe) {  70:                 ioe.printStackTrace();  71:             } catch (FOPException fe) {  72:                 fe.printStackTrace();  73:             }  74:   75:             FormattingResults results = fopDriver.getResults();  76:             logger.info("Pages Formatted: " +   77:                          results.getPageCount());  78:         }  79:     }  80:   81:     public void destroy() {  82:     }  83: }

The last piece of the servlet filter chain is the Web Application deployment descriptor (web.xml), which is shown here:

  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>   9:    Professional XML Development with Apache Tools Examples  10:   </display-name>  11:   <description>  12:     Examples from Professional XML Development with Tools.  13:   </description>  14:   15:   <!-- Define servlet-mapped and path-mapped example filters -->  16:   <filter>  17:     <filter-name>XSLT Filter</filter-name>  18:     <filter-class>  19:      com.sauria.apachexml.ch4.XSLTFOPServletFilter  20:     </filter-class>  21:   </filter>  22:     23:   <filter>  24:     <filter-name>FOP Filter</filter-name>  25:     <filter-class>  26:      com.sauria.apachexml.ch4.FOPServletFilter  27:     </filter-class>  28:   </filter>

You have two filter definitions instead of one:

 29:   30:   <!-- Define filter mappings for the defined filters -->  31:   <filter-mapping>  32:     <filter-name>FOP Filter</filter-name>  33:     <servlet-name>XMLServlet</servlet-name>  34:   </filter-mapping>  35:   36:   <filter-mapping>  37:     <filter-name>XSLT Filter</filter-name>  38:     <servlet-name>XMLServlet</servlet-name>  39:   </filter-mapping>  40: 

The order of the filters in the chain is determined by the order of the <filter-mapping> elements. According to web.xml, the order of the chain is FOP filter, XSLT filter, XML servlet. This looks backward, but it’s exactly what you want. When the browser makes a request, the request is processed by the FOP filter, and then the XSLT filter and after that it is finally allowed to get to the XML servlet. The filter chain doesn’t do anything with the requests. The responses are processed in the reverse order of the requests. So, the response from the XML servlet (a document containing books in the books schema) is passed to the XSLT filter, which processes that response and generates its own response (a document containing XSL formatting objects). The XSLT filter’s response then goes to the FOP filter that takes the XSL document from the XSLT filter and runs FOP over it to produce the final response, which contains a PDF file. You can see that the mapping is exactly what you wanted in order to produce the result:

 41:   <servlet>  42:     <servlet-name>XMLServlet</servlet-name>  43:     <servlet-class>  44:      com.sauria.apachexml.ch3.XMLServlet  45:     </servlet-class>  46:   </servlet>  47:       48:   <servlet-mapping>  49:     <servlet-name>XMLServlet</servlet-name>  50:     <url-pattern>/ch3/*</url-pattern>  51:   </servlet-mapping>  52:     53: </web-app>

We’ll leave the problem of integrating the user-agent-specific HTML system and the FOP-based system as an exercise for you to think about. Consider the fact that you need to execute the FOP filter only in certain circumstances.

Using servlet filters this way shows off the power and flexibility of XML’s approach to layered data manipulation. In this example we see three levels of layering: 1) XML data, 2) an XSLT stylesheet that turns the XML data into XSL, and 3) an XSL document that contains the data and describes how to render the data. This layering is a perfect fit for the Java servlet filter API which allows you to build a generic reusable filter for each layer in the XML architecture. Once You’ve built the filters, you can build systems by combining the filters and updating date structures that tell you which stylesheets and renderers to use. In the example, the stylesheets and renderers have been hardwired into the filter, but you could just as easily perform a lookup in a Java properties file or a SQL database.

FOP provides a way for you to bridge between XML content and a number of non-XML-based publishing formats. If your application publishes documents of some kind, it’s obvious why you need a solution like FOP. If you’re writing business applications, FOP gives you a way to enable your applications for the physical, by providing you with ways to easily generate documents that can be printed.




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