Render a JSP page into HTML in a String

I am using a mail template system that gets templates as JSP pages placed in /WEB-INF/mailtemplates/

I want to internally parse JSPs as if they were real JSPs in my tomcat but:

- Get get them using getResourceAsStream (or any other way)

- make JSP engine translate JSP into HTML into a String object.

- Send a mail with this HTML. (this part is already finished)

What I need is a method with the following proposed signature:

public String getMailTemplate(java.io.InputStream jspPageStream, javax.servlet.HttpServletRequest request) {}

The method must get the JSP from the stream, render it into the HTML and return it as a String.

Of course, it must render the JSP as a real JSP, using the apserver JSP engine. It must so replace any tags and EL objects:

${session.user.name} -> The name of the user

<c:forEach, ...

etc. etc. etc.

緼ny ideas / directions on how to achieve this?

thanks,

Ignacio>

[995 byte] By [icordobaa] at [2007-11-27 9:47:08]
# 1

For your task, there is no solution that will not be dependent on the Application Server you are running. Therefore, if you are running on Tomcat, your solution will depend on Tomcat's JSP compiler.

If you are okay with this dependency, poke around for the documentation on the Tomcat Jasper compiler. I looked into doing something similar once, and eventually gave up and went with an XML-based solution. The code to translate the JSP, compile the Java, run the resultant Servlet is much more complex than it may seem.

--

Discussion of Java, Struts, Spring, Hibernate, etc. http://www.wantii.com/

weswannemachera at 2007-7-12 23:58:55 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 2

I've figured a ver ugly workarround... Using HttpUtils to make a self http request in my application through Tomcat, get the HTML and store it in a String, but I obviously loose session and request information, of course, as I am creating a plain new request. Maybe I code this till I find something a bit more elegant.

Thanks,

Ignacio

icordobaa at 2007-7-12 23:58:55 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 3

How does this e-mail get triggered?

From what you have said, you seem to want to use the current request/session, run a jsp page, and get the result back into a string.

Using a RequestDispatcher.include() call would let you execute a JSP on the current server, and let you pass in the current request.

You then just need to redirect the response from generating that JSP to something else that you can use. Implementing a requestWrapper object will let you do that.

Check out this post which provides code for a similar situation

http://forum.java.sun.com/thread.jspa?threadID=730559&messageID=4220069

The example there is for a filter. To use in a servlet replace the call to chain.doFilter with a call to requestDispatcher.include.

Cheers,

evnafets

evnafetsa at 2007-7-12 23:58:55 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 4

The perfect example of how the mail gets triggered is the "Forgot my password mail". The user invokes a serlvet (a struts action in fact) that must generate an email, but return a complete different response to the user browser: ("Please check your mail"). The email is in HTML and I want it to be rendered from a JSP, which has nothiing to do with the response returned to the browser, but sharing the request (user info and other objects set as attributes to the response). I want to achieve this because my design department likes designing this emails directly as JSPs (somehow I have managed to make them learn some JSP) and with complete independence from my code. Thanks for your examples. I'll try to figure it out how to split response to the browser from response in the HttpServletResponseWrapper.

icordobaa at 2007-7-12 23:58:55 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 5

Just to expand a bit, here is the example adapted for your specific situation:

public void getEmailText(ServletRequest request, ServletResponse response, String emailURL) {

// create an output stream - to file, to memory...

ByteArrayOutputStream out = new ByteArrayOutputStream();

// create the "dummy" response object

RedirectingServletResponse dummyResponse = new RedirectingServletResponse(response, out);

// get a request dispatcher for the email template to load

RequestDispatcher rd = request.getRequestDispatcher(emailURL);

// execute that JSP

rd.include(request, dummyResponse);

// at this point the ByteArrayOutputStream has had the response written into it.

byte[] result = out.getByteArray();

// now you can do with it what you will.

String emailText = new String(result);

return emailText;

}

and then invoke it with something like

getEmailText(request, response, "/WEB-INF/templates/forgotPassword.jsp");

