Nous allons voir dans ce billet comment générer un flux PDF depuis une application Wicket.
Pour l'exemple, nous utiliserons la librairie iText et notre application ZenContact.
Nous allons tout d'abord créer un nouveau lien Imprimer dans la page ViewContact.
Dans le template Html ViewContact.html, nous ajoutons la déclaration du lien Imprimer.
<a wicket:id="print" href="#">Imprimer</a>
Puis dans la classe ViewContact.java nous ajoutons la déclaration du lien.
add(new Link("print", getModel()) { public void onClick() { //insertion du code de génération PDF d'un contact } });
Le framework Wicket propose un mécanisme très simple pour intercepter et personnaliser le flux de retour.
Dans le cycle de vie d'une requête, c'est l'objet de type RequestTarget qui est responsable du traitement final de la réponse HTTP.
La réponse peut avoir en effet plusieurs cibles :
- Soit une page
- Soit une portion de page, c'est le cas notamment des requêtes Ajax (on utilise alors un AjaxRequestTarget)
- Soit une ressource Web (cc, js, image, ...)
Dans le cas qui nous intéresse, la cible de la réponse est un flux PDF.
Wicket propose la classe wicket.request.target.resource.ResourceStreamRequestTarget qui est un wrapper pour renvoyer le contenu d'un java.io.File.
Pour imprimer au format PDF un contact, il nous faut tout d'abord générer avec iText un document PDF au format java.io.File.
Puis tout simplement remplacer la RequestTarget standard par une ResourceStreamRequestTarget contenant le flux PDF.
Ce qui se traduit par les lignes de code suivantes :
add(new Link("print", getModel()) { public void onClick() { Contact contact = (Contact) getModelObject(); File file = PdfFactory.createFile(new ContactPdfBuilder(contact)); ResourceStreamRequestTarget target = new ResourceStreamRequestTarget(new FileResourceStream(file)); target.setFileName("contact-" + contact.getLastName() + ".pdf"); getRequestCycle().getResponse().setContentType("application/pdf"); getRequestCycle().setRequestTarget(target); } });
- Ligne 3 : récupération du modèle Wicket associé à la page, dans notre cas il s'agit d'un POJO Contact.
- Ligne 4 : création du document PDF au format java.io.File (nous détaillons le code iText à la fin du billet).
- Ligne 5 : création d'une instance de ResourceStreamRequestTarget contenant le flux PDF.
- Ligne 6 : appel à la méthode setFileName pour nommer le fichier dans le flux de retour.
- Ligne 7 : modification du Content-Type du flux de retour.
- Ligne 8 : remplacement de la RequestTarget standard par notre ResourceStreamRequestTarget.
Il est bien évidemment possible de renvoyer tout type de flux (excel, word, etc..) en utilisant un ResourceStreamRequestTarget, il suffit d'adapter le Content-Type de la réponse.
Il ne nous reste plus qu'à détailler la création du document iText, ce qui n'est pas spécifique à Wicket.
Dans cet exemple nous utiliserons le Design Pattern Template Method pour intégrer la génération de document iText.
Toutes les parties génériques de la génération d'un document iText (création, ouverture et fermeture du document) sont traitées par une méthode d'une classe abstraite (le patron de méthode, dans notre cas la classe AbstractItextDocumentBuilder).
La partie spécifique de la génération est prise en charge par une sous-classe dédiée (dans notre cas la classe ContactPdfBuilder).

package com.zenika.training.wicket.zencontact.utils.pdf; import java.io.File; public class PdfFactory { public static File createFile(PdfDocument document) { return document.getPdFFile(); } }
public abstract class AbstractItextDocumentBuilder implements PdfDocument { private Document document; private File pdfFile; public File getPdFFile() { if (pdfFile == null) createDocument(); return pdfFile; } private void createDocument() { document = new Document(); try { pdfFile = new File("file.pdf"); PdfWriter.getInstance(document, new FileOutputStream(pdfFile)); document.open(); buildDocument(document); } catch (Exception e) { e.printStackTrace(); } finally { document.close(); } } protected abstract void buildDocument(Document document) throws DocumentException, MalformedURLException, IOException; }
- Ligne 13 : séquence générique de la génération d'un document iText.
- Ligne 19 : appel à la méthode spécifique qui se trouve implémentée par les sous-classes.
public class ContactPdfBuilder extends AbstractItextDocumentBuilder { private Contact contact; public ContactPdfBuilder(Contact contact) { this.contact = contact; } protected void buildDocument(Document document) throws DocumentException { PdfPTable header = new PdfPTable(2); header.setWidthPercentage(100); PdfPCell cell = new PdfPCell(new Paragraph("ZenContact")); cell.setBorder(Rectangle.NO_BORDER); cell.setPadding(10); header.addCell(cell); SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); cell = new PdfPCell(new Paragraph(sdf.format(new Date()))); cell.setHorizontalAlignment(Element.ALIGN_RIGHT); cell.setVerticalAlignment(Element.ALIGN_BOTTOM); cell.setBorder(Rectangle.NO_BORDER); cell.setPadding(10); header.addCell(cell); document.add(header); document.add(new Paragraph("")); document.add(new LineSeparator()); document.add(new Paragraph("")); document.add(new Paragraph("Name : " + contact.getFirstName() + " " + contact.getLastName())); document.add(new Paragraph("Email : " + contact.getEmail())); document.add(new Paragraph("Notes : " + contact.getNotes())); } }

