views:

450

answers:

3

I am doing conversion to image and PDF output. I need an input HTML document that is generated by our application JSPs. Essentially, I need to render the final output product of a JSP based application to a String or memory and then use that string for other processing.

What are some ways that I can just invoke the JSP renderer to get the final HTML content that is normally output to the user? Ideally, I am looking for something that will work for multiple application servers like websphere. But something that is Tomcat specific will also work.

There are a couple of other different approaches, but I think rendering the JSP (which may include sub JSPs) is the best approach.

Optional Paths that I would rather stay away from.

  1. I could perform a network request to the page using the Socket APIs and then read the final output that is rendered from that particular page. This is probably the next best option, but we work on multiple servers and JVMs, targeting the page I need would be complicated.

  2. Use a filter to get that final page output. This Ok but I have always had problems with filters and illegalstateexceptions. It never seems to work 100% the way I need to.

It seems like this should be simple. The JSP compiler is essentially just a library for parsing an input JSP document and subdocuments and then output some HTML content. I would like to invoke that process through Java code. On the server and possibly as a standalone console application.

+3  A: 

This is a downright irritating problem, one I've had to handle a few times and one I've never found a satisfactory solution to.

The basic problem is that the servlet API is of no help here, so you have to trick it. My solution is to write a subclass of HttpServletResponseWrapper which override the getWriter() and getOutput() methods and captures the data into a buffer. You then forward() your request to the URI of the JSP you want to capture, substituting your wrapper response for the original response. You then extract the data from the buffer, manipulate it, and write the end result back to the original response.

Here's my code that does this:

public class CapturingResponseWrapper extends HttpServletResponseWrapper {

    private final OutputStream buffer;

    private PrintWriter writer;
    private ServletOutputStream outputStream;

    public CapturingResponseWrapper(HttpServletResponse response, OutputStream buffer) {
     super(response);
     this.buffer = buffer;
    }

    @Override
    public ServletOutputStream getOutputStream() {
     if (outputStream == null) {
      outputStream = new DelegatingServletOutputStream(buffer);
     }
     return outputStream;
    }

    @Override
    public PrintWriter getWriter() {
     if (writer == null) {
      writer = new PrintWriter(buffer);
     }
     return writer;
    }

    @Override
    public void flushBuffer() throws IOException {
     if (writer != null) {
      writer.flush();
     }
     if (outputStream != null) {
      outputStream.flush();
     }
    }

}

The code to use it can be something like this:

HttpServletRequest originalRequest = ...
HttpServletRequest originalResponse = ...

ByteArrayOutputStream bufferStream = new ByteArrayOutputStream();
CapturingResponseWrapper responseWrapper = new CapturingResponseWrapper(originalResponse, bufferStream);

originalRequest.getRequestDispatcher("/my.jsp").forward(originalRequest, responseWrapper);

responseWrapper.flushBuffer();
byte[] buffer = bufferStream.toByteArray();
// now use the data

It's very ugly, but it's the best solution I've found. In case you're wondering, the wrapper response has to contain the original response because the servlet spec says that you cannot substitute a completely different request or response object when you forward, you have to use the originals, or wrapped versions of them.

skaffman
+1 Beat me to it.
Jack Leow
I get the feeling everyone has the same ghastly solution.
skaffman
That is a good approach. Kind of like the 'filter' oriented approach.But the only problem with this, there are two problems.1. You can't run this as a standalone system. E.g. you can't do:main() { compileJSP() }2. If you use this within filter code, some applciation servers are nitpicking about the state of the requestwrapper and I tend to get illegalstateexceptions and can't ever get the JSP to render to a string (essentially when I do it this it is never straightforward).But, yea that is a good solution.
Berlin Brown
to skaffman.The network request approach isn't that bad either. new Socket()/URL() {http://localhost/myjsppage...read the output from the request}But that involves a network request and probably more code.
Berlin Brown
More code and more slowness. :)
Jack Leow
This worked great. Thanks !!!
rmarimon
A: 

This extends HttpServletResponseWrapper which I don't have access to, is there anyway to do this without extending that class? I am also very very new to JSP.

A: 

DOH! Sorry wrong class, I don't have this one: DelegatingServletOutputStream listed in your class.

Also, I am trying to do this without having to compile a file. I would like to do it all in .jsp files. Is that possible?