001/*
002 * (C) Copyright 2016 Nuxeo SA (http://nuxeo.com/) and others.
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 *     http://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 * Contributors:
017 *     Thibaud Arguillere
018 *     Miguel Nixo
019 */
020package org.nuxeo.ecm.platform.pdf;
021
022import java.io.File;
023import java.io.IOException;
024import org.apache.pdfbox.exceptions.COSVisitorException;
025import org.apache.pdfbox.pdmodel.PDDocument;
026import org.apache.pdfbox.util.PDFMergerUtility;
027import org.nuxeo.ecm.automation.core.util.BlobList;
028import org.nuxeo.ecm.core.api.Blob;
029import org.nuxeo.ecm.core.api.CoreSession;
030import org.nuxeo.ecm.core.api.DocumentModel;
031import org.nuxeo.ecm.core.api.DocumentModelList;
032import org.nuxeo.ecm.core.api.IdRef;
033import org.nuxeo.ecm.core.api.impl.blob.FileBlob;
034import org.nuxeo.runtime.api.Framework;
035
036/**
037 * Merge several PDFs in one.
038 * <p>
039 * Basically, the caller adds blobs and then <code>merge()</code>. The PDFs are merged in the order they were added.
040 * <p>
041 * The class accepts misc parameters: Single <code>Blob</code>, <code>BlobList</code>, single
042 * <code>DocumentModel</code>, <code>DocumentModelList</code> or a list of IDs of <code>DocumentModel</code>
043 * <p>
044 * <i>Notice</i>: These are nuxeo <code>Blob</code>, <code>BlobList</code>, etc.
045 * <p>
046 * When a <code>DocumentModel</code> is used, the code may expect an xpath to extract the blob from the document. When
047 * the xpath parameter is not used (<code>null</code> or ""), the default <code>file:content</code> xpath is used.
048 * <p>
049 * To let the caller be generic, it's ok to pass a null blob, so it is just ignored.
050 *
051 * @since 8.10
052 */
053public class PDFMerge {
054
055    private BlobList blobs = new BlobList();
056
057    public PDFMerge() {
058
059    }
060
061    public PDFMerge(Blob inBlob) {
062        addBlob(inBlob);
063    }
064
065    public PDFMerge(BlobList inBlobs) {
066        addBlobs(inBlobs);
067    }
068
069    public PDFMerge(DocumentModel inDoc, String inXPath) {
070        addBlob(inDoc, inXPath);
071    }
072
073    public PDFMerge(DocumentModelList inDocs, String inXPath) {
074        addBlobs(inDocs, inXPath);
075    }
076
077    // The original usecase actually :-)
078    public PDFMerge(String[] inDocIDs, String inXPath, CoreSession inSession) {
079        addBlobs(inDocIDs, inXPath, inSession);
080    }
081
082    public void addBlob(Blob inBlob) {
083        if (inBlob != null) {
084            blobs.add(inBlob);
085        }
086    }
087
088    public void addBlobs(BlobList inBlobs) {
089        inBlobs.forEach(this::addBlob);
090    }
091
092    public void addBlob(DocumentModel inDoc, String inXPath) {
093        if (inXPath == null || inXPath.isEmpty()) {
094            inXPath = "file:content";
095        }
096        addBlob((Blob) inDoc.getPropertyValue(inXPath));
097    }
098
099    public void addBlobs(DocumentModelList inDocs, String inXPath) {
100        for (DocumentModel doc : inDocs) {
101            addBlob(doc, inXPath);
102        }
103    }
104
105    public void addBlobs(String[] inDocIDs, String inXPath, CoreSession inSession) {
106        for (String id : inDocIDs) {
107            DocumentModel doc = inSession.getDocument(new IdRef(id));
108            addBlob(doc, inXPath);
109        }
110    }
111
112    /**
113     * Merge the PDFs.
114     *
115     * @param inFileName Name of the merged result.
116     * @return The Blob embedding the PDF resulting form the merge.
117     * @throws COSVisitorException
118     * @throws IOException
119     */
120    public Blob merge(String inFileName) throws COSVisitorException, IOException {
121        return merge(inFileName, null, null, null);
122    }
123
124    /**
125     * Merge the PDFs. optionnaly, can set the title, subject and author of the resulting PDF.
126     * <p>
127     * <b>Notice</b> for title, author and subject: If the value is null or "", it is just ignored.
128     *
129     * @param inFileName Name of the merged result.
130     * @param inTitle Title of the resulting PDF.
131     * @param inSubject Subject of the resulting PDF.
132     * @param inAuthor Author of the resulting PDF.
133     * @return The Blob embedding the PDF resulting from the merge.
134     * @throws IOException
135     * @throws COSVisitorException
136     */
137    public Blob merge(String inFileName, String inTitle, String inSubject, String inAuthor) throws IOException,
138        COSVisitorException {
139        Blob finalBlob;
140        switch (blobs.size()) {
141        case 0:
142            finalBlob = null;
143            break;
144        case 1:
145            finalBlob = blobs.get(0);
146            break;
147        default:
148            PDFMergerUtility ut = new PDFMergerUtility();
149            for (Blob b : blobs) {
150                ut.addSource(b.getStream());
151            }
152            File tempFile = File.createTempFile("mergepdf", ".pdf");
153            ut.setDestinationFileName(tempFile.getAbsolutePath());
154            ut.mergeDocuments();
155            if (inTitle != null || inAuthor != null || inSubject != null) {
156                PDDocument finalDoc = PDDocument.load(tempFile);
157                PDFUtils.setInfos(finalDoc, inTitle, inSubject, inAuthor);
158                finalDoc.save(tempFile);
159                finalDoc.close();
160            }
161            finalBlob = new FileBlob(tempFile);
162            Framework.trackFile(tempFile, finalBlob);
163            if (inFileName != null && !inFileName.isEmpty()) {
164                finalBlob.setFilename(inFileName);
165            } else {
166                finalBlob.setFilename(blobs.get(0).getFilename());
167            }
168            finalBlob.setMimeType("application/pdf");
169            break;
170        }
171        return finalBlob;
172    }
173
174}