001/* Copyright (C) 2014 konik.io
002 *
003 * This file is part of the Konik library.
004 *
005 * The Konik library is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * The Konik library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with the Konik library. If not, see <http://www.gnu.org/licenses/>.
017 */
018package io.konik;
019
020import static java.util.logging.Level.WARNING;
021import io.konik.harness.FileAppender;
022import io.konik.harness.FileExtractor;
023import io.konik.harness.appender.DefaultAppendParameter;
024import io.konik.harness.exception.InvoiceAppendError;
025import io.konik.zugferd.Invoice;
026
027import java.io.ByteArrayInputStream;
028import java.io.File;
029import java.io.FileInputStream;
030import java.io.FileNotFoundException;
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.OutputStream;
034import java.io.PipedInputStream;
035import java.io.PipedOutputStream;
036import java.util.ServiceLoader;
037import java.util.logging.Logger;
038
039import javax.inject.Inject;
040import javax.inject.Named;
041import javax.inject.Singleton;
042
043/**
044 * Transforms, appends or extracts invoices to PDFs.
045 */
046@Named
047@Singleton
048public class PdfHandler {
049
050   private static final Logger LOG = Logger.getLogger(PdfHandler.class.getName());
051   
052   private final FileAppender fileAppender;
053   private final FileExtractor fileExtractor;
054   private final InvoiceTransformer transformer;
055
056   /**
057    * Instantiates a new PDF handler.
058    *
059    * @param fileAppender the file appender
060    * @param fileExtractor the file extractor
061    * @param transformer the invoice model transformer
062    */
063   @Inject
064   public PdfHandler(FileAppender fileAppender, FileExtractor fileExtractor, InvoiceTransformer transformer) {
065      this.fileAppender = fileAppender;
066      this.fileExtractor = fileExtractor;
067      this.transformer = transformer;
068   }
069
070   /**
071    * Instantiates a default invoice transformer using the Service loader to inject an PDF carriage that should be on the classpath.
072    * 
073    * If error is thrown check you have a Konik PDF Carriage on the classpath.
074    */
075   public PdfHandler() {
076      this.fileAppender = ServiceLoader.load(FileAppender.class).iterator().next();
077      this.fileExtractor = ServiceLoader.load(FileExtractor.class).iterator().next();
078      this.transformer = new InvoiceTransformer();
079   }
080
081   /**
082    * Append an invoice to a PDF.  
083    * 
084    * The resulting Pdf Output is based on the input PDF, but might be converted to PDF/A-3 when needed.
085    * 
086    * @param invoice that should be attached to the pdf.
087    * @param inputPdf to witch we are going to append the invoice to, input will not be modified
088    * @param resultingPdf is the modified copy of the input PDF with the invoice.
089    */
090   public void appendInvoice(final Invoice invoice, final InputStream inputPdf, final OutputStream resultingPdf) {
091      try {
092         append(invoice, inputPdf, resultingPdf);
093      } catch (IOException e) {
094         throw new InvoiceAppendError("Not able to append invoice to PDF", e);
095      }
096   }
097
098   private void append(final Invoice invoice, final InputStream inputPdf, final OutputStream resultingPdf) throws IOException {
099      PipedOutputStream pipedOutputStream = new PipedOutputStream();
100      PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream, 65536);
101      try {
102         String version = invoice.getContext().getGuideline().getVersion().toString();
103         String confomanceLevel = invoice.getContext().getGuideline().getConformanceLevel().name();
104         transformer.fromModelAsync(invoice, pipedOutputStream);
105         DefaultAppendParameter parameter = new DefaultAppendParameter(inputPdf, pipedInputStream, resultingPdf,
106               version, confomanceLevel);
107         fileAppender.append(parameter);
108      } finally {
109         pipedInputStream.close();
110      }
111   }
112
113   /**
114    * Extract invoice from given pdf file
115    *
116    * @param pdfFile the pdf file containing the ZUGFeRD XML File 
117    * @return the transformed ZUGFeRD invoice
118    * @throws FileNotFoundException if the pdf is not found 
119    */
120   public Invoice extractInvoice(File pdfFile) throws FileNotFoundException {
121      byte[] xmlInvoice = fileExtractor.extract(new FileInputStream(pdfFile));
122      return transformer.toModel(new ByteArrayInputStream(xmlInvoice));
123   }
124
125   /**
126    * Extract invoice from given pdf stream
127    *
128    * @param pdfInputStream the pdf input stream
129    * @return the transformed ZUGFeRD invoice
130    */
131   public Invoice extractInvoice(InputStream pdfInputStream) {
132      InputStream invoiceInputStream = fileExtractor.extractToStream(pdfInputStream);
133      Invoice invoiceModel = transformer.toModel(invoiceInputStream);
134      closeQuietly(invoiceInputStream);
135      return invoiceModel;
136      
137   }
138
139   private static void closeQuietly(InputStream stream) {
140      try {
141         if (stream != null) {
142            stream.close();
143         }
144      }catch(IOException e) {
145         LOG.log(WARNING, "Could not close InputStream. This can be a memory leak as the PDF might still be open.", e);
146      }
147   }
148}