With Seam, you can create a pdf file on the fly using only xhtml pages, but if you
want to get the PDF file’s binary data, that is much harder task. The basic
problem, that this function was not designed to provide the binary data, rather
than just generate the PDF file using JSF custom tags. You can read the
solution in a lot of blogs and forums: just create a mock FacesContext, use
that for render the page, then extract the binary data. I have tried the solutions
described in various places, but for JBoss AS 5.1 and Seam 2.2.0 GA they didn’t
worked. The main problem was to create the mock
FacesContext, because you have to have the
javax.faces.application.Application and
javax.faces.render.RenderKitFactory instances which I had no luck
to get at the time of the creation of the mock FacesContext. But by
implementing a servlet context listener, I was able to capture both values. You need to register the DocumentServlet and the custom servlet context listener into the web.xml, create the /simple.xhtml page (see later) and put the xhtml code in paragraph #3 into an other page (the menu, for example). The other classes should be put into the ejb module of your application, into the org.example.pdf package. Let's see the steps in detail:
1.Servlet Context Listener and Document Servlet registration in web.xml
In order the PDF generation on the fly to be able to work,
you must add the followings servlet mapping to the web.xml. The servlet context
listener is used to capture the javax.faces.application.Application and
javax.faces.render.RenderKitFactory instances when the servlet context
initialized.
<servlet>
<servlet-name>Document Store Servlet</servlet-name>
<servlet-class>org.jboss.seam.document.DocumentStoreServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Document Store Servlet</servlet-name>
<url-pattern>*.pdf</url-pattern>
</servlet-mapping>
<listener>
<listener-class>
org.example.pdf.MockServletContextListener
</listener-class>
</listener>
2. Page that generates PDF
You must create a an xhtml page ("/simple.xhtml"), to test the export
function:
<p:document xmlns:p="http://jboss.com/products/seam/pdf">
<p:chapter>Hello PDF!</p:chapter>
</p:document>
3. Testing xhtml code
In order to test the PDF export function, the following link
could be placed somewhere (e.g,: into the menu). This will call the
mockPDFFileSaver’s savePDF(fileName, viewName) method, which simply generated
the pdf then saves the results into the given file. This example will save the pdf into "d:\simple.pdf"
<s:link
includePageParams="false"
propagation="none"
value="Simple PDF test"
action="#{mockPDFFileSaver.
savePDF('d:\\simple.pdf','/simple.xhtml')}">
</s:link>
4.Servlet Context Listener
The listener can capture the „Application” and „Render Kit
Factory” instances. It then saves into the Seam component „factoryData”.
Lifecycle.beginCall() initializes Seam.
package org.example.pdf;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.render.RenderKitFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.jboss.seam.Component;
import org.jboss.seam.contexts.Lifecycle;
/**
* ServletContextListener implementation to capture the JSF Application and
* renderKitFactory. Should be registered into web.xml to work
*
* @author anemeth
*
*/
public class MockServletContextListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent event) {
}
public void contextInitialized(ServletContextEvent event) {
Lifecycle.beginCall();
Application application = ((ApplicationFactory) FactoryFinder
.getFactory(FactoryFinder.APPLICATION_FACTORY)).getApplication();
RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder
.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
FactoryData factoryData = (FactoryData) Component.getInstance("factoryData", true);
factoryData.setApplication(application);
factoryData.setRenderKitFactory(renderKitFactory);
}
}
5. Mock Servlet Context
The MockServletContext from Seam
2.2 was modified in order to work under JBoss AS 5.1
package org.example.pdf;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.render.RenderKitFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.jboss.seam.Component;
import org.jboss.seam.contexts.Lifecycle;
/**
* ServletContextListener implementation to capture the JSF Application and
* renderKitFactory. Should be registered into web.xml to work
*
* @author anemeth
*
*/
public class MockServletContextListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent event) {
}
public void contextInitialized(ServletContextEvent event) {
Lifecycle.beginCall();
Application application = ((ApplicationFactory) FactoryFinder
.getFactory(FactoryFinder.APPLICATION_FACTORY)).getApplication();
RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder
.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
FactoryData factoryData = (FactoryData) Component.getInstance("factoryData", true);
factoryData.setApplication(application);
factoryData.setRenderKitFactory(renderKitFactory);
}
}
6. FactoryData
This is a simple Seam component for holding the
„javax.faces.application.Application” and „javax.faces.render.RenderKitFactory”
package org.example.pdf;
import javax.faces.application.Application;
import javax.faces.render.RenderKitFactory;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
/**
* Class for holding the JSF "Application" instance and the renderKitFactory.
* @author anemeth
*
*/
@Name("factoryData")
@Scope(ScopeType.APPLICATION)
public class FactoryData {
private Application application;
private RenderKitFactory renderKitFactory;
/**
* @return the application
*/
public Application getApplication() {
return this.application;
}
/**
* @return the renderKitFactory
*/
public RenderKitFactory getRenderKitFactory() {
return this.renderKitFactory;
}
/**
* @param application
* the application to set
*/
public void setApplication(Application application) {
this.application = application;
}
/**
* @param renderKitFactory
* the renderKitFactory to set
*/
public void setRenderKitFactory(RenderKitFactory renderKitFactory) {
this.renderKitFactory = renderKitFactory;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("FactoryData [application=");
builder.append(this.application);
builder.append(", renderKitFactory=");
builder.append(this.renderKitFactory);
builder.append("]");
return builder.toString();
}
}
7. MockPDFFileSaver
This is a simple Seam component
to demonstrate the PDF export function
package org.example.pdf;
import java.io.FileOutputStream;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
@Name("mockPDFFileSaver")
public class MockPDFFileSaver {
@In(create = true)
PdfExporter pdfExporter;
/**
* Saves the PDF generated by the pageName to the file denoted by the
* fileName
*
* @param fileName
* the file name
* @param pageName
* the page name
*/
public void savePDF(String fileName, String pageName) {
byte[] pdfBytes = pdfExporter.pdfExport(pageName);
try {
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(pdfBytes);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
8. PDFExporter
The pdf export functionality is implemented in this Seam
component. This works by rendering the given view into a mock context. The
DocumentStore then contains the rendered pdf. The real question is what the id
of the generated pdf. The DocumentStrore is a Conversation scoped component so
basically the Id will be started from 1 when a new conversation starts. But for
sure, the Id can be queried, in the case more document is generated per
conversation.
package org.example.pdf;
import java.io.ByteArrayOutputStream;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.document.DocumentData;
import org.jboss.seam.document.DocumentStore;
import org.jboss.seam.faces.Renderer;
@Name("pdfExporter")
public class PdfExporter {
/**
* returns the byte array of the PDF file generated using the page xhtml
* @param page the page name of the xhtml for the pdf
* @return the byte array containing the PDF file data
*/
public byte[] pdfExport(String page) {
byte[] pdfBytes = null;
EmptyFacesContext emptyFacesContext = null;
try {
emptyFacesContext = new EmptyFacesContext();
try {
Renderer renderer = Renderer.instance();
renderer.render(page);
DocumentStore store = DocumentStore.instance();
String nextId = store.newId();
long docId = Long.parseLong(nextId) - 1;
DocumentData data = store.getDocumentData("" + docId);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
data.writeDataToStream(baos);
pdfBytes = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
emptyFacesContext.restore();
}
return pdfBytes;
}
}
I'm really enjoying the theme/design of your site. Do you ever run into any web browser compatibility problems? A handful of my blog readers have complained about my website not operating correctly in Explorer but looks great in Chrome. Do you have any tips to help fix this problem?
ReplyDeleteAlso see my webpage - Scott Tucker