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 static javax.xml.bind.JAXBContext.newInstance;
022import io.konik.exception.TransformationException;
023import io.konik.zugferd.Invoice;
024
025import java.io.ByteArrayOutputStream;
026import java.io.File;
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.OutputStream;
030import java.net.URL;
031import java.util.logging.Logger;
032
033import javax.inject.Named;
034import javax.inject.Singleton;
035import javax.xml.XMLConstants;
036import javax.xml.bind.JAXBContext;
037import javax.xml.bind.JAXBElement;
038import javax.xml.bind.JAXBException;
039import javax.xml.bind.Marshaller;
040import javax.xml.bind.Unmarshaller;
041import javax.xml.transform.stream.StreamSource;
042import javax.xml.validation.Schema;
043import javax.xml.validation.SchemaFactory;
044import javax.xml.validation.Validator;
045
046import org.xml.sax.SAXException;
047
048/**
049 * Transforms invoices from one representation to another. In other words marshaling and unmarshalling.
050 * 
051 */
052@Named
053@Singleton
054public class InvoiceTransformer {
055
056   private static final Logger LOG = Logger.getLogger(InvoiceTransformer.class.getName());
057   
058   private static final String MARSHALLING_ERROR = "Marshalling error";
059
060   private static final String KONIK_CONTEXT = "io.konik.zugferd";
061
062   private final JAXBContext jaxbContext;
063
064   /**
065    * Instantiates a default invoice transformer.
066    */
067   public InvoiceTransformer() {
068      try {
069         this.jaxbContext = newInstance(KONIK_CONTEXT);
070      } catch (JAXBException e) {
071         throw new TransformationException("Could not instantiate JaxB Context", e);
072      }
073   }
074
075   /**
076    * Instantiates a new invoice transformer providing a JAXB Context
077    *
078    * @param jaxbContext the JAXB context
079    */
080   InvoiceTransformer(JAXBContext jaxbContext) {
081      this.jaxbContext = jaxbContext;
082   }
083
084   /**
085    * Transform from XML input stream to the invoice model.
086    * 
087    * @param xmlInputStream the xml input stream
088    * @return the invoice model
089    */
090   public Invoice toModel(InputStream xmlInputStream) {
091      try {
092         Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
093         return unmarshaller.unmarshal(new StreamSource(xmlInputStream), Invoice.class).getValue();
094      } catch (JAXBException e) {
095         throw new TransformationException(MARSHALLING_ERROR, e);
096      }
097   }
098
099   /**
100    * Transform from XML content from File to the invoice model.
101    * 
102    * @param file the file
103    * @return the invoice
104    */
105   @SuppressWarnings("unchecked")
106   public Invoice toModel(File file) {
107      try {
108         Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
109         return ((JAXBElement<Invoice>) unmarshaller.unmarshal(file)).getValue();
110      } catch (JAXBException e) {
111         throw new TransformationException(MARSHALLING_ERROR, e);
112      }
113   }
114
115   /**
116    * Transform from Invoice model to xml byte array.
117    * 
118    * @param invoice the invoice
119    * @return the byte[]
120    */
121   public byte[] fromModel(Invoice invoice) {
122      ByteArrayOutputStream outputStream = new ByteArrayOutputStream(16000);
123      try {
124         Marshaller marshaller = createMarshaller();
125         marshaller.marshal(invoice, outputStream);
126      } catch (JAXBException e) {
127         throw new TransformationException(MARSHALLING_ERROR, e);
128      }
129      return outputStream.toByteArray();
130   }
131
132   /**
133    * Transform from Invoice model to output stream.
134    *
135    * @param invoice the invoice
136    * @param outputStream the output stream
137    */
138   public void fromModel(Invoice invoice, OutputStream outputStream) {
139      try {
140         Marshaller marshaller = createMarshaller();
141         marshaller.marshal(invoice, outputStream);
142      } catch (JAXBException e) {
143         throw new TransformationException(MARSHALLING_ERROR, e);
144      }
145   }
146
147   /**
148    * From model Async.
149    * 
150    * Will start a new Thread for the transformation.
151    *
152    * @param invoice the invoice
153    * @param outputStream the output stream
154    */
155   public void fromModelAsync(final Invoice invoice, final OutputStream outputStream) {
156      new Thread(new Runnable() {
157         @Override
158         public void run() {
159            fromModel(invoice, outputStream);
160            try {
161               outputStream.flush();
162               outputStream.close();
163            } catch (IOException e) {
164               LOG.log(WARNING, "Faild to Transform Model", e);
165            }
166         }
167      }).start();
168   }
169
170   /**
171    * Gets the ZUGFeRD schema Validator.
172    * 
173    * @return the Schema Validator
174    * @throws SAXException the SAX exception
175    */
176   public Validator getZfSchemaValidator() throws SAXException {      
177      SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
178      URL schemaInvoice = InvoiceTransformer.class.getResource("/zfSchema/ZUGFeRD_1p0.xsd");
179      Schema invoiceSchema = sf.newSchema(schemaInvoice);
180      return invoiceSchema.newValidator();
181   }
182
183   private Marshaller createMarshaller() throws JAXBException {
184      Marshaller marshaller = jaxbContext.createMarshaller();
185      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, formatXmlOutput());
186      return marshaller;
187   }
188
189   protected Boolean formatXmlOutput() {
190      return Boolean.FALSE;
191   }
192
193}