How To Create Email from JSF Pages
Posted by Roger Keays, 11 October 2008, 8:29 PM
Hi everybody. In this blog I'm going to share a little trick for creating emails from JSF pages (or views, if you prefer). The concept is actually independent of JSF and could be used for any servlet-based technology, but JSF is sooooo in right now, so we're going to focus on that.
Right, so the basic idea is to temporarily trick the ServletResponse object into writing all the content to an in-memory buffer, rather than streaming it to the client's browser. We then manually invoke the JSF render phase for the particular view using the special ServletResponse and voila!, the captured output can be put into an email.
Let's have a look at some of the code.
First, here is our ServletResponse which captures the (text-only) output. It wraps the existing response object, but intercepts the vital getWriter() method to return a local in-memory CharArrayWriter:
/**
* This is a response wrapper which saves all of the output to a char array,
* so it can be retrieved as a string afterwards with the toString() method.
* We only support capturing text output currently.
*/
public class ResponseCatcher implements HttpServletResponse {
/** the backing output stream for text content */
CharArrayWriter output;
/** a writer for the servlet to use */
PrintWriter writer;
/** a real response object to pass tricky methods to */
HttpServletResponse response;
/**
* Create the response wrapper.
*/
public ResponseCatcher(HttpServletResponse response) {
this.response = response;
output = new CharArrayWriter();
writer = new PrintWriter(output, true);
}
/**
* Return a print writer so it can be used by the servlet. The print
* writer is used for text output.
*/
public PrintWriter getWriter() {
return writer;
}
public void flushBuffer() throws IOException {
writer.flush();
}
public boolean isCommitted() {
return false;
}
public boolean containsHeader(String arg0) {
return false;
}
/* wrapped methods */
public String encodeURL(String arg0) {
return response.encodeURL(arg0);
}
public String encodeRedirectURL(String arg0) {
return response.encodeRedirectURL(arg0);
}
public String encodeUrl(String arg0) {
return response.encodeUrl(arg0);
}
public String encodeRedirectUrl(String arg0) {
return response.encodeRedirectUrl(arg0);
}
public String getCharacterEncoding() {
return response.getCharacterEncoding();
}
public String getContentType() {
return response.getContentType();
}
public int getBufferSize() {
return response.getBufferSize();
}
public Locale getLocale() {
return response.getLocale();
}
public void sendError(int arg0, String arg1) throws IOException {
response.sendError(arg0, arg1);
}
public void sendError(int arg0) throws IOException {
response.sendError(arg0);
}
public void sendRedirect(String arg0) throws IOException {
response.sendRedirect(arg0);
}
/* null ops */
public void addCookie(Cookie arg0) {}
public void setDateHeader(String arg0, long arg1) {}
public void addDateHeader(String arg0, long arg1) {}
public void setHeader(String arg0, String arg1) {}
public void addHeader(String arg0, String arg1) {}
public void setIntHeader(String arg0, int arg1) {}
public void addIntHeader(String arg0, int arg1) {}
public void setStatus(int arg0) {}
public void setStatus(int arg0, String arg1) {}
public void setCharacterEncoding(String arg0) {}
public void setContentLength(int arg0) {}
public void setContentType(String arg0) {}
public void setBufferSize(int arg0) {}
public void resetBuffer() {}
public void reset() {}
public void setLocale(Locale arg0) {}
/* unsupported methods */
public ServletOutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Return the captured content.
*/
@Override
public String toString() {
return output.toString();
}
}
Now, all we need to do is render the view using the ResponseCatcher above:
/**
* Render a view in memory and return the content as a string. The
* request parameter 'emailClient' is set to true during rendering.
*/
public String captureView(String template) {
// initial values
FacesContext faces = FacesContext.getCurrentInstance();
ExternalContext context = faces.getExternalContext();
HttpServletResponse response = (HttpServletResponse)
context.getResponse();
ResponseCatcher catcher = new ResponseCatcher(response);
ViewHandler views = faces.getApplication().getViewHandler();
// render the message
try {
context.setResponse(catcher);
context.getRequestMap().put("emailClient", true);
views.renderView(faces, views.createView(faces, template));
context.getRequestMap().remove("emailClient");
context.setResponse(response);
} catch (IOException ioe) {
String msg = "Failed to render email internally";
faces.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, msg, msg));
return null;
}
return catcher.toString();
}
Pretty straight forward huh?. The important line, where the view is actually rendered, is shown in bold above. Now that you have your view rendered as a string you can easily turn it into an email. But that part of the job is your homework ;)
Like the rest of the code posted on my blog, you can find this in the Furnace Webcore library. I'm currently only using this with Facelets, but I used to do something similar with the JSP ViewHandler so I'd expect it to work okay with JSP too.
À bientôt!
| << Teach Yourself Malay 6 | Back to Blog | Teach Yourself Malay 5 >> |
Comment posted by: Pete Muir on 12 October 2008, 6:03 AM
Hey Roger,
Did you know we have an email library in Seam which allows you to do this, and much more (has special tags for setting from, to, subject, headers, attachments and body).
Though, we're not using your rendering method - I didn't know you could make JSF do that, currently we do it through Facelets directly, but yours is *much* neater.
Comment posted by: Roger Keays on 12 October 2008, 11:03 AM
Hi Pete,
I forgot to mention the Seam method... sorry! Here is this link to the Seam email docs for other readers:
http://docs.jboss.org/seam/2.1.0.CR1/reference/en-US/html/mail.html
I know Seam has a component set for creating PDFs too. FWIW, you can also use the captureView() method described above in conjunction with the Flying Saucer xhtml renderer to create pdfs from JSF views. There is a pretty detailed tutorial on java.net describing this:
Combine JSF Facelets and the Flying Saucer XHTML Renderer
I'd like to create a JSF component which renders its children to a pdf, but last time I tried Facelets did not support binary output from components (that was probably a year ago now though).
Comment posted by: Roger Keays on 5 February 2009, 5:18 PM
Woah, the captureView() method returns an empty string when you use RichFaces if faces.responseComplete() has been called. Their ViewHandler doesn't do anything in this case, which makes sense I guess, but caught me out.
Comment posted by: Zoltan, Gyurasits on 27 April 2009, 10:04 PM
Hi Roger!
It's wonderful solutions!
I need this, but not work for me :(
I get only content outside of view tag. Can you help a little?
This is my example:
www . gyurasits . hu / GenerateJSFView . rar
I use the SUN reference JSF implementation.
Thanks a lot!
Best Regards!
Zoltan Gyurasits
Add a comment
Please visit http://www.ilikespam.com/blog/how-to-create-email-from-jsf-pages to add your comments.