Building XslServletXslServlet and XslServletLiaisonImpl perform all the work of calling the XSLT processor. XslServletListing 8.1 is XslServlet , your specialized HttpServlet . It replaces doGet() and doPost() with its own version. The new methods have an additional parameter of type XslServletLiaison . Listing 8.1 XslServlet.javapackage com.psol.pesticide; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class XslServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { XslServletLiaisonImpl liaison = new XslServletLiaisonImpl(getClass(),request); doGet(request,response,liaison); liaison.writeResponse(response); } public void doGet(HttpServletRequest request, HttpServletResponse response, XslServletLiaison liaison) throws ServletException, IOException { response.sendError(HttpServletResponse.SC_BAD_REQUEST); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { XslServletLiaisonImpl liaison = new XslServletLiaisonImpl(getClass(),request); doPost(request,response,liaison); liaison.writeResponse(response); } public void doPost(HttpServletRequest request, HttpServletResponse response, XslServletLiaison liaison) throws ServletException, IOException { response.sendError(HttpServletResponse.SC_BAD_REQUEST); } } In answer to an HTTP request ( GET or POST ), the servlet creates an XslServletLiaisonImpl , passes the request to the specialized doGet() or doPost() , and uses the liaison to write the following response: public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { XslServletLiaisonImpl liaison = new XslServletLiaisonImpl(getClass(),request); doGet(request,response,liaison); liaison.writeResponse(response); } XslServletLiaisonThe declaration for XslServletLiaison is shown in Listing 8.2. It enables the servlet to communicate with the XSL processor. The main methods are as follows :
Listing 8.2 XslServletLiaison.javapackage com.psol.pesticide; import java.io.*; public interface XslServletLiaison { public XslWriter getWriter() throws IOException; public File getXSL(); public void setXSL(File xsl); public String getSkin(); } Servlet SkinsXslServletLiaison must select the appropriate style before applying it. Experience shows that the most flexible solution is to extract a reference to the style sheet from the URL. However, it is not safe to pass it directly as a URL parameter, such as
because hackers can forge URLs that point to their XSL files. In other words, it enables them to run their own code (their style sheet) on your server: http://catwoman.pineapplesoft.com/buglist? xsl=http://www.hacker.com/ malign-style-sheet.xsl Instead, pass a shorter reference in the URL and turn into a safe file path on the server. In this project, the shorter reference is called a skin. The skin is simply a generic name for a given look and feel. For example, the following URLs respectively apply the cool and fast skins to the buglist servlet: http://catwoman.pineapplesoft.com/buglist/cool http://catwoman.pineapplesoft.com/buglist/fast Tip The alternatives to extracting the style sheet from the URL are
XslServletLiaisonImpl maps the skin ( cool ) to a file. It builds the filename by concatenating the skin to the servlet classname: // extract the "skin" from the URL String pathInfo = request.getPathInfo(); if(null != pathInfo) { StringTokenizer tokenizer = new StringTokenizer(pathInfo,"/",false); if(tokenizer.hasMoreTokens()) skin = tokenizer.nextToken(); } // turn the skin in the path to the style sheet if(null != skin) if(skin.equals("none")) return; else xsl = new File("stylesheet",skin); else xsl = new File("stylesheet"); StringTokenizer tokenizer = new StringTokenizer(clasz.getName(),".",false); while(tokenizer.hasMoreTokens()) if(tokenizer.countTokens() == 1) xsl = new File(xsl,tokenizer.nextToken() + ".xsl"); else xsl = new File(xsl,tokenizer.nextToken()); As you can see, the skin none is not converted to a filename. none returns the raw XML file (applying no XSL style sheet). This is useful for debugging. The following are several advantages of skins:
Tip Why use a special skin name, such as none , for raw XML? Why not reserve it for the empty skin, as in the following:
In practice, it is best to use a special name because visitors might forget the skin when typing the URL. The empty skin should be reserved to a default style sheet rather than raw XML. XslServletLiaisonImplListing 8.3 is XslServletLiaisonImpl , the default implementation of XslServletLiaison using Xalan 1.0. If you elect another XSL processor, you must provide an implementation of XslServletLiaison for that processor. Listing 8.3 XslServletLiaisonImpl.javapackage com.psol.pesticide; import java.io.*; import java.util.*; import org.xml.sax.*; import javax.servlet.*; import javax.servlet.http.*; import org.apache.xalan.xslt.*; public class XslServletLiaisonImpl implements XslServletLiaison { protected String skin = null; protected File xsl = null; protected ByteArrayOutputStream ostream = null; protected XslWriter writer = null; protected static Dictionary stylesheets = new Hashtable(); public XslServletLiaisonImpl(Class clasz, HttpServletRequest request) { // extract the "skin" from the path information String pathInfo = request.getPathInfo(); if(null != pathInfo) { StringTokenizer tokenizer = new StringTokenizer(pathInfo,"/",false); if(tokenizer.hasMoreTokens()) skin = tokenizer.nextToken(); } // turn the skin in the path to the style sheet if(null != skin) if(skin.equals("none")) return; else xsl = new File("stylesheet",skin); else xsl = new File("stylesheet"); StringTokenizer tokenizer = new StringTokenizer(clasz.getName(),".",false); while(tokenizer.hasMoreTokens()) if(tokenizer.countTokens() == 1) xsl = new File(xsl,tokenizer.nextToken() + ".xsl"); else xsl = new File(xsl,tokenizer.nextToken()); } public XslWriter getWriter() { if(null == writer) { ostream = new ByteArrayOutputStream(); writer = new XslWriter(ostream); } return writer; } public File getXSL() { return xsl; } public void setXSL(File xsl) { this.xsl = xsl; } public String getSkin() { return skin; } protected class Struct { public long lastModified; public StylesheetRoot stylesheet; public Struct(long lastModified,StylesheetRoot stylesheet) { this.lastModified = lastModified; this.stylesheet = stylesheet; } } protected void writeStyledXml(InputStream istream, HttpServletResponse response) throws SAXException, ServletException, IOException { XSLTProcessor processor = XSLTProcessorFactory.getProcessor(); // deal with precompiled style sheets Struct s = (Struct)stylesheets.get(xsl); if(null == s s.lastModified < xsl.lastModified()) { XSLTInputSource source = new XSLTInputSource(new FileInputStream(xsl)); s = new Struct(xsl.lastModified(), processor.processStylesheet(source)); stylesheets.put(xsl,s); } else processor.setStylesheet(s.stylesheet); // set the content-type String ct = s.stylesheet.getOutputMediaType(), cs = s.stylesheet.getOutputEncoding(); if(null == ct) { if(s.stylesheet.isOutputMethodSet()) { String method = s.stylesheet.getOutputMethod(); if(method.equalsIgnoreCase("xml")) ct = "text/xml"; else if(method.equalsIgnoreCase("html")) ct = "text/html"; else if(method.equalsIgnoreCase("text")) ct = "text/plain"; else throw new ServletException("Unknown method"); } else ct = "text/xml"; } if(null != cs) ct += "; charset=\ "" + cs + "\ ""; response.setContentType(ct); // transform and write the result XSLTInputSource source = new XSLTInputSource(istream); XSLTResultTarget target = new XSLTResultTarget(response.getOutputStream()); processor.process(source,null,target); } protected void writeRawXml(InputStream istream, HttpServletResponse response) throws IOException { response.setContentType("text/xml"); OutputStream ostream = response.getOutputStream(); int c = istream.read(); while(-1 != c) { ostream.write(c); c = istream.read(); } } public void writeResponse(HttpServletResponse response) throws IOException, ServletException { if(null != writer) writer.flush(); if(null == ostream 0 == ostream.size()) return; writer.flush(); InputStream istream = new ByteArrayInputStream(ostream.toByteArray()); if(null == xsl) writeRawXml(istream,response); else try { writeStyledXml(istream,response); } catch(SAXException e) { throw new ServletException(e.getMessage()); } } } You saw how the constructor selects the style sheet in the previous section. writeResponse() is called by XslServlet to perform the XSL transformation. It then delegates the actual work to writeRawXml() and writeStyledXml() . writeStyledXml() is the method that calls the XSL processor. It takes advantage of Xalan precompiled style sheets, and it won't reread a style sheet unless it has been modified: // deal with precompiled style sheets Struct s = (Struct)stylesheets.get(xsl); if(null == s s.lastModified < xsl.lastModified()) { XSLTInputSource source = new XSLTInputSource(new FileInputStream(xsl)); s = new Struct(xsl.lastModified(), processor.processStylesheet(source)); stylesheets.put(xsl,s); } else processor.setStylesheet(s.stylesheet); The method also sets the content type, according to the media type and character set selected in the style sheet: // set the content-type String ct = s.stylesheet.getOutputMediaType(), cs = s.stylesheet.getOutputEncoding(); if(null == ct) { if(s.stylesheet.isOutputMethodSet()) { String method = s.stylesheet.getOutputMethod(); if(method.equalsIgnoreCase("xml")) ct = "text/xml"; else if(method.equalsIgnoreCase("html")) ct = "text/html"; else if(method.equalsIgnoreCase("text")) ct = "text/plain"; else throw new ServletException("Unknown method"); } else ct = "text/xml"; } if(null != cs) ct += "; charset=\ "" + cs + "\ ""; response.setContentType(ct); Finally, the method calls Xalan to perform the transformation: // transform and write the result XSLTInputSource source = new XSLTInputSource(istream); XSLTResultTarget target = new XSLTResultTarget(response.getOutputStream()); processor.process(source,null,target); |