001/* 002 * Copyright (C) 2014 Konik.io 003 * 004 * This file is part of Konik library. 005 * 006 * Konik library is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU Affero General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Konik library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU Affero General Public License for more details. 015 * 016 * You should have received a copy of the GNU Affero General Public License 017 * along with Konik library. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package io.konik.itext.appender; 020 021import static com.itextpdf.text.pdf.AFRelationshipValue.Alternative; 022import static com.itextpdf.text.pdf.PdfName.AFRELATIONSHIP; 023import static com.itextpdf.text.pdf.PdfName.MODDATE; 024import static com.itextpdf.text.pdf.PdfName.PARAMS; 025import io.konik.InvoiceTransformer; 026import io.konik.exception.TransformationWarning; 027import io.konik.harness.InvoiceAppendError; 028import io.konik.harness.InvoiceAppender; 029import io.konik.itext.xmp.XmpAppender; 030import io.konik.itext.xmp.ZfXmpInfo; 031import io.konik.zugferd.Invoice; 032import io.konik.zugferd.profile.ConformanceLevel; 033 034import java.io.ByteArrayInputStream; 035import java.io.ByteArrayOutputStream; 036import java.io.IOException; 037import java.io.InputStream; 038import java.io.OutputStream; 039 040import javax.inject.Inject; 041import javax.inject.Named; 042import javax.inject.Singleton; 043 044import com.itextpdf.text.DocumentException; 045import com.itextpdf.text.pdf.PdfAConformanceLevel; 046import com.itextpdf.text.pdf.PdfAStamper; 047import com.itextpdf.text.pdf.PdfArray; 048import com.itextpdf.text.pdf.PdfDate; 049import com.itextpdf.text.pdf.PdfDictionary; 050import com.itextpdf.text.pdf.PdfFileSpecification; 051import com.itextpdf.text.pdf.PdfName; 052import com.itextpdf.text.pdf.PdfReader; 053 054/** 055 * The Class IText PDF Invoice Appender. 056 * 057 * For now we expect a compliant PDF/A-3B. 058 * 059 */ 060@Named 061@Singleton 062public class ITextPdfInvoiceAppender implements InvoiceAppender { 063 064 private static final String INVOICE = "INVOICE"; 065 066 private static final String ZF_FILE_NAME = "ZUGFeRD-invoice.xml"; 067 068 private final XmpAppender xmp; 069 070 private final InvoiceTransformer transformer; 071 072 /** 073 * Instantiates a new i-text pdf invoice appender. 074 */ 075 public ITextPdfInvoiceAppender() { 076 this(new XmpAppender(),new InvoiceTransformer()); 077 } 078 079 /** 080 * Instantiates a new i-text pdf invoice appender. 081 * 082 * @param xmpAppender the xmp appender 083 * @param invoiceTransformer the invoice transformer 084 */ 085 @Inject 086 public ITextPdfInvoiceAppender(XmpAppender xmpAppender, InvoiceTransformer invoiceTransformer) { 087 this.xmp = xmpAppender; 088 this.transformer = invoiceTransformer; 089 } 090 091 /** 092 * Append invoice. 093 * 094 * @param invoice the invoice 095 * @param pdf the in PDF byte array 096 * @return the byte[] 097 */ 098 @Override 099 public byte[] append(final Invoice invoice, final byte[] pdf) { 100 ByteArrayInputStream isPdf = new ByteArrayInputStream(pdf); 101 ByteArrayOutputStream osPdf = new ByteArrayOutputStream(pdf.length); 102 103 append(invoice, isPdf, osPdf); 104 105 return osPdf.toByteArray(); 106 } 107 108 /** 109 * Append invoice. 110 * 111 * @param invoice the invoice 112 * @param inputPdf the input pdf 113 * @param resultingPdf the resulting pdf 114 */ 115 @Override 116 public void append(final Invoice invoice, InputStream inputPdf, OutputStream resultingPdf) { 117 try { 118 appendInvoiceIntern(invoice, inputPdf, resultingPdf); 119 } catch (DocumentException e) { 120 throw new InvoiceAppendError("Could not open PD for modification or to close it", e); 121 } catch (IOException e) { 122 throw new InvoiceAppendError("PDF IO Error", e); 123 } 124 } 125 126 /** 127 * Append invoice intern. 128 * 129 * @param invoice the invoice 130 * @param inPdf the in pdf 131 * @param output the output 132 * @throws IOException Signals that an I/O exception has occurred. 133 * @throws DocumentException the document exception 134 */ 135 private void appendInvoiceIntern(Invoice invoice, InputStream inPdf, OutputStream output) throws IOException, 136 DocumentException { 137 138 byte[] content = transformer.fromModel(invoice); 139 140 PdfReader reader = new PdfReader(inPdf); 141 142 PdfAStamper stamper = new PdfAStamper(reader, output, PdfAConformanceLevel.PDF_A_3B); 143 144 appendZfContentToXmp(stamper,invoice); 145 146 // Creating PDF/A-3 compliant attachment. 147 PdfDictionary embeddedFileParams = new PdfDictionary(); 148 embeddedFileParams.put(PARAMS, new PdfName(ZF_FILE_NAME)); 149 embeddedFileParams.put(MODDATE, new PdfDate()); 150 PdfFileSpecification fs = PdfFileSpecification.fileEmbedded(stamper.getWriter(), null, ZF_FILE_NAME, content, 151 "text/xml", embeddedFileParams, 0); 152 fs.put(AFRELATIONSHIP, Alternative); 153 stamper.addFileAttachment(ZF_FILE_NAME, fs); 154 155 //AF 156 PdfArray array = new PdfArray(); 157 array.add(fs.getReference()); 158 stamper.getWriter().getExtraCatalog().put(new PdfName("AF"), array); 159 160 stamper.close(); 161 reader.close(); 162 } 163 164 private void appendZfContentToXmp(PdfAStamper stamper, Invoice invoice) throws IOException { 165 ConformanceLevel profile = invoice.getContext().getGuideline().getConformanceLevel(); 166 ZfXmpInfo info = new ZfXmpInfo(profile, ZF_FILE_NAME, INVOICE); 167 try { 168 byte[] newXmpMetadata = xmp.append(stamper.getReader().getMetadata(), info); 169 stamper.setXmpMetadata(newXmpMetadata); 170 } catch (TransformationWarning e) { 171 throw new InvoiceAppendError("Error Appending XMP to PDF", e); 172 // TODO if we don't rethrow we should provide a result object with a warning Msg. 173 } 174 } 175 176 @Override 177 public byte[] append(Invoice invoice, InputStream inputStreamPdf) { 178 return null; 179 } 180}