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