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}