I have a code which runs apache fop against xml content and xsl markup and gives me the apache Intermediate Format output:
StreamSource contentSource = new StreamSource(xmlContentStream);
StreamSource transformSource = new StreamSource(xslMarkupStream);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
Transformer xslfoTransformer = getTransformer(transformSource);
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
IFDocumentHandler targetHandler = foUserAgent.getRendererFactory().createDocumentHandler(
foUserAgent, MimeConstants.MIME_PDF);
FPSIFSerializer fpsSerializer = new FPSIFSerializer();
fpsSerializer.setContext(new IFContext(foUserAgent));
fpsSerializer.mimicDocumentHandler(targetHandler);
foUserAgent.setDocumentHandlerOverride(fpsSerializer);
Fop fop = fopFactory.newFop("application/X-fop-intermediate-format", foUserAgent, outStream);
DefaultHandler defaultHandler = fop.getDefaultHandler();
Result res = new SAXResult(defaultHandler);
xslfoTransformer.transform(contentSource, res);
Then I use that Intermediate Format file to render pdf and png files out of it.
I'm able to set up my own serilaizer here (FPSIFSerializer()).
I have several pages reports, but I don't need to process all of them. Is there any way to skip some pages or extract them from IntermediateFormat so I will be able e.g. to render only 1st page as png and then 2nd to pdf, etc ?
There
http://svn.apache.org/viewvc/xmlgraphics/fop/branches/archive/fop-1_1/examples/embedding/java/embedding/intermediate/ExampleConcat.java?view=markup
is an example of how to concatenate files via IFConcatenator, so I wonder about the best way to split the multipage file?
Thank_you!
The way I've done it is using custom document handler.
/**
* Custom Apache FOP Intermediate Format document handler which allows page skipping.
* Not thread safe.
*/
public class IFPageFilter extends IFDocumentHandlerProxy {
private static final Logger LOGGER = LoggerFactory.getLogger(IFPageFilter.class);
private int currentPage;
private final int desiredPage;
/**
* #param delegate The real document handler
* #param desiredPage the page you want to render (1-based). Other pages will be skipped.
*/
public IFPageFilter(final IFDocumentHandler delegate, final int desiredPage) {
super(delegate);
this.desiredPage = desiredPage;
}
#Override
public void startPage(final int index, final String name, final String pageMasterName, final Dimension size) throws IFException {
currentPage = index + 1;
if (currentPage == desiredPage) {
super.startPage(index, name, pageMasterName, size);
} else {
// do nothing
LOGGER.debug("Page skipped");
}
}
#Override
public IFPainter startPageContent() throws IFException {
if (currentPage == desiredPage) {
return super.startPageContent();
} else {
return EmptyPainter.getInstance();
}
}
#Override
public void endPageContent() throws IFException {
if (currentPage == desiredPage) {
super.endPageContent();
}
}
}
Then you can attach your handler like that:
final IFDocumentHandler targetHandler = FOP_FACTORY.getRendererFactory().createDocumentHandler(userAgent, mime);
final IFPageFilter documentHandler = new IFPageFilter(targetHandler, page);
final ByteArrayOutputStream mimeOut = new ByteArrayOutputStream(XSL_STREAM_BUFFER_SIZE);
IFUtil.setupFonts(documentHandler);
// Tell the target handler where to write the PDF to
targetHandler.setResult(new StreamResult(mimeOut));
try (final InputStream is = ifStream.toInputStream()) {
final Source src = new StreamSource(is);
new IFParser().parse(src, documentHandler, userAgent);
}
return mimeOut;
and you will get the only page you need in the output stream.
Class EmptyPainter is a dirty hack. It is empty implementation of apache IFPainter, it used here to skip page content and avoid NPE. I'm not happy about it, but that is the only way I was able to make it work.
Please note that I use FOP 1.1, and if you faced with such problems it worth to look at trunk - some of them already solved there. I guess dirty hack with EmptyPainter will not be necessary in trunk.
Please give tips if something could be done better here.
Thanks
Related
I have an action that turns XML data into an XSL based report that is viewed on a web page. A separate action called by a user can be used to transform this report into a PDF and save it to a location.
I am looking to use the Quartz Scheduler to run and save the report as a PDF every day. I have confirmed that the Quartz Scheduler runs successfully, however when it attempts to transform the data into a PDF report is fails.
public byte[] render(Action action, String location) throws Exception {
// Transform the source XML to System.out.
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
// configure fopFactory as desired
FopFactory fopFactory = FopFactory.newInstance();
// configure foUserAgent as desired
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
// Create a transformer for the stylesheet.
Templates templates = null;
Transformer transformer;
if (location != null) {
templates = getTemplates(location);
transformer = templates.newTransformer();
} else {
transformer = TransformerFactory.newInstance().newTransformer();
}
transformer.setURIResolver(getURIResolver());
Object result = action;
Source xmlSource = getDOMSourceForStack(result);
// Construct fop with desired output format
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
Result res = new SAXResult(fop.getDefaultHandler());
transformer.transform(xmlSource, res);
return out.toByteArray();
} catch (Exception e) {
throw e;
} finally {
out.close(); // ...and flush...
}
}
protected Templates getTemplates(String path) throws TransformerException, IOException {
if (path == null) {
throw new TransformerException("Stylesheet path is null");
}
Templates templates = null;
URL resource = ServletActionContext.getServletContext().getResource(path);
if (resource == null) {
throw new TransformerException("Stylesheet " + path + " not found in resources.");
}
TransformerFactory factory = TransformerFactory.newInstance();
templates = factory.newTemplates(new StreamSource(resource.openStream()));
return templates;
}
protected Source getDOMSourceForStack(Object value)
throws IllegalAccessException, InstantiationException {
return new DOMSource(getAdapterFactory().adaptDocument("result", value));
}
protected AdapterFactory getAdapterFactory() {
if (adapterFactory == null) {
adapterFactory = new AdapterFactory();
}
return adapterFactory;
}
protected void setAdapterFactory(AdapterFactory adapterFactory) {
this.adapterFactory = adapterFactory;
}
protected URIResolver getURIResolver() {
return new ServletURIResolver(
ServletActionContext.getServletContext());
}
}
The action parameter is the action that runs the report that will be transformed and the location parameter is the location of the XSL Stylesheet that formats the report. This action functions when called by the user, but when Quartz tries to call it on a scheduled basis it throws a NullPointerException error at the
URL resource = ServletActionContext.getServletContext().getResource(path);
line. Is there a way to get Quartz to work with this transformation action?
Your code throws NPE because it is executed by Quartz outside of a Struts action and therefore ServletActionContext.getServletContext() returns null. You need to find out a different way to get hold of the ServletContext. The easiest would be implementing a ServletContextListener that would save the ServletContext instance in a private static field when its contextInitialized method is invoked. Then in your Quartz job code, you would use something like this:
MyServletContextListener.getServletContext().getResource(path);
getServletContext is a static method that you will need to add to your ServletContextListener and it will simply return the saved ServletContext instance.
You need to make sure Quartz is started AFTER your ServletContextListener's contextInitialized method has been called. The easiest would be starting Quartz from the contextInitialized method. This ensures that when Quartz executes any of your jobs, the ServletContext field would be initialized and MyServletContextListener.getServletContext() would not return null.
Does Apache Velocity include a mechanism for adding metadata to a template?
I'm trying to add some extra information to my templates (e.g., type and descriptive name), and then read those to programmatically group templates by type, and list the templates on the UI using their descriptive name.
I've tried to use literal #[[...]]# blocks (and parse them), and #set directives, but both a have issues. They are hacky (require some parsing of the template) and far from elegant.
Hmmm, I'm not aware of anything built-in to do this. To avoid processing a whole template on a first pass though, one trick is to conditionally throw an exception (MetadataFinished below) during that pass, but not normal execution.
Clearly this would still need to compile the whole template up front, though this should come in useful at execution time.
E.g.
import org.apache.commons.io.output.NullWriter;
public class Metadata {
private Map<String, Template> byKey = new LinkedHashMap<>();
private Template currentTemplate;
/** Callback from .vm */
public void set(String key) throws MetadataFinished {
// Only do this in addTemplate()
if (currentTemplate != null) {
byKey.put(key, currentTemplate);
throw new MetadataFinished();
}
}
public void addTemplate(Template template) {
currentTemplate = template;
try {
Context context = new VelocityContext();
context.put("metadata", this);
template.merge(context, new NullWriter());
} catch (MetadataFinished ex) {
// Ignored
} finally {
currentTemplate = null;
}
}
public void execute(String key) {
Template template = byKey.get(key);
Context context = new VelocityContext();
PrintWriter pw = new PrintWriter(System.out);
template.merge(context, pw);
pw.flush();
}
// Extends Error to avoid Velocity adding a wrapping MethodInvocationException
private static class MetadataFinished extends Error {
}
public static void main(String[] args) {
Metadata metadata = new Metadata();
VelocityEngine engine = new VelocityEngine();
engine.setProperty("file.resource.loader.path", "/temp");
engine.init();
String[] fileNames = { "one.vm", "two.vm" };
for (String fileName : fileNames) {
Template template = engine.getTemplate(fileName);
metadata.addTemplate(template);
}
metadata.execute("vm1");
metadata.execute("vm2");
}
}
Then in one.vm:
$!metadata.set("vm1")##
-----------
This is VM1
-----------
The ## there is a bit ugly - it's just to stop a blank line being output. If readability is important, this can be made a bit neater with a macro though:
#metadata("vm2")
-----------
This is VM2
-----------
That macro could be defined in the global VM_global_library.vm:
#macro( metadata $key )
$!metadata.set($key)#end
Just for reference, the output is as expected:
-----------
This is VM1
-----------
-----------
This is VM2
-----------
How to add header to pdf from an html source using itext?
Currently, we have extended PdfPageEventHelper and overriden these methods. Works fine but it throws a RuntimeWorkerException when I get to 2+ pages.
#Override
void onStartPage(PdfWriter writer, Document document) {
InputStream is = new ByteArrayInputStream(header?.getBytes());
XMLWorkerHelper.getInstance().parseXHtml(writer, document, is);
}
#Override
void onEndPage(PdfWriter writer, Document document) {
InputStream is = new ByteArrayInputStream(footer?.getBytes());
XMLWorkerHelper.getInstance().parseXHtml(writer, document, is);
}
It is forbidden to add content in the onStartPage() event in general. It is forbidden to add content to the document object in the onEndPage(). You should add your header and your footer in the onEndPage() method using PdfWriter, NOT document. Also: you are wasting plenty of CPU by parsing the HTML over and over again.
Please take a look at the HtmlHeaderFooter example.
It has two snippets of HTML, one for the header, one for the footer.
public static final String HEADER =
"<table width=\"100%\" border=\"0\"><tr><td>Header</td><td align=\"right\">Some title</td></tr></table>";
public static final String FOOTER =
"<table width=\"100%\" border=\"0\"><tr><td>Footer</td><td align=\"right\">Some title</td></tr></table>";
Note that there are better ways to describe the header and footer than by using HTML, but maybe it's one of your requirements, so I won't ask you why you don't use any of the methods that is explained in the official documentation. By the way: all the information you need to solve your problem can also be found in that free ebook so you may want to download it...
We will read these HTML snippets only once in our page event and then we'll render the elements over and over again on every page:
public class HeaderFooter extends PdfPageEventHelper {
protected ElementList header;
protected ElementList footer;
public HeaderFooter() throws IOException {
header = XMLWorkerHelper.parseToElementList(HEADER, null);
footer = XMLWorkerHelper.parseToElementList(FOOTER, null);
}
#Override
public void onEndPage(PdfWriter writer, Document document) {
try {
ColumnText ct = new ColumnText(writer.getDirectContent());
ct.setSimpleColumn(new Rectangle(36, 832, 559, 810));
for (Element e : header) {
ct.addElement(e);
}
ct.go();
ct.setSimpleColumn(new Rectangle(36, 10, 559, 32));
for (Element e : footer) {
ct.addElement(e);
}
ct.go();
} catch (DocumentException de) {
throw new ExceptionConverter(de);
}
}
}
Do you see the mechanism we use to add the Element objects obtained from XML Worker? We create a ColumnText object that will write to the direct content of the writer (using the document is forbidden). We define a Rectangle and we using go() to render the elements.
The results is shown in html_header_footer.pdf.
Bruno's anwser is correct but it didn't worked for me completely as XMLWorkerHelper.parsetoElementsList was not able to parse some system fonts on the other hand XMLWorkerHelper.getInstance().parseXHtml(writer, document, is);
} was able to parse system fonts correctly so i have to go down the route of elements handler which worked a treat here's the code in C#
/// <summary>
/// returns pdf in bytes.
/// </summary>
/// <param name="contentsHtml">contents.</param>
/// <param name="headerHtml">header contents.</param>
/// <param name="footerHtml">footer contents.</param>
/// <returns></returns>
public Byte[] GetPDF(string contentsHtml, string headerHtml, string footerHtml)
{
// Create a byte array that will eventually hold our final PDF
Byte[] bytes;
// Boilerplate iTextSharp setup here
// Create a stream that we can write to, in this case a MemoryStream
using (var ms = new MemoryStream())
{
// Create an iTextSharp Document which is an abstraction of a PDF but **NOT** a PDF
using (var document = new Document(PageSize.A4, 40, 40, 120, 120))
{
// Create a writer that's bound to our PDF abstraction and our stream
using (var writer = PdfWriter.GetInstance(document, ms))
{
// Open the document for writing
document.Open();
var headerElements = new HtmlElementHandler();
var footerElements = new HtmlElementHandler();
XMLWorkerHelper.GetInstance().ParseXHtml(headerElements, new StringReader(headerHtml));
XMLWorkerHelper.GetInstance().ParseXHtml(footerElements, new StringReader(footerHtml));
writer.PageEvent = new HeaderFooter(headerElements.GetElements(), footerElements.GetElements());
// Read your html by database or file here and store it into finalHtml e.g. a string
// XMLWorker also reads from a TextReader and not directly from a string
using (var srHtml = new StringReader(contentsHtml))
{
// Parse the HTML
iTextSharp.tool.xml.XMLWorkerHelper.GetInstance().ParseXHtml(writer, document, srHtml);
}
document.Close();
}
}
// After all of the PDF "stuff" above is done and closed but **before** we
// close the MemoryStream, grab all of the active bytes from the stream
bytes = ms.ToArray();
}
return bytes;
}
}
page events and elements handler code is here
public partial class HeaderFooter : PdfPageEventHelper
{
private ElementList HeaderElements { get; set; }
private ElementList FooterElements { get; set; }
public HeaderFooter(ElementList headerElements, ElementList footerElements)
{
HeaderElements = headerElements;
FooterElements = footerElements;
}
public override void OnEndPage(PdfWriter writer, Document document)
{
base.OnEndPage(writer, document);
try
{
ColumnText headerText = new ColumnText(writer.DirectContent);
foreach (IElement e in HeaderElements)
{
headerText.AddElement(e);
}
headerText.SetSimpleColumn(document.Left, document.Top, document.Right, document.GetTop(-100), 10, Element.ALIGN_MIDDLE);
headerText.Go();
ColumnText footerText = new ColumnText(writer.DirectContent);
foreach (IElement e in FooterElements)
{
footerText.AddElement(e);
}
footerText.SetSimpleColumn(document.Left, document.GetBottom(-100), document.Right, document.GetBottom(-40), 10, Element.ALIGN_MIDDLE);
footerText.Go();
}
catch (DocumentException de)
{
throw new Exception(de.Message);
}
}
}
public class HtmlElementHandler : IElementHandler
{
public ElementList Elements { get; set; }
public HtmlElementHandler()
{
Elements = new ElementList();
}
public ElementList GetElements()
{
return Elements;
}
public void Add(IWritable w)
{
if (w is WritableElement)
{
foreach (IElement e in ((WritableElement)w).Elements())
{
Elements.Add(e);
}
}
}
}
In vaadin 7, how does one lazily determine the file name when using FileDownloader?
final Button downloadButton = new Button("Download file");
FileDownloader downloader = new FileDownloader(new StreamResource(new StreamSource() {
#Override
public InputStream getStream () {
return new ByteArrayInputStream(expesiveCalculationOfContent());
}
}, "file.snub"));
downloader.extend(downloadButton);
In this code sample, clearly the filename
is rubbish
has to be known early on.
How can one lazily determine the filename of the downloaded file?
I do not know if it is dirty but this works: extending FileDownloader.handleConnectorRequest() to call the StreamResource.setFilename() prior to calling its super's method.
{
final Button downloadButton = new Button("Download file");
final StreamResource stream = new StreamResource(
new StreamSource() {
#Override
public InputStream getStream() {
return new ByteArrayInputStream("Hola".getBytes());
}
}, "badname.txt");
FileDownloader downloader = new FileDownloader(stream) {
#Override
public boolean handleConnectorRequest(VaadinRequest request,
VaadinResponse response, String path)
throws IOException {
stream.setFilename("better-name.txt");
return super
.handleConnectorRequest(request, response, path);
}
};
downloader.extend(downloadButton);
layout.addComponent(downloadButton);
}
This is the final solution I came up with:
/**
* This specializes {#link FileDownloader} in a way, such that both the file name and content can be determined
* on-demand, i.e. when the user has clicked the component.
*/
public class OnDemandFileDownloader extends FileDownloader {
/**
* Provide both the {#link StreamSource} and the filename in an on-demand way.
*/
public interface OnDemandStreamResource extends StreamSource {
String getFilename ();
}
private static final long serialVersionUID = 1L;
private final OnDemandStreamResource onDemandStreamResource;
public OnDemandFileDownloader (OnDemandStreamResource onDemandStreamResource) {
super(new StreamResource(onDemandStreamResource, ""));
this.onDemandStreamResource = checkNotNull(onDemandStreamResource,
"The given on-demand stream resource may never be null!");
}
#Override
public boolean handleConnectorRequest (VaadinRequest request, VaadinResponse response, String path)
throws IOException {
getResource().setFilename(onDemandStreamResource.getFilename());
return super.handleConnectorRequest(request, response, path);
}
private StreamResource getResource () {
return (StreamResource) this.getResource("dl");
}
}
If one assumes that lazily determining a file name means dynamically setting the filename irrespective of what the actual file system name may be, then the code below is what I'm using.
In the code below fileName points to the local file system file with a name that we want to change upon download. A usecase for this would be when one uploaded files to tmp with filenames containing some random characters that where not present in the original upload.
File file = new File(localFile);
final FileResource fileResource = new FileResource(file);
if (!file.exists()) {
throw new IllegalStateException();
}
final StreamResource stream = new StreamResource(
new StreamSource() {
#Override
public InputStream getStream() {
return fileResource.getStream().getStream();
}
}, "newname.txt");
FileDownloader fileDownloader = new FileDownloader(stream);
fileDownloader.extend(downloadButton);
In my app I display pdf's using a ByteArrayResource.
This was working fine untill I started working with bigger files. The conversion to ByteArray keeps giving me an out of memory error.
This is how I do it at the moment:
File myPdf=new File(thePath);
FileInputStream fin = new FileInputStream(myPdf);
final byte fileContent[] = new byte[(int)myPdf.length()];
fin.read(fileContent);
fin.close();
ResourceReference rr = new ResourceReference(dePdf.getName()) {
#Override
public IResource getResource() {
return new ByteArrayResource("Application/pdf", fileContent);
}
};
if (rr.canBeRegistered()) {
getApplication().getResourceReferenceRegistry().registerResourceReference(rr);
}
return wmc;
Is there a better way to display a big file?
Try using ResourceStreamResource and FileResourceStream:
File myPdf=new File(thePath);
FileResourceStream frs = new FileResourceStream(myPdf);
ResourceStreamResource rsr = new ResourceStreamResource(frs);
rsr.setContentDisposition(ContentDisposition.ATTACHMENT);
rsr.setFileName(fileName);
//the same code for resource reference creation and registration
//...
Not entirely sure (never really used them myself) but a ContextRelativeResource may be an option. Perhaps something like:
final File myPdf=new File(thePath);
ResourceReference rr = new ResourceReference(dePdf.getName()) {
#Override
public IResource getResource() {
// You'll need to adjust the path here to be relative to your context
return new ContextRelativeResource(myPdf.getAbsolutePath());
}
};
if (rr.canBeRegistered()) {
getApplication().getResourceReferenceRegistry().registerResourceReference(rr);
}