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;
024
025import org.apache.pdfbox.multipdf.PDFMergerUtility;
026import org.apache.pdfbox.pdmodel.PDDocument;
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     */
118    public Blob merge(String inFileName) throws IOException {
119        return merge(inFileName, null, null, null);
120    }
121
122    /**
123     * Merge the PDFs. optionnaly, can set the title, subject and author of the resulting PDF.
124     * <p>
125     * <b>Notice</b> for title, author and subject: If the value is null or "", it is just ignored.
126     *
127     * @param inFileName Name of the merged result.
128     * @param inTitle Title of the resulting PDF.
129     * @param inSubject Subject of the resulting PDF.
130     * @param inAuthor Author of the resulting PDF.
131     * @return The Blob embedding the PDF resulting from the merge.
132     */
133    public Blob merge(String inFileName, String inTitle, String inSubject, String inAuthor) throws IOException {
134        Blob finalBlob;
135        switch (blobs.size()) {
136        case 0:
137            finalBlob = null;
138            break;
139        case 1:
140            finalBlob = blobs.get(0);
141            break;
142        default:
143            PDFMergerUtility ut = new PDFMergerUtility();
144            for (Blob b : blobs) {
145                ut.addSource(b.getStream());
146            }
147            File tempFile = File.createTempFile("mergepdf", ".pdf");
148            ut.setDestinationFileName(tempFile.getAbsolutePath());
149            ut.mergeDocuments();
150            if (inTitle != null || inAuthor != null || inSubject != null) {
151                PDDocument finalDoc = PDDocument.load(tempFile);
152                PDFUtils.setInfos(finalDoc, inTitle, inSubject, inAuthor);
153                finalDoc.save(tempFile);
154                finalDoc.close();
155            }
156            finalBlob = new FileBlob(tempFile);
157            Framework.trackFile(tempFile, finalBlob);
158            if (inFileName != null && !inFileName.isEmpty()) {
159                finalBlob.setFilename(inFileName);
160            } else {
161                finalBlob.setFilename(blobs.get(0).getFilename());
162            }
163            finalBlob.setMimeType("application/pdf");
164            break;
165        }
166        return finalBlob;
167    }
168
169}