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}