/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2010 Luis Goncalves.
*
* XAdES4j is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or any later version.
*
* XAdES4j is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with XAdES4j. If not, see <http://www.gnu.org/licenses/>.
*/
package xades4j.xml.marshalling;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import xades4j.properties.QualifyingProperty;
import xades4j.properties.data.GenericDOMData;
import xades4j.properties.data.PropertyDataObject;
import xades4j.properties.data.SigAndDataObjsPropertiesData;
import xades4j.xml.bind.xades.ObjectFactory;
import xades4j.xml.bind.xades.XmlSignedPropertiesType;
import xades4j.xml.bind.xades.XmlUnsignedPropertiesType;
import xades4j.utils.CollectionUtils;
import xades4j.utils.DOMHelper;
/**
*
* @author Luís
*/
abstract class BaseJAXBMarshaller<TXml>
{
private static final Map<Class, JAXBContext> jaxbContexts;
static
{
try
{
Map<Class, JAXBContext> contexts = new HashMap<Class, JAXBContext>();
contexts.put(XmlSignedPropertiesType.class, JAXBContext.newInstance(XmlSignedPropertiesType.class));
contexts.put(XmlUnsignedPropertiesType.class, JAXBContext.newInstance(XmlUnsignedPropertiesType.class));
jaxbContexts = Collections.unmodifiableMap(contexts);
}
catch (JAXBException e)
{
throw new UnsupportedOperationException(e);
}
}
private final Map<Class, QualifyingPropertyDataToXmlConverter<TXml>> converters;
private final String propsElemName;
protected BaseJAXBMarshaller(int convertersInitialSize, String propsElemName)
{
this.converters = new HashMap<Class, QualifyingPropertyDataToXmlConverter<TXml>>(convertersInitialSize);
this.propsElemName = propsElemName;
}
protected void putConverter(
Class<? extends PropertyDataObject> propClass,
QualifyingPropertyDataToXmlConverter<TXml> propConverter)
{
this.converters.put(propClass, propConverter);
}
protected void doMarshal(
SigAndDataObjsPropertiesData properties,
Node qualifyingPropsNode,
TXml xmlProps) throws MarshalException
{
if (properties.isEmpty())
{
return;
}
Document doc = qualifyingPropsNode.getOwnerDocument();
Collection<PropertyDataObject> unknownSigProps = null;
if (!properties.getSigProps().isEmpty())
{
prepareSigProps(xmlProps);
unknownSigProps = convert(properties.getSigProps(), xmlProps, doc);
}
Collection<PropertyDataObject> unknownDataObjProps = null;
if (!properties.getDataObjProps().isEmpty())
{
prepareDataObjsProps(xmlProps);
unknownDataObjProps = convert(properties.getDataObjProps(), xmlProps, doc);
}
if (propsNotAlreadyPresent(qualifyingPropsNode))
// If the QualifyingProperties node doesn't already have an element
// for the current type of properties, do a JAXB marshalling into it.
{
doJAXBMarshalling(qualifyingPropsNode, xmlProps);
} else
{
// If it has, marshall into a temp node and transfer the resulting
// nodes into the appropriate QualifyingProperties children.
Node tempNode = DOMHelper.createElement(
qualifyingPropsNode.getOwnerDocument(), "temp", null, QualifyingProperty.XADES_XMLNS);
// - A little work around to inherit the namespace node defined in
// the document. Its just a matter of style.
qualifyingPropsNode.appendChild(tempNode);
doJAXBMarshalling(tempNode, xmlProps);
qualifyingPropsNode.removeChild(tempNode);
transferProperties(qualifyingPropsNode, tempNode);
}
// The top-most XML element for the current type of properties.
Element topMostPropsElem = DOMHelper.getFirstDescendant(
(Element) qualifyingPropsNode,
QualifyingProperty.XADES_XMLNS, propsElemName);
if (!CollectionUtils.nullOrEmpty(unknownSigProps))
{
marshallUnknownProps(unknownSigProps, DOMHelper.getFirstChildElement(topMostPropsElem));
}
if (!CollectionUtils.nullOrEmpty(unknownDataObjProps))
{
marshallUnknownProps(unknownDataObjProps, DOMHelper.getLastChildElement(topMostPropsElem));
}
}
private Collection<PropertyDataObject> convert(
Collection<PropertyDataObject> props,
TXml xmlProps,
Document doc) throws MarshalException
{
Collection<PropertyDataObject> unknownProps = null;
// Convert each property to the corresponding JAXB object. Each converter
// will add the corresponding object to the tree.
// If a converter is not found, it means that the property is unknown in
// this version of XAdES; it will be converted afterwards.
QualifyingPropertyDataToXmlConverter<TXml> conv;
for (PropertyDataObject p : props)
{
conv = this.converters.get(p.getClass());
if (null == conv)
{
unknownProps = CollectionUtils.newIfNull(unknownProps, 1);
unknownProps.add(p);
} else
{
conv.convertIntoObjectTree(p, xmlProps, doc);
}
}
return unknownProps;
}
private boolean propsNotAlreadyPresent(Node qualifyingPropsNode)
{
return null == DOMHelper.getFirstDescendant(
(Element) qualifyingPropsNode,
QualifyingProperty.XADES_XMLNS, propsElemName);
}
private void doJAXBMarshalling(Node qualifyingPropsNode, TXml xmlProps) throws MarshalException
{
try
{
// Create the JAXB marshaller.
JAXBContext jaxbContext = jaxbContexts.get(xmlProps.getClass());
Marshaller marshaller = jaxbContext.createMarshaller();
// Create the root JAXBElement.
Object propsElem = createPropsXmlElem(new ObjectFactory(), xmlProps);
// Marshal the properties.
marshaller.marshal(propsElem, qualifyingPropsNode);
}
catch (JAXBException ex)
{
throw new MarshalException("Error on JAXB marshalling", ex);
}
}
private void transferProperties(Node qualifPropsNode, Node tempNode)
{
// The QualifyingProperties node already has a child element for the current
// type of properties.
Element existingProps = DOMHelper.getFirstDescendant(
(Element) qualifPropsNode,
QualifyingProperty.XADES_XMLNS, propsElemName);
// The new properties (Signed or Unsigned) were marshalled into the temp
// node.
Element newProps = DOMHelper.getFirstChildElement(tempNode);
Element newSpecificProps = DOMHelper.getFirstChildElement(newProps);
do
{
Element existingSpecificProps = DOMHelper.getFirstDescendant(
existingProps, newSpecificProps.getNamespaceURI(), newSpecificProps.getLocalName());
if (null == existingSpecificProps)
// No element for these properties. Append the new element to the existing
// properties.
{
existingProps.appendChild(newSpecificProps);
} else
// There are properties. Transfer all the new properties into the existing
// element.
{
transferChildren(newSpecificProps, existingSpecificProps);
}
newSpecificProps = DOMHelper.getNextSiblingElement(newSpecificProps);
} while (newSpecificProps != null);
}
private void transferChildren(Element from, Element to)
{
Node child = from.getFirstChild();
Node childSib;
while (child != null)
{
// Need this temp node because when the 'child' node is appended to
// the destination subtree I won't be able to access the siblings on
// the previous subtree.
childSib = child.getNextSibling();
to.appendChild(child);
child = childSib;
}
}
private void marshallUnknownProps(
Collection<PropertyDataObject> unknownProps,
Element parent) throws MarshalException
{
for (PropertyDataObject pData : unknownProps)
{
if (!(pData instanceof GenericDOMData))
{
throw new UnsupportedDataObjectException(pData);
}
Node propElem = ((GenericDOMData) pData).getPropertyElement();
if (propElem.getOwnerDocument() != parent.getOwnerDocument())
{
propElem = parent.getOwnerDocument().importNode(propElem, true);
}
parent.appendChild(propElem);
}
}
protected abstract void prepareSigProps(TXml xmlProps);
protected abstract void prepareDataObjsProps(TXml xmlProps);
protected abstract Object createPropsXmlElem(
ObjectFactory objFact,
TXml xmlProps);
}