001/*
002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Vladimir Pasquier <vpasquier@nuxeo.com>
016 */
017package org.nuxeo.ecm.automation.core.operations.blob;
018
019import java.io.File;
020import java.io.IOException;
021
022import org.apache.pdfbox.exceptions.COSVisitorException;
023import org.apache.pdfbox.util.PDFMergerUtility;
024import org.nuxeo.ecm.automation.OperationContext;
025import org.nuxeo.ecm.automation.OperationException;
026import org.nuxeo.ecm.automation.core.Constants;
027import org.nuxeo.ecm.automation.core.annotations.Context;
028import org.nuxeo.ecm.automation.core.annotations.Operation;
029import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
030import org.nuxeo.ecm.automation.core.annotations.Param;
031import org.nuxeo.ecm.automation.core.util.BlobList;
032import org.nuxeo.ecm.core.api.Blob;
033import org.nuxeo.ecm.core.api.Blobs;
034import org.nuxeo.runtime.api.Framework;
035
036/**
037 * Given a File document holding a pdf on the file:content property and 2 pdfs on the files:files property, the
038 * following operation will provide a pdf that is the result of the merge of all the pdfs, with the content of the one
039 * in file:content property first.
040 *
041 * @since 5.8
042 */
043@Operation(id = ConcatenatePDFs.ID, category = Constants.CAT_CONVERSION, label = "Concatenate PDFs", description = "Given a File document holding a pdf on the file:content property and 2 pdfs on the files:files property, the following operation will provide a pdf that is the result of the merge of all the pdfs, with the content of the one in file:content property first.")
044public class ConcatenatePDFs {
045
046    public static final String ID = "Blob.ConcatenatePDFs";
047
048    @Context
049    protected OperationContext ctx;
050
051    @Param(name = "blob_to_append", required = false, description = "Optional blob reference in context to append in first place.")
052    protected String xpathBlobToAppend = "";
053
054    @Param(name = "filename", required = true, description = "The merge pdf result filename.")
055    protected String filename;
056
057    @OperationMethod
058    public Blob run(Blob blob) throws OperationException, IOException, COSVisitorException {
059        PDFMergerUtility ut = new PDFMergerUtility();
060        checkPdf(blob);
061        if (xpathBlobToAppend.isEmpty()) {
062            return blob;
063        }
064        handleBlobToAppend(ut);
065        ut.addSource(blob.getStream());
066        return appendPDFs(ut);
067    }
068
069    @OperationMethod
070    public Blob run(BlobList blobs) throws IOException, OperationException, COSVisitorException {
071        PDFMergerUtility ut = new PDFMergerUtility();
072        if (!xpathBlobToAppend.isEmpty()) {
073            handleBlobToAppend(ut);
074        }
075        for (Blob blob : blobs) {
076            checkPdf(blob);
077            ut.addSource(blob.getStream());
078        }
079        return appendPDFs(ut);
080    }
081
082    protected Blob appendPDFs(PDFMergerUtility ut) throws IOException, COSVisitorException {
083        File tempFile = File.createTempFile(filename, ".pdf");
084        ut.setDestinationFileName(tempFile.getAbsolutePath());
085        ut.mergeDocuments();
086        Blob fb = Blobs.createBlob(tempFile);
087        Framework.trackFile(tempFile, fb);
088        fb.setFilename(filename);
089        return fb;
090    }
091
092    /**
093     * Check if blob to append is a PDF blob.
094     */
095    protected void handleBlobToAppend(PDFMergerUtility ut) throws IOException, OperationException {
096        try {
097            Blob blobToAppend = (Blob) ctx.get(xpathBlobToAppend);
098            if (blobToAppend == null) {
099                throw new OperationException("The blob to append from variable context: '" + xpathBlobToAppend
100                        + "' is null.");
101            }
102            checkPdf(blobToAppend);
103            ut.addSource(blobToAppend.getStream());
104        } catch (ClassCastException e) {
105            throw new OperationException("The blob to append from variable context: '" + xpathBlobToAppend
106                    + "' is not a blob.", e);
107        }
108    }
109
110    /**
111     * Check if blob is a pdf.
112     */
113    protected void checkPdf(Blob blob) throws OperationException {
114        if (!"application/pdf".equals(blob.getMimeType())) {
115            throw new OperationException("Blob " + blob.getFilename() + " is not a PDF.");
116        }
117    }
118}