evnafetsa at 2007-7-12 23:58:55 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 6

Hi again

any ideas on why this is not working?

I get an empty ByteArrayOutputStream / String (include() method in the dispatcher doesn't make the dispatcher write anything into the OutputStream).

templatePath is the absolute path of the JSP ("/user/ForgotPassword.jsp")

ByteArrayOutputStream out = new ByteArrayOutputStream();

RequestDispatcher dispatcher = servletcontext.getRequestDispatcher(templatePath);

RedirectingServletResponse dummyResponse = new RedirectingServletResponse(response, out);

dispatcher.include(request,dummyResponse);

return out.toString();

Thanks for any info.

icordobaa at 2007-7-12 23:58:55 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 7

Oooops... wrote my last message before reading yours but I've seen the code is identical (i've tried getting the dispatcher from both the request and the ServletContext, but get the same empty result. Nothing is written into the RedirectServletStream inner class object inside the RedirectingServletResponse object.

thanks again

icordobaa at 2007-7-12 23:58:55 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 8
hmmm. At a guess its buffering things somewhere.Solution would be to flush things out.trydummyResponse.flushBuffer()out.flush()
evnafetsa at 2007-7-12 23:58:56 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 9

Hi again,

I've been trying to put this to work with no success... even after flushing as you suggested.

The problem seems to be that at least tomcat 5 doesn't write directly to the ResponseWrapper, but invokes getResponse() to get the wrapped real response, writing there.

In the constructor:

public RedirectingServletResponse(HttpServletResponse response, OutputStream out) {

super(response);

this.out = new RedirectServletStream(out);

}

Real servlet response is passed to the parent class. I've overcharged the getResponse() method in the Wrapper and it is invoked several times. The problem is then where to get a HttpServletResponse object, but not the real response one. Using the wrapper doesn't work as getWriter() and getOutputstrem() methods there are only invoked by the JspWriter object in the compiled jsp via "releasePageContext()" method. Can't really trace this, but have seen it with break points and cheked in the .java jsp code of the page.

I'll have a look to figure out how to get a new instance of the HttpServletResponse object to pass it to the Wrapper constructor... any ideas?

(Hope I've been able to explain the problem)

Thanks,

Ignacio

icordobaa at 2007-7-12 23:58:56 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 10
I'm confused. Where is it calling the method getResponse() from?I don't see how it could get back to the original response.
evnafetsa at 2007-7-12 23:58:56 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 11

I have overcharged RedirectingServletResponse.getResponse to insert a breakpoint. When it stops I get this stack:

RequestDispatcher.include(request,dummyResponse)

ApplicationDispatcher.include()

ApplicationDispatcher.doInclude()

ApplicationDispatcher.wrapResponse()

RedirectingServletResponse.getResponse()

That is why I guess that the JSP page is writing directly to the original response and not to ResponseWrapper.getWriter() or ResponseWrapper.getOutputStream(). I get an empty String then as nothing has been written to the RedirectingServletResponse ByteArrayOutputStream.

Thanks,

Ignacio

icordobaa at 2007-7-12 23:58:56 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 12

[nobr]Ok, just tried it out myself, and I'm getting the same result you are.

Nothing makes it out to the RedirectingServletResponse.

It appears to be buffering it somewhere in the included page.

If you put a command to flush the buffer on the jsp page being included: <% out.flush; %> then it works.

So why doesn't it work normally? Why doesn't the call to requestDispatcher.include (or forward) flush/commit its response?

I will admit to being stuck on this one. I'm figuring its something in the internal workings of Tomcat/JSP to do with when a page is flushed or not, but I can't thing of anything right now.

Here is the test I put together to try this out (along with a few corrections to my previous code). I will admit to being stumped :-)

The response wrapper:

package web;

import java.io.IOException;

import java.io.OutputStream;

import java.io.PrintWriter;

import javax.servlet.ServletOutputStream;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpServletResponseWrapper;

public class RedirectingServletResponse extends HttpServletResponseWrapper {

RedirectServletStream out;

/**

* @param arg0

*/

public RedirectingServletResponse(HttpServletResponse response, OutputStream out) {

super(response);

this.out = new RedirectServletStream(out);

}

/* (non-Javadoc)

* @see javax.servlet.ServletResponse#flushBuffer()

*/

public void flushBuffer() throws IOException {

out.flush();

}

/* (non-Javadoc)

* @see javax.servlet.ServletResponse#getOutputStream()

*/

public ServletOutputStream getOutputStream() throws IOException {

return out;

}

/* (non-Javadoc)

* @see javax.servlet.ServletResponse#getWriter()

*/

public PrintWriter getWriter() throws IOException {

return new PrintWriter(out);

}

/* (non-Javadoc)

* @see javax.servlet.ServletResponseWrapper#getResponse()

*/

public ServletResponse getResponse() {

return super.getResponse();

}

/* (non-Javadoc)

* @see javax.servlet.ServletResponseWrapper#setResponse(javax.servlet.ServletResponse)

*/

public void setResponse(ServletResponse arg0) {

super.setResponse(arg0);

}

private static class RedirectServletStream extends ServletOutputStream {

OutputStream out;

RedirectServletStream(OutputStream out) {

this.out = out;

}

public void write(int param) throws java.io.IOException {

out.write(param);

}

}

}

A JSP using this class: requestWrapperTest.jsp

<%@ page import="java.io.*" %>

<%@ page import="web.RedirectingServletResponse" %>

<%@ page contentType="text/html;charset=UTF8" %>

<%!

public String getEmailText(HttpServletRequest request, HttpServletResponse response, String emailURL) {

try{

// create an output stream - to file, to memory...

ByteArrayOutputStream out = new ByteArrayOutputStream();

// create the "dummy" response object

RedirectingServletResponse dummyResponse;

dummyResponse = new RedirectingServletResponse(response, out);

// get a request dispatcher for the email template to load

RequestDispatcher rd = request.getRequestDispatcher(emailURL);

// execute that JSP

rd.include(request, dummyResponse);

// at this point the ByteArrayOutputStream has had the response written into it.

dummyResponse.flushBuffer();

byte[] result = out.toByteArray();

System.out.println("result = " + result.length);

// now you can do with it what you will.

String emailText = new String(result);

return emailText;

}

catch (Exception e){

e.printStackTrace(System.out);

return "DANGER WILL ROBINSON!";

}

}

%>

<html>

<body>

<h2> Server Info Virtual!!</h2>

Server info = <%= application.getServerInfo() %> <br>

Servlet engine version = <%= application.getMajorVersion() %>.<%= application.getMinorVersion() %><br>

Java version = <%= System.getProperty("java.vm.version") %><br>

<hr>

<%

String result = getEmailText(request, response, "/testLoad.jsp");

System.out.println(result);

%>

<%= result %>

</body>

</html>

And the template page being included: testLoad.jsp

This is the page loaded for testing

<%

System.out.println("in the inner jsp");

// uncomment the following line to see this example work

//out.flush();

%>

[/nobr]

evnafetsa at 2007-7-12 23:58:56 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...
# 13

Hi there,

here is the end of the main method of the generated .java file by tomcat for my JSP. The handlePageException method is the only moment where the wrapper getWriter() method is invoked, but not to write anything, and just to "end" things up. It seems the code is written to the original Response and this last invocation is done to the ResponseWrapper. I'll have a look to your code and include the flush small scriptlet to see if at least it makes it return the html code.

thanks,

Ignacio

out.write("</div>\n");

out.write("<div class=\"box\">\n");

out.write(" ");

if (_jspx_meth_html_form_1(_jspx_page_context))

return;

out.write("\n");

out.write("</div>\n");

} catch (Throwable t) {

if (!(t instanceof SkipPageException)){

out = _jspx_out;

if (out != null && out.getBufferSize() != 0)

out.clearBuffer();

if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);

}

} finally {

if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);

}

}

icordobaa at 2007-7-12 23:58:56 > top of Java-index,Enterprise & Remote Computing,Web Tier APIs...