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}