Tested Solution!
This solution actually get ideas already posted here (specially from @gyan).
- Write a Servlet to zip
(you could use an filter for that too. For example, suppose that you have a ZipFilter. You could map the filter for all *.zip and chain that filter with a URLRewrite filter for respective .jsf URL).
public class ZipServlet
extends HttpServlet {
@Override
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException,
IOException {
ZipEntry zipEntry = new ZipEntry("helloWorld.html");
ZipHttpServletResponseWrapper respWrapper = new ZipHttpServletResponseWrapper(response, zipEntry);
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/helloWorld.jsf");
dispatcher.forward(request, respWrapper);
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "inline; filename=output.zip;");
response.flushBuffer();
respWrapper.getOutputStream().close();
}
}
There's no much to say about that. From Java 7, you can use a native Class to create zip files properly. Using Decorator pattern with ZipOutputStream on top of response.getOutputStream() should be recommended way.
Remember that HttpServletResponseWrapper is a decorator. You should not use that if you don't want to reuse the target "servlet" output directly (you could use an stunt HttpServletResponse rather than use HttpServletResponseWrapper).
public class ZipHttpServletResponseWrapper
extends HttpServletResponseWrapper {
private ZipEntry entry;
private ZipServletOutputStreamWrapper streamWrapper;
private ZipOutputStream outputStream;
private PrintWriter printWriter;
public ZipHttpServletResponseWrapper(HttpServletResponse response, ZipEntry entry) {
super(response);
this.entry = entry;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (streamWrapper == null) {
outputStream = new ZipOutputStream(this.getResponse().getOutputStream());
outputStream.putNextEntry(entry);
streamWrapper = new ZipServletOutputStreamWrapper(outputStream);
}
return streamWrapper;
}
@Override
public PrintWriter getWriter() throws IOException {
if (printWriter == null) {
printWriter = new PrintWriter(getOutputStream());
}
return printWriter;
}
private class ZipServletOutputStreamWrapper
extends ServletOutputStream {
private ZipOutputStream outputStream;
public ZipServletOutputStreamWrapper(ZipOutputStream outputStream) {
this.outputStream = outputStream;
}
@Override
public void close() throws IOException {
outputStream.closeEntry();
outputStream.finish();
}
@Override
public void write(int b) throws IOException {
outputStream.write(b);
}
}
}
- Now, the secret: mapping wisely!
Some important parts of JSF could use Filter chain (for example, myfaces from Apache use some extensions to provide some JSF components). In this case, you should specify in these filters digest FORWARD and REQUEST
<filter-mapping>
<filter-name>extensionsFilter</filter-name>
<url-pattern>*.jsf</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>