001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.view.document; 018 019import java.io.ByteArrayOutputStream; 020import java.io.OutputStream; 021import java.util.Map; 022import javax.servlet.http.HttpServletRequest; 023import javax.servlet.http.HttpServletResponse; 024 025import com.lowagie.text.Document; 026import com.lowagie.text.DocumentException; 027import com.lowagie.text.PageSize; 028import com.lowagie.text.pdf.PdfWriter; 029 030import org.springframework.web.servlet.view.AbstractView; 031 032/** 033 * Abstract superclass for PDF views. Application-specific view classes 034 * will extend this class. The view will be held in the subclass itself, 035 * not in a template. 036 * 037 * <p>This view implementation uses Bruno Lowagie's 038 * <a href="https://www.lowagie.com/iText">iText</a> API. 039 * Known to work with the original iText 2.1.7 as well as its fork 040 * <a href="https://github.com/LibrePDF/OpenPDF">OpenPDF</a>. 041 * <b>We strongly recommend OpenPDF since it is actively maintained 042 * and fixes an important vulnerability for untrusted PDF content.</b> 043 * 044 * <p>Note: Internet Explorer requires a ".pdf" extension, as it doesn't 045 * always respect the declared content type. 046 * 047 * @author Rod Johnson 048 * @author Juergen Hoeller 049 * @author Jean-Pierre Pawlak 050 * @see AbstractPdfStamperView 051 */ 052public abstract class AbstractPdfView extends AbstractView { 053 054 /** 055 * This constructor sets the appropriate content type "application/pdf". 056 * Note that IE won't take much notice of this, but there's not a lot we 057 * can do about this. Generated documents should have a ".pdf" extension. 058 */ 059 public AbstractPdfView() { 060 setContentType("application/pdf"); 061 } 062 063 064 @Override 065 protected boolean generatesDownloadContent() { 066 return true; 067 } 068 069 @Override 070 protected final void renderMergedOutputModel( 071 Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { 072 073 // IE workaround: write into byte array first. 074 ByteArrayOutputStream baos = createTemporaryOutputStream(); 075 076 // Apply preferences and build metadata. 077 Document document = newDocument(); 078 PdfWriter writer = newWriter(document, baos); 079 prepareWriter(model, writer, request); 080 buildPdfMetadata(model, document, request); 081 082 // Build PDF document. 083 document.open(); 084 buildPdfDocument(model, document, writer, request, response); 085 document.close(); 086 087 // Flush to HTTP response. 088 writeToResponse(response, baos); 089 } 090 091 /** 092 * Create a new document to hold the PDF contents. 093 * <p>By default returns an A4 document, but the subclass can specify any 094 * Document, possibly parameterized via bean properties defined on the View. 095 * @return the newly created iText Document instance 096 * @see com.lowagie.text.Document#Document(com.lowagie.text.Rectangle) 097 */ 098 protected Document newDocument() { 099 return new Document(PageSize.A4); 100 } 101 102 /** 103 * Create a new PdfWriter for the given iText Document. 104 * @param document the iText Document to create a writer for 105 * @param os the OutputStream to write to 106 * @return the PdfWriter instance to use 107 * @throws DocumentException if thrown during writer creation 108 */ 109 protected PdfWriter newWriter(Document document, OutputStream os) throws DocumentException { 110 return PdfWriter.getInstance(document, os); 111 } 112 113 /** 114 * Prepare the given PdfWriter. Called before building the PDF document, 115 * that is, before the call to {@code Document.open()}. 116 * <p>Useful for registering a page event listener, for example. 117 * The default implementation sets the viewer preferences as returned 118 * by this class's {@code getViewerPreferences()} method. 119 * @param model the model, in case meta information must be populated from it 120 * @param writer the PdfWriter to prepare 121 * @param request in case we need locale etc. Shouldn't look at attributes. 122 * @throws DocumentException if thrown during writer preparation 123 * @see com.lowagie.text.Document#open() 124 * @see com.lowagie.text.pdf.PdfWriter#setPageEvent 125 * @see com.lowagie.text.pdf.PdfWriter#setViewerPreferences 126 * @see #getViewerPreferences() 127 */ 128 protected void prepareWriter(Map<String, Object> model, PdfWriter writer, HttpServletRequest request) 129 throws DocumentException { 130 131 writer.setViewerPreferences(getViewerPreferences()); 132 } 133 134 /** 135 * Return the viewer preferences for the PDF file. 136 * <p>By default returns {@code AllowPrinting} and 137 * {@code PageLayoutSinglePage}, but can be subclassed. 138 * The subclass can either have fixed preferences or retrieve 139 * them from bean properties defined on the View. 140 * @return an int containing the bits information against PdfWriter definitions 141 * @see com.lowagie.text.pdf.PdfWriter#AllowPrinting 142 * @see com.lowagie.text.pdf.PdfWriter#PageLayoutSinglePage 143 */ 144 protected int getViewerPreferences() { 145 return PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage; 146 } 147 148 /** 149 * Populate the iText Document's meta fields (author, title, etc.). 150 * <br>Default is an empty implementation. Subclasses may override this method 151 * to add meta fields such as title, subject, author, creator, keywords, etc. 152 * This method is called after assigning a PdfWriter to the Document and 153 * before calling {@code document.open()}. 154 * @param model the model, in case meta information must be populated from it 155 * @param document the iText document being populated 156 * @param request in case we need locale etc. Shouldn't look at attributes. 157 * @see com.lowagie.text.Document#addTitle 158 * @see com.lowagie.text.Document#addSubject 159 * @see com.lowagie.text.Document#addKeywords 160 * @see com.lowagie.text.Document#addAuthor 161 * @see com.lowagie.text.Document#addCreator 162 * @see com.lowagie.text.Document#addProducer 163 * @see com.lowagie.text.Document#addCreationDate 164 * @see com.lowagie.text.Document#addHeader 165 */ 166 protected void buildPdfMetadata(Map<String, Object> model, Document document, HttpServletRequest request) { 167 } 168 169 /** 170 * Subclasses must implement this method to build an iText PDF document, 171 * given the model. Called between {@code Document.open()} and 172 * {@code Document.close()} calls. 173 * <p>Note that the passed-in HTTP response is just supposed to be used 174 * for setting cookies or other HTTP headers. The built PDF document itself 175 * will automatically get written to the response after this method returns. 176 * @param model the model Map 177 * @param document the iText Document to add elements to 178 * @param writer the PdfWriter to use 179 * @param request in case we need locale etc. Shouldn't look at attributes. 180 * @param response in case we need to set cookies. Shouldn't write to it. 181 * @throws Exception any exception that occurred during document building 182 * @see com.lowagie.text.Document#open() 183 * @see com.lowagie.text.Document#close() 184 */ 185 protected abstract void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, 186 HttpServletRequest request, HttpServletResponse response) throws Exception; 187 188}