/****************************************************************************
* Copyright (C) 2012-2015 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.ws.jaxb;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.openecard.ws.marshal.MarshallingTypeException;
import org.openecard.ws.marshal.WSMarshaller;
import org.openecard.ws.marshal.WSMarshallerException;
import org.openecard.ws.marshal.WhitespaceFilter;
import org.openecard.ws.soap.MessageFactory;
import org.openecard.ws.soap.SOAPBody;
import org.openecard.ws.soap.SOAPException;
import org.openecard.ws.soap.SOAPMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Implementation of a WSMarshaller utilizing JAXB and Javas default XML facilities.
*
* @author Tobias Wich
*/
public final class JAXBMarshaller implements WSMarshaller {
private static final Logger LOG = LoggerFactory.getLogger(JAXBMarshaller.class);
// Marshaller and Unmarshaller
private final MarshallerImpl marshaller;
// w3 factory
private final DocumentBuilderFactory w3Factory;
private final DocumentBuilder w3Builder;
private final Transformer serializer;
// soap
private final MessageFactory soapFactory;
/**
* Creates a JAXBMarshaller capable of marshalling und unmarshalling all JAXB element types found in the classpath
* resource classes.lst.
*/
public JAXBMarshaller() {
MarshallerImpl tmpMarshaller;
DocumentBuilderFactory tmpW3Factory;
DocumentBuilder tmpW3Builder;
Transformer tmpSerializer;
MessageFactory tmpSoapFactory;
try {
tmpMarshaller = new MarshallerImpl();
// instantiate w3 stuff
tmpW3Factory = DocumentBuilderFactory.newInstance();
tmpW3Factory.setNamespaceAware(true);
tmpW3Factory.setIgnoringComments(true);
tmpW3Factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// XXE countermeasures
tmpW3Factory.setExpandEntityReferences(false);
tmpW3Factory.setXIncludeAware(false);
tmpW3Factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
try {
tmpW3Factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
} catch (ParserConfigurationException ex) {
LOG.warn("Failed to disallow DTDs entirely.");
}
tmpW3Factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
tmpW3Factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
tmpW3Builder = tmpW3Factory.newDocumentBuilder();
TransformerFactory tfactory = TransformerFactory.newInstance();
tfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// XXE countermeasures
tfactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tfactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
try {
tfactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
} catch (TransformerConfigurationException ex) {
LOG.warn("Failed to disallow DTDs entirely.");
}
tmpSerializer = tfactory.newTransformer();
tmpSerializer.setOutputProperty(OutputKeys.INDENT, "yes");
tmpSerializer.setOutputProperty(OutputKeys.STANDALONE, "yes");
tmpSerializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
tmpSerializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
// instantiate soap stuff
tmpSoapFactory = MessageFactory.newInstance();
} catch (ParserConfigurationException | TransformerConfigurationException | IllegalArgumentException | SOAPException ex) {
LOG.error("Failed to initialize XML components.", ex);
System.exit(1); // non recoverable
throw new RuntimeException("Failed to initialize marshaller.", ex);
}
marshaller = tmpMarshaller;
w3Factory = tmpW3Factory;
w3Builder = tmpW3Builder;
serializer = tmpSerializer;
soapFactory = tmpSoapFactory;
}
////////////////////////////////////////////////////////////////////////////
// public functions to marshal and convert stuff
////////////////////////////////////////////////////////////////////////////
@Override
public void addXmlTypeClass(Class<?> xmlTypeClass) throws MarshallingTypeException {
marshaller.addXmlClass(xmlTypeClass);
}
@Override
public void removeAllTypeClasses() {
marshaller.removeAllClasses();
}
@Override
public synchronized Document str2doc(String docStr) throws SAXException {
try {
// read dom as w3
StringReader strReader = new StringReader(docStr);
InputSource inSrc = new InputSource(strReader);
Document doc = w3Builder.parse(inSrc);
WhitespaceFilter.filter(doc);
return doc;
} catch (IOException ex) {
throw new SAXException(ex);
}
}
@Override
public synchronized Document str2doc(InputStream docStr) throws SAXException, IOException {
// read dom as w3
Document doc = w3Builder.parse(docStr);
WhitespaceFilter.filter(doc);
return doc;
}
@Override
public synchronized String doc2str(Node doc) throws TransformerException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
serializer.transform(new DOMSource(doc), new StreamResult(out));
String result;
try {
result = out.toString("UTF-8");
} catch (UnsupportedEncodingException ex) {
throw new TransformerException(ex);
}
return result;
}
@Override
public synchronized Object unmarshal(Node n) throws MarshallingTypeException, WSMarshallerException {
Document newDoc = createDoc(n);
Object result;
try {
result = marshaller.getUnmarshaller().unmarshal(newDoc); //NOI18N
} catch (JAXBException ex) {
throw new MarshallingTypeException(ex);
}
return result;
}
@Override
public synchronized <T> JAXBElement<T> unmarshal(Node n, Class<T> c) throws MarshallingTypeException,
WSMarshallerException {
Document newDoc = createDoc(n);
JAXBElement<T> result;
try {
result = marshaller.getUnmarshaller().unmarshal(newDoc, c); //NOI18N
} catch (JAXBException ex) {
throw new MarshallingTypeException(ex);
}
return result;
}
private Document createDoc(Node n) throws WSMarshallerException {
Document newDoc = null;
if (n instanceof Document) {
newDoc = (Document) n;
} else if (n instanceof Element) {
newDoc = w3Builder.newDocument();
Node root = newDoc.importNode(n, true);
newDoc.appendChild(root);
} else {
throw new WSMarshallerException("Only w3c Document and Element are accepted.");
}
return newDoc;
}
@Override
public synchronized Document marshal(Object o) throws MarshallingTypeException {
try {
Document d = w3Builder.newDocument();
marshaller.getMarshaller().marshal(o, d);
return d;
} catch (JAXBException ex) {
throw new MarshallingTypeException(ex);
}
}
@Override
public synchronized SOAPMessage doc2soap(Document envDoc) throws SOAPException {
SOAPMessage msg = soapFactory.createMessage(envDoc);
return msg;
}
@Override
public synchronized SOAPMessage add2soap(Document content) throws SOAPException {
SOAPMessage msg = soapFactory.createMessage();
SOAPBody body = msg.getSOAPBody();
body.addDocument(content);
return msg;
}
}