JaVa
   

Generating Binary Content

So far we've considered the generation of markup. What if we want to generate binary content? The template technologies we've looked at are unsuited to generating binary content. None of them, for example, gives us enough control over generated white space.

NoteĀ 

We may, however, be able to use an XML approach. XSL-FO (XSL Formatting Objects) is the other half of the XSL specification (other than XSLT), and defines an XML format describing layout. In the future, XSL-FO may be understood by browsers and other GUIs. Presently XSL-FO must be converted into another layout format, such as PDF, for display. For more information on XSL-FO see http://www.ibiblio.org/xml/books/bible2/chapters/ch18.html. See http://xml.apache.org/fop/ for Apache FOP, one of the few existing products that can convert XSL-FO to displayable formats. Apache FOP supports PDF and SVG, among other formats.

Sometimes we might work with the HttpServletResponse object directly. For example, we could implement our View interface to get the ServletOutputStream from the response object and output binary data. The binary data might be contained in the model provider by the controller. However, often we can use a helper classes that provide an abstraction for the binary format we wish to generate: for example, if the format is well known and publicly documented, such as image formats or PDF. Let's illustrate this approach by examining PDF generation.

Generating PDF with iText

Strictly speaking, PDF isn't a binary format. However, it isn't human-readable and it can contain ASCII-encoded binary data such as image data, so it must be approached in the same way as binary formats. PDF is publicly documented. It's commercial creator, Adobe, sells PDF tools, but shares the specification. Thus there are several open source Java libraries that can be used to generate PDF documents. I used iText version 0.93, a PDF generation library written by Bruno Lowagie, and published under the GNU GPL. It is available from http://www.lowagie.com/iText/, which also offers excellent documentation and many examples of Java code using iText. iText also provides a similar model for generating (X)HTML, XML and RTF, although its primary focus is PDF generation. PDF generation is a perfect app for the "generation library" approach that I rejected to generate HTML. This is a complex format, for which no template language can be used and which it's essential that Java app code doesn't need to handle without a high-level abstraction. Using iText to generate PDF is simple and reasonably intuitive. As with XMLC, we'll need an app-specific class to generate each PDF view. As usual, we begin by creating a View definition in /WEB-INF/classes/views.properties. As with XMLC, there are no required bean properties, although we can specify properties to customize page size and other output parameters. The complete view definition is as follows:

 showReservation.class=
 com.wrox.expertj2ee.ticket.web.pdfviews.ShowReservationView


As for XMLC, our MVC framework provides a convenient superclass - in this case, the com.interface21.web.servlet.view.pdf.AbstractPdfView abstract class - using the Template Method pattern to simplify app-specific PDF generation classes. Subclasses must implement the following protected abstract method to write model data to the iText PDF document passed as the second parameter. The request and response objects won't normally be used, but we include them in case the view needs locale information or to write a cookie:

 protected abstract void buildPdfDocument(Map model,
 Document pdfDoc,
 HttpServletRequest request,
 HttpServletResponse response)
 throws DocumentException;


NoteĀ 

Please see Appendix A for information on how to install iText and on the implementation of the com.interface21.web.servlet.view.pdf.AbstractPdfView framework superclass.

