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 com.google.common.collect.Lists;
021import io.konik.csv.pdf.FileAppenderPriorityComparator;
022import io.konik.harness.FileAppender;
023import io.konik.harness.FileExtractor;
024import io.konik.harness.appender.DefaultAppendParameter;
025import io.konik.harness.exception.InvoiceAppendError;
026import io.konik.zugferd.Invoice;
027
028import javax.inject.Inject;
029import javax.inject.Named;
030import javax.inject.Singleton;
031import java.io.*;
032import java.util.Collections;
033import java.util.Iterator;
034import java.util.List;
035import java.util.ServiceLoader;
036import java.util.logging.Logger;
037
038import static io.konik.csv.pdf.FileAppenderPriorityComparator.Order;
039import static java.util.logging.Level.WARNING;
040
041/**
042 * Transforms, appends or extracts invoices to PDFs.
043 */
044@Named
045@Singleton
046public class PdfHandler {
047
048   private static final Logger LOG = Logger.getLogger(PdfHandler.class.getName());
049   
050   private final FileAppender fileAppender;
051   private final FileExtractor fileExtractor;
052   private final InvoiceTransformer transformer;
053
054   /**
055    * Instantiates a new PDF handler.
056    *
057    * @param fileAppender the file appender
058    * @param fileExtractor the file extractor
059    * @param transformer the invoice model transformer
060    */
061   @Inject
062   public PdfHandler(FileAppender fileAppender, FileExtractor fileExtractor, InvoiceTransformer transformer) {
063      this.fileAppender = fileAppender;
064      this.fileExtractor = fileExtractor;
065      this.transformer = transformer;
066   }
067
068   /**
069    * Instantiates a default invoice transformer using the Service loader to inject an PDF carriage that should be on the classpath.
070    * 
071    * If error is thrown check you have a Konik PDF Carriage on the classpath.
072    */
073   public PdfHandler() {
074      Iterator iterator = ServiceLoader.load(FileAppender.class).iterator();
075      List<FileAppender> appenders = Lists.<FileAppender>newArrayList(iterator);
076
077      if (appenders.isEmpty()) {
078         throw new IllegalStateException("FileAppender implementation not found in the classpath!");
079      }
080
081      Collections.sort(appenders, new FileAppenderPriorityComparator(Order.DESC));
082
083      this.fileAppender = appenders.get(0);
084      this.fileExtractor = ServiceLoader.load(FileExtractor.class).iterator().next();
085      this.transformer = new InvoiceTransformer();
086   }
087
088   /**
089    * Append an invoice to a PDF.  
090    * 
091    * The resulting Pdf Output is based on the input PDF, but might be converted to PDF/A-3 when needed.
092    * 
093    * @param invoice that should be attached to the pdf.
094    * @param inputPdf to witch we are going to append the invoice to, input will not be modified
095    * @param resultingPdf is the modified copy of the input PDF with the invoice.
096    */
097   public void appendInvoice(final Invoice invoice, final InputStream inputPdf, final OutputStream resultingPdf) {
098      try {
099         append(invoice, inputPdf, resultingPdf);
100      } catch (IOException e) {
101         throw new InvoiceAppendError("Not able to append invoice to PDF", e);
102      }
103   }
104
105   private void append(final Invoice invoice, final InputStream inputPdf, final OutputStream resultingPdf) throws IOException {
106      PipedOutputStream pipedOutputStream = new PipedOutputStream();
107      PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream, 65536);
108      try {
109         String version = invoice.getContext().getGuideline().getVersion().versionAlt();
110         String confomanceLevel = invoice.getContext().getGuideline().getConformanceLevel().name();
111         transformer.fromModelAsync(invoice, pipedOutputStream);
112         DefaultAppendParameter parameter = new DefaultAppendParameter(inputPdf, pipedInputStream, resultingPdf,
113               version, confomanceLevel);
114         fileAppender.append(parameter);
115      } finally {
116         pipedInputStream.close();
117      }
118   }
119
120   /**
121    * Extract invoice from given pdf file
122    *
123    * @param pdfFile the pdf file containing the ZUGFeRD XML File 
124    * @return the transformed ZUGFeRD invoice
125    * @throws FileNotFoundException if the pdf is not found 
126    */
127   public Invoice extractInvoice(File pdfFile) throws FileNotFoundException {
128      byte[] xmlInvoice = fileExtractor.extract(new FileInputStream(pdfFile));
129      return transformer.toModel(new ByteArrayInputStream(xmlInvoice));
130   }
131
132   /**
133    * Extract invoice from given pdf stream
134    *
135    * @param pdfInputStream the pdf input stream
136    * @return the transformed ZUGFeRD invoice
137    */
138   public Invoice extractInvoice(InputStream pdfInputStream) {
139      InputStream invoiceInputStream = fileExtractor.extractToStream(pdfInputStream);
140      Invoice invoiceModel = transformer.toModel(invoiceInputStream);
141      closeQuietly(invoiceInputStream);
142      return invoiceModel;
143      
144   }
145
146   private static void closeQuietly(InputStream stream) {
147      try {
148         if (stream != null) {
149            stream.close();
150         }
151      }catch(IOException e) {
152         LOG.log(WARNING, "Could not close InputStream. This can be a memory leak as the PDF might still be open.", e);
153      }
154   }
155}