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.IOException;
021import java.util.Map;
022
023import javax.servlet.http.HttpServletRequest;
024import javax.servlet.http.HttpServletResponse;
025
026import com.lowagie.text.pdf.PdfReader;
027import com.lowagie.text.pdf.PdfStamper;
028
029import org.springframework.util.Assert;
030import org.springframework.web.servlet.view.AbstractUrlBasedView;
031
032/**
033 * Abstract superclass for PDF views that operate on an existing
034 * document with an AcroForm. Application-specific view classes
035 * will extend this class to merge the PDF form with model data.
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>Thanks to Bryant Larsen for the suggestion and the original prototype!
045 *
046 * @author Juergen Hoeller
047 * @since 2.5.4
048 * @see AbstractPdfView
049 */
050public abstract class AbstractPdfStamperView extends AbstractUrlBasedView {
051
052        public AbstractPdfStamperView(){
053                setContentType("application/pdf");
054        }
055
056
057        @Override
058        protected boolean generatesDownloadContent() {
059                return true;
060        }
061
062        @Override
063        protected final void renderMergedOutputModel(
064                        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
065
066                // IE workaround: write into byte array first.
067                ByteArrayOutputStream baos = createTemporaryOutputStream();
068
069                PdfReader reader = readPdfResource();
070                PdfStamper stamper = new PdfStamper(reader, baos);
071                mergePdfDocument(model, stamper, request, response);
072                stamper.close();
073
074                // Flush to HTTP response.
075                writeToResponse(response, baos);
076        }
077
078        /**
079         * Read the raw PDF resource into an iText PdfReader.
080         * <p>The default implementation resolve the specified "url" property
081         * as ApplicationContext resource.
082         * @return the PdfReader instance
083         * @throws IOException if resource access failed
084         * @see #setUrl
085         */
086        protected PdfReader readPdfResource() throws IOException {
087                String url = getUrl();
088                Assert.state(url != null, "'url' not set");
089                return new PdfReader(obtainApplicationContext().getResource(url).getInputStream());
090        }
091
092        /**
093         * Subclasses must implement this method to merge the PDF form
094         * with the given model data.
095         * <p>This is where you are able to set values on the AcroForm.
096         * An example of what can be done at this level is:
097         * <pre class="code">
098         * // get the form from the document
099         * AcroFields form = stamper.getAcroFields();
100         *
101         * // set some values on the form
102         * form.setField("field1", "value1");
103         * form.setField("field2", "Vvlue2");
104         *
105         * // set the disposition and filename
106         * response.setHeader("Content-disposition", "attachment; FILENAME=someName.pdf");</pre>
107         * <p>Note that the passed-in HTTP response is just supposed to be used
108         * for setting cookies or other HTTP headers. The built PDF document itself
109         * will automatically get written to the response after this method returns.
110         * @param model the model Map
111         * @param stamper the PdfStamper instance that will contain the AcroFields.
112         * You may also customize this PdfStamper instance according to your needs,
113         * e.g. setting the "formFlattening" property.
114         * @param request in case we need locale etc. Shouldn't look at attributes.
115         * @param response in case we need to set cookies. Shouldn't write to it.
116         * @throws Exception any exception that occurred during document building
117     */
118        protected abstract void mergePdfDocument(Map<String, Object> model, PdfStamper stamper,
119                        HttpServletRequest request, HttpServletResponse response) throws Exception;
120
121}