The app-specific PDF view to display a reservation begins by subclassing AbstractPdfView:

 public class ShowReservationView extends AbstractPdfView {


Next we define the data and font constants we'll use as we generate output. Ideally, this content should be held in a ResourceBundle, allowing the view implementation to use the appropriate ResourceBundle to the request locale. Note that as we can't use a template language, our Java code will be forced at least to manipulate variables containing output strings:

 private static final String MESSAGE1 =
 "{0} tickets have been reserved for you for " +
 "{1} minutes to give you time to " +
 "complete your purchase. The seat numbers are: ";
 private static final String COSTING =
 "The total cost of these tickets will be {0}. " +
 "This includes a tutorialing fee of {1}."
 private static final String ADJACENT_WARNING =
 "Please note that due of lack of availability, some " +
 " of the seats offered are not adjacent";
 private static final Font HEADLINE_FONT =
 new Font(Font.TIMES_NEW_ROMAN, 15, Font.BOLD, Color.red);
 private static final Font HEADING_FONT =
 new Font(Font.HELVETICA, 13, Font.ITALIC, Color.black);
 private static final Font TEXT_FONT =
 new Font (Font.HELVETICA, 11, Font.BOLD, Color.black);
 private static final Font WARNING_FONT =
 new Font(Font.HELVETICA, 12, Font.BOLD, Color.black);


We must implement the buildPdfDocument() protected abstract method to generate content:

 protected void buildPdfDocument (Map model, Document pdfDoc,
 HttpServletRequest request, HttpServletResponse response)
 throws DocumentException {


As with the XMLC view, we begin by extracting the required model objects from the map. As their controller must have made these available, we don't need to check that they're non-null. We can simply allow a NullPointerException, as this amounts to an assertion failure (in Java 1.4 each controller method could conclude with an assertion that the necessary model keys were non-null):

 Reservation reservation = (Reservation)
 model.get(TicketController.RESERVATION_KEY);
 Performance performance = (Performance)
 model.get(TicketController.PERFORMANCE_KEY);
 PriceBand priceBand = (PriceBand)
 model.get(TicketController.PRICE_BAND_KEY);


Next, we use the same code we've seen before to format dates and currency amounts according to the request locale:

 SimpleDateFormat df = (SimpleDateFormat)
 DateFormat.getDateInstance(DateFormat.SHORT, request.getLocale());
 df.applyPattern("EEEE MMMM dd, yyyy");
 String formattedDate = df.format(performance.getWhen());
 df.applyPattern("h:mm a");
 String formattedTime = df.format(performance.getWhen());
 NumberFormat cf = NumberFormat.getCurrencyInstance();
 String formattedTotalPrice = cf.format(reservation.getTotalPrice());
 String formattedBookingFee =
 cf.format(reservation.getQuoteRequest().getBookingFee());


Now we can begin to generate dynamic content. This takes the form of adding new objects representing document content to the Document object:

 String title = "Reservation for " + performance.getShow().getName();
 pdfDoc.add(new Paragraph(title, HEADLINE_FONT));
 String when = formattedDate + " at " + formattedTime;
 pdfDoc.add(new Paragraph(when, HEADING_FONT));
 pdfDoc.add(new Paragraph());
 String note = MessageFormat.format(MESSAGE1, new String[] { "" +
 reservation.getSeats().length,
 "" + reservation.getMinutesReservationWillBeValid()});
 pdfDoc.add(new Paragraph(note, TEXT_FONT));


In this model, iteration is handled by ordinary Java code, making it straightforward:

 List list = new List (false, 20);
 list.setListSymbol(new Chunk("\u2022",
 new Font(Font.HELVETICA, 20, Font.BOLD)));
 for (int i = 0; i < reservation.getSeats().length; i++) {
 list.add(new ListItem(reservation.getSeats()[i].getName()));
 }
 pdfDoc.add(list);
 pdfDoc.add(new Paragraph());
 note = MessageFormat.format(COSTING, new String[] {
 formattedTotalPrice, formattedBookingFee });
 pdfDoc.add(new Paragraph(note, TEXT_FONT));
 pdfDoc.add(new Paragraph());


Likewise, the conditional inclusion of the warning about seats not being adjacent involves a simple Java if:

 if (!reservation.getSeatsAreAdjacent()) {
 pdfDoc.add(new Paragraph(ADJACENT_WARNING, WARNING_FONT));
 }
 }
}


This is pretty simple, given the complexity of PDF, but it shows why such an approach isn't such a good idea for generating HTML. Having Java code manipulate text content is inelegant, and it's hard to get a feel for what the completed document will look like. A template-based approach is much more natural if it's an option. The PDF document we've just created will look like this:

Java Click To expand

iText supports adding images to PDF documents, so we could display the seating plan graphic as well. Please refer to iText documentation for further information.

JaVa
   
Comments