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}