Saturday, May 24, 2008

openEPRS: Web Services Console

I decided to spruce up the default page for the openEPRS web services application.  The application is implemented using the Jersey RI for JSR-311 REST Web Services.  While Jersey provides a nice UI for testing your web services, trying to integrate it into the main application was more trouble than it was worth.  Since the framework is still under development, I don't want to redo all my modifications every time a new version is released.  I still wanted to find some way of displaying what services are available and document how they are used.

Jersey uses WADL (Web Application Description Languge) to describe REST web services.  WADL is designed to provide a simple alternative to WSDL for use with XML/HTTP Web applications.  Searching the web lead me to Mark Nottingham's stylesheet for formatting WADL as HTML. This provided a starting point for a stylesheet that I could incorporate into my own application.

Now that I had a stylesheet that could convert WADL to something human readable, I could proceed with integrating the transformation with the web application.  In Jersey, accessing:
http://localhost/resources/application.wadl
will return the WADL for the application:
<application xmlns="http://research.sun.com/wadl/2006/10">
    <resources base="http://localhost:8080/openeprs-svcs/resources/">
        <resource path="/userRoles/">
            <method name="GET">
                <request>
                    <param default="0" name="start" type="xs:int" xmlns:xs="http://www.w3.org/2001/XMLSchema" />
                    <param default="10" name="max" type="xs:int" xmlns:xs="http://www.w3.org/2001/XMLSchema" />
                </request>
                <response>
                    <representation mediatype="application/xml">
                    <representation mediatype="application/json">
                </response>
            </method>
...

        </resource>
    </resources>
</application>
The easiest solution would be a servlet filter that could convert the WADL on demand. I decided that I would use a custom parameter (format=html) appended to the end of the standard URL that would notify the filter that it needed to perform the transformation. Since I needed to modify the response from the servlet, I first created a servlet response wrapper:
// Comments removed for clarity
public class ResponseWrapper extends HttpServletResponseWrapper {
    private ByteArrayOutputStream output;

    public ResponseWrapper(HttpServletResponse response) {
        super(response);
        output = new ByteArrayOutputStream();
    }

    public byte[] getData() {
        return output.toByteArray();
    }

    @Override
    public ServletOutputStream getOutputStream() {
        return new FilterServletOutputStream(output);
    }

    @Override
    public PrintWriter getWriter() {
        return new PrintWriter(getOutputStream(), true);
    }
}
and a specialized servlet output stream:
// Comments removed for clarity
public class FilterServletOutputStream extends ServletOutputStream {
    private DataOutputStream stream;

    public FilterServletOutputStream(OutputStream output) {
        stream = new DataOutputStream(output);
    }

    @Override
    public void write(int b) throws IOException {
        stream.write(b);
    }

    @Override
    public void write(byte[] b) throws IOException {
        stream.write(b);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        stream.write(b, off, len);
    }
}
Now I was ready to create the servlet filter:
// Partial code listing showing the important bits...
public class WadlFormatFilter implements Filter {
    private ServletContext ctx;
    private FilterConfig filterConfig = null;
    private TransformerFactory xsltFactory = null;
    private Templates xsltTemplates = null;
    private static final boolean debug = false;
   ...    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        if (debug) log("WadlFormatFilter:doFilter()");
        
        Throwable problem = null;
        
        try {
            if (request != null) {
                String type = request.getParameter("format");
                
                if (type != null && type.equals("html")) {
                    String contentType = "text/html";
                    
                    OutputStream out = response.getOutputStream(); 
                    ResponseWrapper wrapper = new ResponseWrapper((HttpServletResponse) response);

                    chain.doFilter(request, wrapper);
                    
                    ByteArrayInputStream test = new ByteArrayInputStream(wrapper.getData());
                    
                    Source xfrmSrc = new StreamSource(test);
                    Transformer tmplXfrmer = xsltTemplates.newTransformer();
                    CharArrayWriter finalOut = new CharArrayWriter();
                    StreamResult xfrmResult = new StreamResult(finalOut);

                    tmplXfrmer.transform(xfrmSrc, xfrmResult);
                    
                    response.setContentType(contentType);
                    response.setContentLength(finalOut.toString().length());
                    out.write(finalOut.toString().getBytes());
                    out.close();

                    if (debug) log("WadlFormatFilter:XSLT transform succeeded");      
                } else {
                    chain.doFilter(request, response);
                }
            }
        } catch (Throwable t) {
            problem = t;
            t.printStackTrace();
        }

        if (problem != null) {
            if (problem instanceof ServletException) {
                throw (ServletException) problem;
            }
            if (problem instanceof IOException) {
                throw (IOException) problem;
            }
            sendProcessingError(problem, response);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
        this.ctx = filterConfig.getServletContext();
        this.xsltFactory = TransformerFactory.newInstance();
        
        if (filterConfig != null) {
            if (debug) log("WadlFormatFilter:Initializing filter");
            
            String xsltFile = filterConfig.getInitParameter("xsltfile");
        
            try {
                this.xsltTemplates = xsltFactory.newTemplates(
                        new StreamSource(this.ctx.getRealPath(xsltFile)));
            } catch (Exception ex) {
                log(ex.toString());
            }  
        }
    }
    ...
}
I edited the application's web.xml to configure the filter:
<filter>
    <filter-name>WadlFormatFilter</filter-name>
    <filter-class>com.zunisoft.openeprs.servlet.WadlFormatFilter</filter-class>
    <init-param>
        <param-name>xsltfile</param-name>
        <param-value>/common/xsl/wadl_to_html.xsl</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>WadlFormatFilter</filter-name>
    <url-pattern>/resources/application.wadl</url-pattern>
</filter-mapping>
I rebuilt my application and navigated to:
http://localhost/resources/application.wadl?format=html
which returned the following page in my browser: