PDFs mit Attachments und xhtmlrenderer (FlyingSaucer) erstellen

Nun, genau wie mein Arbeitsfeld ändern sich auch die Themen dieses Blogs ständig. Daher schreibe ich heute mal etwas aus der bunten Welt von Java. Um etwas genauer zu sein: Es geht um die serverseitige Erstellung von PDF-Dateien mithilfe von xhtmlrenderer, aka The Flying Saucer Project. Und zwar natürlich nicht nur normale PDFs, nein, das wäre ja geradezu trivial. Ich möchte an die PDFs noch Dateianhänge, sogenannte Attachments, fügen.
Die offizielle iText-Seite – iText ist die Komponente zur Erstellung von PDFs in xhtmlrenderer. Der xhtmlrenderer selbst ist eigentlich mehr ein Browser und dient dazu (x)html als Quelle für die iText-Konvertierung zu verwenden. – beschreibt auf dem einzigen verfügbaren Tutorial die Erstellung von PDFs mit Attachments etwa so:

Document document = new Document( PageSize.A4);
PdfWriter writer
  = PdfWriter.getInstance( document, new FileOutputStream( new File( "myDocument.pdf" ) ) );
writer.setViewerPreferences( PdfWriter.PageModeUseAttachments );
document.open();
PdfFileSpecification fs = 
   PdfFileSpecification.fileEmbedded( writer, "somePhysicalFilename.ext", "someDisplayFilename.ext", null );
writer.addFileAttachment( fs );
document.add( ... );
document.close();

Das Problem bei der Verwendung von xhtmlrenderer ist, dass man kein Document und damit auch kein PdfWriter Objekt zur verfügung hat. Dort erstellt man ein PDF nämlich etwa so:

ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
DocumentBuilder dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = dBuilder.parse( new InputSource( new StringReader( "<html> ... </html>" ) ) );
File basePath = new File( "/somewhere/" );

ITextRenderer renderer = new ITextRenderer();
renderer.setDocument( doc, basePath.toURI().toURL().toString() );
renderer.layout();
renderer.createPDF( pdfStream );

byte[] pdfByte = pdfStream.toByteArray();
pdfStream.close();

File uploadDirectory = new File( "/someUploadDirectory" );
uploadDirectory.mkdirs();

File pdfFile = new File( uploadDirectory, "myDocument.pdf" );
FileOutputStream fos = new FileOutputStream( pdfFile );
fos.write( pdfByte );
fos.close();

Zwar gibt es die Möglichkeit einen PdfWriter zu erlangen, indem man renderer.getWriter() ausführt, jedoch hat dieses Vorgehen bei mir immer nur beschädigte Dateien erzeugt.
Die Lösung habe ich in zwei Dingen gefunden:

  1. Im Zwischenspeichern der Anhanglosen Datei und …
  2. … in der PdfStamper Klasse.

Nehmen wir das vorangegangene Listing als Basis sieht das Hinzufügen von Attachments folgendermaßen aus:

File pdfFileWithAttachments = new File( "myDocumentWithAttachments.pdf" );
FileOutputStream fosFinal = new FileOutputStream( pdfFileWithAttachments );
PdfReader pr = new PdfReader( pdfFile.toURI().toURL() );
PdfStamper ps = new PdfStamper( pr, fosFinal );

ps.setViewerPreferences( PdfWriter.PageModeUseAttachments );
File attachment = new File( "somePhysicalFilename.ext" );
PdfFileSpecification fa = PdfFileSpecification.fileEmbedded(
						ps.getWriter(),
						attachment.getAbsolutePath(),
						attachment.getName(),
						null );
ps.addFileAttachment( attachment.getName(), fa );
ps.close();
fosFinal.close();

Ja und das war’s auch eigentlich schon.