Chad Minick
Software Developer

XSL Transformation with JAXB

November 17, 2009 Comments, , , , ,

The goal here is to take the output of a JAXB Marshaller and pass it straight to an xslt processor like JAXP. The issue with this is that JAXP takes it's input from various sorts of input streams and JAXB marshaller outputs usually in some sort of output stream. I saw some (bad) solutions about changing an output stream into an input stream and then pass it to JAXP. eww. The cool thing about JAXB is that it can marshall into a SAX ContentHandler. And there is an implementation of ContentHandler that handles XSL transformations for us! Here is an example of a http servlet that will do an xslt transformation on a JAXB marshalled POJO and dump the results right into into the servlet's output stream:

package org.codeangel.awesome;

import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.*;

public class DisplayServlet extends HttpServlet {

  private Templates displayTemplate;
  private TransformerFactory transFact;
  private static JAXBContext jaxbc;

  static {
    try {
      jaxbc = JAXBContext.newInstance(MyClass.class);
    } catch (JAXBException e) {
      e.printStackTrace();
    }
  }
    
  @Override
  public void init() throws ServletException {
    //We are going to compile the xslt stylesheet on init and cache the result. 
    //(in this example display.xsl is in the root of the webapp)
    transFact = TransformerFactory.newInstance();
    try {
      displayTemplate = transFact.newTemplates(
        new StreamSource(getServletContext()
              .getResource("/display.xsl").toExternalForm())
      );
    } catch (TransformerConfigurationException e) {
      e.printStackTrace();
    } catch (MalformedURLException e) {
      e.printStackTrace();
    }

  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    //In this example I'm fetching an object from the servlet context, I could
    //just as easily got the object from hibernate, or any other datasource
    MyClass myObject = (MyClass)getServletContext().getAttribute("MyObject");

    //I just happen to be tranforming to html, you can change this as needed
    resp.setContentType("text/html");
            
    try {
      Marshaller marshaller = jaxbc.createMarshaller();
      OutputStream out = resp.getOutputStream();

      TransformerFactory transFact = TransformerFactory.newInstance();
      Result outputResult = new StreamResult(out);
            
      TransformerHandler handler = 
      ((SAXTransformerFactory) transFact).newTransformerHandler(displayTemplate);
      handler.setResult(outputResult);

      //this is where everything happens!
      marshaller.marshal(myObject, handler);
                
    }  catch (TransformerConfigurationException e) {
       e.printStackTrace();
    } catch (JAXBException e) {
      e.printStackTrace();
    }
  }
}

Edit: as Jesper has pointed out, you really should only create one JaxBContext, as it's pretty heavy. I moved the creation to a static block for simplification... You could also use a singleton pattern here as well.

comments powered by Disqus
Chad Minick | Software Developer
Twitter | Github | LinkedIn