package at.gv.egovernment.moa.spss.server.invoke; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.EntityResolver; import org.xml.sax.SAXException; import iaik.ixsil.util.URI; import iaik.ixsil.util.XPointerReferenceResolver; import iaik.server.modules.xml.DataObject; import iaik.server.modules.xml.XMLDataObject; import at.gv.egovernment.moa.logging.LogMsg; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.Constants; import at.gv.egovernment.moa.util.DOMUtils; import at.gv.egovernment.moa.util.EntityResolverChain; import at.gv.egovernment.moa.util.MOAEntityResolver; import at.gv.egovernment.moa.util.MOAErrorHandler; import at.gv.egovernment.moa.util.StreamEntityResolver; import at.gv.egovernment.moa.util.StreamUtils; import at.gv.egovernment.moa.spss.MOAApplicationException; import at.gv.egovernment.moa.spss.MOASystemException; import at.gv.egovernment.moa.spss.api.common.Content; import at.gv.egovernment.moa.spss.api.common.ContentBinary; import at.gv.egovernment.moa.spss.api.common.ContentLocRef; import at.gv.egovernment.moa.spss.api.common.ContentXML; import at.gv.egovernment.moa.spss.api.common.MetaInfo; import at.gv.egovernment.moa.spss.api.common.XMLDataObjectAssociation; import at.gv.egovernment.moa.spss.api.xmlverify.TransformParameter; import at.gv.egovernment.moa.spss.api.xmlverify.TransformParameterBinary; import at.gv.egovernment.moa.spss.server.iaik.xml.ByteArrayDataObjectImpl; import at.gv.egovernment.moa.spss.server.iaik.xml.ByteStreamDataObjectImpl; import at.gv.egovernment.moa.spss.server.iaik.xml.DataObjectImpl; import at.gv.egovernment.moa.spss.server.iaik.xml.XMLDataObjectImpl; import at.gv.egovernment.moa.spss.server.iaik.xml.XMLNodeListDataObjectImpl; import at.gv.egovernment.moa.spss.util.MessageProvider; /** * A class to create DataObjects contained in different * locations of the MOA XML request format. * * @author Patrick Peck * @author Gregor Karlinger * @version $Id$ */ public class DataObjectFactory { /** The single instance of this class. */ private static DataObjectFactory instance = null; /** * Return the only instance of this class. * * @return The only instance of this class. */ public static synchronized DataObjectFactory getInstance() { if (instance == null) { instance = new DataObjectFactory(); } return instance; } /** * Create a new DataObjectFactory. * * Protected to disallow multiple instances. */ protected DataObjectFactory() { } /** * Return the signature environment, i.e., the root element of the * document, into which the signature will be inserted (if created) or which * contains the signature (if verified). * * @param content The Content object containing the signature * environment. * @param supplements Additional schema or DTD information. * @return The signature environment or null, if no * signature environment exists. * @throws MOASystemException A system error occurred building the signature * environment (see message for details). * @throws MOAApplicationException An error occurred building the signature * environment (see message for details). */ public XMLDataObject createSignatureEnvironment( Content content, List supplements) throws MOASystemException, MOAApplicationException { String reference = content.getReference(); EntityResolver entityResolver; byte[] contentBytes; // check for content and reference not being set at the same time checkAllowContentAndReference(content, false); // build the EntityResolver for validating parsing if (supplements == null || supplements.isEmpty()) { entityResolver = new MOAEntityResolver(); } else { EntityResolverChain chain = new EntityResolverChain(); chain.addEntityResolver(buildSupplementEntityResolver(supplements)); chain.addEntityResolver(new MOAEntityResolver()); entityResolver = chain; } // convert the content into a byte array try { switch (content.getContentType()) { case Content.BINARY_CONTENT : { InputStream is = ((ContentBinary) content).getBinaryContent(); contentBytes = StreamUtils.readStream(is); break; } case Content.LOCREF_CONTENT: { ExternalURIResolver uriResolver = new ExternalURIResolver(); String locRefURI = ((ContentLocRef) content).getLocationReferenceURI(); try { InputStream is = uriResolver.resolve(locRefURI); contentBytes = StreamUtils.readStream(is); } catch (MOAApplicationException e) { throw new MOAApplicationException("3203", new Object[]{reference, locRefURI}, e); } break; } case Content.REFERENCE_CONTENT : { ExternalURIResolver uriResolver = new ExternalURIResolver(); InputStream is = uriResolver.resolve(reference); contentBytes = StreamUtils.readStream(is); break; } case Content.XML_CONTENT : { Element element = checkForSingleElement(((ContentXML) content).getXMLContent()); contentBytes = DOMUtils.serializeNode(element, "UTF-8"); break; } default : contentBytes = null; // this will not happen } } catch (MOAApplicationException e) { throw e; } catch (Exception e) { throw new MOAApplicationException("2219", null); } // try to parse validating try { ByteArrayInputStream is = new ByteArrayInputStream(contentBytes); Document doc = DOMUtils.parseDocument( is, true, Constants.ALL_SCHEMA_LOCATIONS, null, entityResolver, new MOAErrorHandler()); return new XMLDataObjectImpl(doc.getDocumentElement()); } catch (Exception e) { // never mind, we'll try non-validating MessageProvider msg = MessageProvider.getInstance(); Logger.info(new LogMsg(msg.getMessage("invoker.00", null))); } // try to parse non-validating try { ByteArrayInputStream is = new ByteArrayInputStream(contentBytes); Document doc = DOMUtils.parseDocument(is, false, null, null); return new XMLDataObjectImpl(doc.getDocumentElement()); } catch (Exception e) { throw new MOAApplicationException("2218", null); } } /** * Create an XMLDataObject from the given signature environment. * * @param signatureEnvironment The signature environment contained in the * result. * @param uri The URI identifying the data. This must be either the empty * URI, an URI starting with "#xpointer", "#xmlns" * or "#element"; or an URI starting with "#" and * followed by an element ID. * @param referenceID The reference ID to set for the data object. * @return A data object containing the signature environment. */ public DataObject createFromSignatureEnvironment( Element signatureEnvironment, String uri, String referenceID) throws MOAApplicationException { DataObjectImpl dataObject = null; if ("".equals(uri)) { dataObject = new XMLDataObjectImpl(signatureEnvironment); } else if ( uri.startsWith("#xpointer") || uri.startsWith("#xmlns") || uri.startsWith("#element")) { try { XPointerReferenceResolver resolver = new XPointerReferenceResolver(); URI uriObj = new URI(uri); NodeList nodes = resolver.resolveForest( uriObj, signatureEnvironment.getOwnerDocument(), null); dataObject = new XMLNodeListDataObjectImpl(nodes); } catch (Exception e) { throw new MOAApplicationException("2237", new Object[] { uri }); } } else if (uri.startsWith("#")) { String id = uri.substring(1); Element refElem = signatureEnvironment.getOwnerDocument().getElementById(id); if (refElem == null) { throw new MOAApplicationException("2237", new Object[] { id }); } dataObject = new XMLDataObjectImpl(refElem); } dataObject.setReferenceID(referenceID); dataObject.setURI(uri); return dataObject; } /** * Build a StreamEntityResolver from a List of * supplements. * * @param supplements The supplements, given as * XMLDataObjectAssociations. * @return A StreamEntityResolver mapping the supplements by * their reference URI to an InputStream of their respective * content. */ private static StreamEntityResolver buildSupplementEntityResolver(List supplements) throws MOAApplicationException { Map entities = new HashMap(); Iterator iter; for (iter = supplements.iterator(); iter.hasNext();) { XMLDataObjectAssociation supplement = (XMLDataObjectAssociation) iter.next(); Content content = supplement.getContent(); String reference = content.getReference(); switch (content.getContentType()) { case Content.BINARY_CONTENT : { entities.put(reference, ((ContentBinary) content).getBinaryContent()); break; } case Content.LOCREF_CONTENT: { ExternalURIResolver uriResolver = new ExternalURIResolver(); String locRefURI = ((ContentLocRef) content).getLocationReferenceURI(); InputStream contentIS = null; try { contentIS = uriResolver.resolve(locRefURI); } catch (MOAApplicationException e) { throw new MOAApplicationException("3202", new Object[]{reference, locRefURI}, e); } entities.put(reference, contentIS); break; } case Content.XML_CONTENT : { // serialize the first element node that is found in the supplement // and make it available as a stream NodeList nodes = ((ContentXML) content).getXMLContent(); int i = 0; // find the first element node while (i < nodes.getLength() && nodes.item(i).getNodeType() != Node.ELEMENT_NODE) i++; // serialize the node if (i < nodes.getLength()) { try { byte[] serialized = DOMUtils.serializeNode(nodes.item(i), "UTF-8"); entities.put(reference, new ByteArrayInputStream(serialized)); } catch (Exception e) { throw new MOAApplicationException("2281", new Object[]{reference}, e); } } break; } } } return new StreamEntityResolver(entities); } /** * Create a DataObject from a Content object. * * @param content The Content object containing the data. * @param finalDataMetaInfo The meta information corresponding with content. * @param referenceID The reference ID to set in the resulting * DataObject. May be null. * @param allowContentAndReference If true, then * content is allowed to contain both a Reference * attribute and content. Otherwise, either a Reference * attribute or content must be set. * @param binaryAsXml If true, a content child given as * Base64Content must contain XML data. * @param xmlAsNodeList If true, the children of a * XMLContent child element are returned as a * XMLNodeListDataObject. Otherwise, XMLContent may * only contain a single child node, which must be an element and which is * returned as an XMLDataObject. * @param referenceAsXml If true, then content loaded from the * URI given as the Reference attribute must be XML data. * If false, an attempt is made to parse the data as XML and * return an XMLDataObject but if this fails, a * BinaryDataObject is returned containing a byte stream to the * data. * @return A DataObject representing the data in * content. If base64AsXml==true and * xmlAsNodeList==false and referenceAsXml==true, * then the result can safely be cast to an XMLDataObject. * @throws MOASystemException An error indicating an internal problem. See the * wrapped exception for details. * @throws MOAApplicationException An error occurred handling the content * (probably while opening a reference or parsing the data). See the wrapped * exception for details. */ public DataObject createFromContentOptionalRefType( Content content, MetaInfo finalDataMetaInfo, String referenceID, boolean allowContentAndReference, boolean binaryAsXml, boolean xmlAsNodeList, boolean referenceAsXml) throws MOASystemException, MOAApplicationException { String reference = content.getReference(); DataObjectImpl dataObject = null; checkAllowContentAndReference(content, allowContentAndReference); // ok, build the data object; use content first, if available switch (content.getContentType()) { case Content.XML_CONTENT : { ContentXML contentXml = (ContentXML) content; dataObject = createFromXmlContent(contentXml, xmlAsNodeList); break; } case Content.BINARY_CONTENT : { ContentBinary contentBinary = (ContentBinary) content; dataObject = createFromBinaryContent(contentBinary, binaryAsXml, false); break; } case Content.LOCREF_CONTENT : { String locRefURI = ((ContentLocRef) content).getLocationReferenceURI(); try { dataObject = createFromURIImpl(locRefURI, referenceAsXml); } catch (MOAApplicationException e) { throw new MOAApplicationException("3201", new Object[]{reference, locRefURI}, e); } break; } case Content.REFERENCE_CONTENT : { dataObject = createFromURIImpl(reference, referenceAsXml); break; } } // set URI and reference ID dataObject.setURI(reference); dataObject.setReferenceID(referenceID); // set Type gathered from corresponding meta information dataObject.setTypeURI(finalDataMetaInfo.getType()); return dataObject; } /** * Check, if content and reference URIs are allowed in the content an throw * an exception if an illegal combination of the two occurs. * * @param content The Content to check. * @param allowContentAndReference Whether explicit content and a reference * are allowed at the same time. * @throws MOAApplicationException If allowContentAndRefernece * is false and both explicit content and reference are set, * an exception is thrown. */ private static void checkAllowContentAndReference( Content content, boolean allowContentAndReference) throws MOAApplicationException { String reference = content.getReference(); // check for content and reference not being set if (content.getContentType() == Content.REFERENCE_CONTENT && reference == null) { String errorCode = allowContentAndReference ? "1111" : "1110"; throw new MOAApplicationException(errorCode, null); } // if we only allow either content or reference being set at once, check if (!allowContentAndReference && (content.getContentType() != Content.REFERENCE_CONTENT) && (reference != null)) { throw new MOAApplicationException("1110", null); } } /** * Create a DataObject from a * XMLDataObjectAssociation object. * * @param xmlDataObjAssoc The XMLDataObjectAssociation object. * @param xmlContentAllowed Whether the content contained in the * xmlDataObjAssoc is allowed to be of type * XML_CONTENT. * @param binaryContentRepeatable If binary content must be provided as a * DataObject that can be read multiple times. * @return A DataObject representing the data in * xmlDataObjAssoc. * @throws MOASystemException An error indicating an internal problem. See the * wrapped exception for details. * @throws MOAApplicationException An error occurred handling the content * (probably while parsing the data). See the wrapped exception for details. */ public DataObject createFromXmlDataObjectAssociation( XMLDataObjectAssociation xmlDataObjAssoc, boolean xmlContentAllowed, boolean binaryContentRepeatable) throws MOASystemException, MOAApplicationException { Content content = xmlDataObjAssoc.getContent(); MetaInfo metaInfo = xmlDataObjAssoc.getMetaInfo(); String mimeType = metaInfo != null ? metaInfo.getMimeType() : null; DataObjectImpl dataObject = null; switch (content.getContentType()) { case Content.XML_CONTENT : { if (xmlContentAllowed) { dataObject = createFromXmlContent((ContentXML) content, true); } else { throw new MOAApplicationException("2280", null); } break; } case Content.BINARY_CONTENT : { dataObject = createFromBinaryContent( (ContentBinary) content, false, binaryContentRepeatable); break; } case Content.LOCREF_CONTENT : { String locRefURI = ((ContentLocRef) content).getLocationReferenceURI(); try { dataObject = createFromURIImpl(locRefURI, false); } catch (MOAApplicationException e) { throw new MOAApplicationException("3201", new Object[]{content.getReference(), locRefURI}, e); } break; } } dataObject.setURI(content.getReference()); dataObject.setMimeType(mimeType); return dataObject; } /** * Create a DataObject from a TransformParameter * object. * * @param transformParameter The TransformParameter object * containing the data. * @return A DataObject representing the data in * root. * @throws MOASystemException An error indicating an internal problem. See the * wrapped exception for details. * @throws MOAApplicationException An error occurred handling the content * (probably while opening a reference or parsing the data). See the wrapped * exception for details. */ public DataObject createFromTransformParameter(TransformParameter transformParameter) throws MOASystemException, MOAApplicationException { DataObjectImpl dataObject; switch (transformParameter.getTransformParameterType()) { case TransformParameter.BINARY_TRANSFORMPARAMETER : TransformParameterBinary tpBinary = (TransformParameterBinary) transformParameter; try { //dataObject = new ByteArrayDataObjectImpl(Base64Utils.encode(tpBinary.getBinaryContent())); dataObject = new ByteArrayDataObjectImpl( StreamUtils.readStream(tpBinary.getBinaryContent())); } catch (Exception e) { return null; } //dataObject = new ByteStreamDataObjectImpl(tpBinary.getBinaryContent()); break; default : // resolve uri and build the content ExternalURIResolver resolver = new ExternalURIResolver(); InputStream is = resolver.resolve(transformParameter.getURI()); String contentType = resolver.getContentType(); dataObject = new ByteStreamDataObjectImpl(is); dataObject.setMimeType(contentType); break; } dataObject.setURI(transformParameter.getURI()); return dataObject; } /** * Create a DataObject from data located at the given URI. * * @param uri The URI where the data is located. This method uses * an ExternalURIResolver to resolve URIs. * @param asXml If true, a DataObject is only * returned, if the content consists of XML data. If it does not consist of * XML data, an MOAApplicationException will be thrown. If this * parameter is false and the content consists of XML data, this * method will still attempt to parse it. * @return The DataObject contained at the URI. * @throws MOASystemException A system error parsing the XML content. * @throws MOAApplicationException An error occurred on opening, reading or * parsing the data behind the URI. */ public DataObject createFromURI(String uri, boolean asXml) throws MOASystemException, MOAApplicationException { return createFromURIImpl(uri, asXml); } /** * Create a DataObject from data located at the given URI. * * @param uri The URI where the data is located. This method uses * an ExternalURIResolver to resolve URIs. * @param asXml If true, a DataObject is only * returned, if the content consists of XML data. If it does not consist of * XML data, an MOAApplicationException will be thrown. If this * parameter is false and the content type is detected as being * XML data, this method will still attemt to parse it. * @return The DataObject contained at the URI. * @throws MOASystemException A system error parsing the XML content. * @throws MOAApplicationException An error occurred on opening, reading or * parsing the data behind the URI. */ private DataObjectImpl createFromURIImpl(String uri, boolean asXml) throws MOASystemException, MOAApplicationException { ExternalURIResolver resolver = new ExternalURIResolver(); InputStream is = resolver.resolve(uri); String contentType = resolver.getContentType(); DataObjectImpl dataObject; // read the content if (contentType != null && contentTypeIsXml(contentType)) { Document doc; if (asXml) { try { // try parsing non-validating: this has to succeed or we // bail out by throwing an exception is = resolver.resolve(uri); doc = DOMUtils.parseDocument(is, false, null, null); dataObject = new XMLDataObjectImpl(doc.getDocumentElement()); } catch (ParserConfigurationException e) { throw new MOASystemException("1106", null, e); } catch (SAXException e) { throw new MOAApplicationException("2209", null, e); } catch (IOException e) { throw new MOAApplicationException("2210", null, e); } } else { try { // try parsing non-validating: need not succeed is = resolver.resolve(uri); doc = DOMUtils.parseDocument(is, false, null, null); dataObject = new XMLDataObjectImpl(doc.getDocumentElement()); } catch (Exception e) { // this is the last chance: return the data as a byte stream is = resolver.resolve(uri); dataObject = new ByteStreamDataObjectImpl(is); } } } else if (asXml) { // if we need XML data, we're in the wrong place here throw new MOAApplicationException("2211", new Object[] { uri }); } else { // content is binary: make it available as a binary input stream dataObject = new ByteStreamDataObjectImpl(is); } dataObject.setMimeType(contentType); dataObject.setURI(uri); return dataObject; } /** * Determine whether the content type is XML. * * Content types recognized as XML start with text/xml and * application/xml. * * @param contentType The content MIME type. * @return boolean If true, the content type is XML, otherwise * not. */ private static boolean contentTypeIsXml(String contentType) { return contentType.startsWith("text/xml") || (contentType.startsWith("application/xml")); } /** * Create a DataObject from a ContentXML object. * * @param xmlContent The ContentXML object from * which the DataObject is to be built. * @param xmlAsNodeList If true, the children of * xmlContent are returned as a * XMLNodeListDataObject. Otherwise, * xmlContent may only contain a single child node, which must be * an element and which is returned as an XMLDataObject. * @return A DataObject representing the XML content in * xmlContent. * @throws MOAApplicationException If xmlAsNodeList is * false and xmlContent does not have a single child * element. */ private DataObjectImpl createFromXmlContent( ContentXML xmlContent, boolean xmlAsNodeList) throws MOAApplicationException { DataObjectImpl dataObject; if (xmlAsNodeList) { dataObject = new XMLNodeListDataObjectImpl(xmlContent.getXMLContent()); } else { NodeList nodes = xmlContent.getXMLContent(); Element element = checkForSingleElement(nodes); // build the XMLDataObject dataObject = new XMLDataObjectImpl(element); } return dataObject; } /** * Check, that the given NodeList contains a single DOM element * node and return it, otherwise throw an exception. * * @param nodes The NodeList to check for a single element. * @return The single element contained in nodes. * @throws MOAApplicationException Thrown, if nodes does not * contain exactly 1 element node. */ private Element checkForSingleElement(NodeList nodes) throws MOAApplicationException { Element element = null; int i; // check for a single element node for (i = 0; i < nodes.getLength(); i++) { if (nodes.item(i).getNodeType() == Node.ELEMENT_NODE) { if (element == null) { element = (Element) nodes.item(i); } else { throw new MOAApplicationException("1109", null); } } } // return the element node if (element == null) { throw new MOAApplicationException("1107", null); } else { return element; } } /** * Create a DataObject from a ContentBinary object. * * @param binaryContent The ContentBinary object containing the * data. * @param asXml If true, binaryContent must * contain XML data. Otherwise, a BinaryDataObject will be * returned containing a byte stream to the decoded Base64 data. * @param repeatable If multiple calls to getInputStream() must * repeatedly return the content of the data object. * @return A DataObject representing the content contained in * binaryContent. * @throws MOASystemException An error indicating an internal problem. See the * wrapped exception for details. * @throws MOAApplicationException An error occurred handling the content * (probably while parsing the data). See the wrapped exception for details. */ private DataObjectImpl createFromBinaryContent( ContentBinary binaryContent, boolean asXml, boolean repeatable) throws MOASystemException, MOAApplicationException { InputStream byteStream = binaryContent.getBinaryContent(); DataObjectImpl dataObject; if (asXml) { Document doc; try { doc = DOMUtils.parseDocument(byteStream, false, null, null); dataObject = new XMLDataObjectImpl(doc.getDocumentElement()); } catch (ParserConfigurationException e) { throw new MOASystemException("1106", null, e); } catch (SAXException e) { throw new MOAApplicationException("2209", null, e); } catch (IOException e) { throw new MOAApplicationException("2210", null, e); } } else { if (repeatable) { try { dataObject = new ByteArrayDataObjectImpl(StreamUtils.readStream(byteStream)); } catch (IOException e) { throw new MOAApplicationException("2210", null); } } else { dataObject = new ByteStreamDataObjectImpl(byteStream); } } return dataObject; } }