/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.platform.xml.xdk; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.oxm.NamespaceResolver; import org.eclipse.persistence.oxm.XMLConstants; import org.eclipse.persistence.oxm.XMLDescriptor; import org.eclipse.persistence.platform.xml.XMLNamespaceResolver; import org.eclipse.persistence.platform.xml.XMLParser; import org.eclipse.persistence.platform.xml.XMLPlatform; import org.eclipse.persistence.platform.xml.XMLPlatformException; import org.eclipse.persistence.platform.xml.XMLPlatformFactory; import org.eclipse.persistence.platform.xml.XMLSchemaReference; import org.eclipse.persistence.platform.xml.XMLTransformer; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import oracle.xml.parser.schema.XMLSchema; import oracle.xml.parser.schema.XSDBuilder; import oracle.xml.parser.schema.XSDComplexType; import oracle.xml.parser.schema.XSDConstantValues; import oracle.xml.parser.schema.XSDElement; import oracle.xml.parser.schema.XSDNode; import oracle.xml.parser.schema.XSDValidator; import oracle.xml.parser.v2.XMLDocument; import oracle.xml.parser.v2.XMLElement; import oracle.xml.parser.v2.XMLError; import oracle.xml.parser.v2.XMLNode; import oracle.xml.parser.v2.XMLParseException; import oracle.xml.parser.v2.XSLException; /** * <p><b>Purpose</b>: An implementation of XMLPlatform using Oracle XDK APIs.</p> */ public class XDKPlatform implements XMLPlatform { private Method buildSchemaMethod; public XDKPlatform() { super(); try { Class[] argTypes = { URL.class }; buildSchemaMethod = Helper.getDeclaredMethod(XSDBuilder.class, "build", argTypes); } catch (NoSuchMethodException e) { } } /** * Execute advanced XPath statements that are required for TopLink EIS. * @param contextNode * @param xPath * @param xmlNamespaceResolver * @return * @throws XMLPlatformException */ @Override public Node selectSingleNodeAdvanced(Node contextNode, String xPath, XMLNamespaceResolver xmlNamespaceResolver) throws XMLPlatformException { try { XMLNode xmlNode = (XMLNode)contextNode; XDKNamespaceResolver xdkNamespaceResolver = new XDKNamespaceResolver(xmlNamespaceResolver); return xmlNode.selectSingleNode(xPath, xdkNamespaceResolver); } catch (XSLException e) { throw XMLPlatformException.xmlPlatformInvalidXPath(e); } } /** * Execute advanced XPath statements that are required for TopLink EIS. * @param contextNode the node relative to which the XPath * statement will be executed. * xPath the XPath statement * namespaceResolver used to resolve namespace prefixes * to the corresponding namespace URI * @return the XPath result * @throws XMLPlatformException */ @Override public NodeList selectNodesAdvanced(Node contextNode, String xPath, XMLNamespaceResolver xmlNamespaceResolver) throws XMLPlatformException { try { XMLNode xmlNode = (XMLNode)contextNode; XDKNamespaceResolver xdkNamespaceResolver = new XDKNamespaceResolver(xmlNamespaceResolver); return xmlNode.selectNodes(xPath, xdkNamespaceResolver); } catch (XSLException e) { throw XMLPlatformException.xmlPlatformInvalidXPath(e); } } @Override public Document createDocument() throws XMLPlatformException { return new XMLDocument(); } @Override public Document createDocumentWithPublicIdentifier(String name, String publicIdentifier, String systemIdentifier) throws XMLPlatformException { try { XMLDocument xmlDocument = (XMLDocument)createDocument(); Element rootElement = xmlDocument.createElement(name); xmlDocument.appendChild(rootElement); xmlDocument.setDoctype(name, systemIdentifier, publicIdentifier); return xmlDocument; } catch (Exception e) { throw XMLPlatformException.xmlPlatformCouldNotCreateDocument(e); } } @Override public Document createDocumentWithSystemIdentifier(String name, String systemIdentifier) throws XMLPlatformException { try { if (null == systemIdentifier) { Document document = createDocument(); Element rootElement = document.createElement(name); document.appendChild(rootElement); return document; } XMLDocument xmlDocument = (XMLDocument)createDocument(); Element rootElement = xmlDocument.createElement(name); xmlDocument.appendChild(rootElement); xmlDocument.setDoctype(name, systemIdentifier, null); return xmlDocument; } catch (Exception e) { throw XMLPlatformException.xmlPlatformCouldNotCreateDocument(e); } } @Override public boolean isWhitespaceNode(Text text) { try { String value = text.getNodeValue(); if (null == value) { return false; } else { return value.trim().equals(""); } } catch (NullPointerException e) { // The 9.0.4 XDK will throw a NPE on getNoderValue() if the node value is null. return false; } } @Override public String resolveNamespacePrefix(Node contextNode, String namespacePrefix) throws XMLPlatformException { if (null == namespacePrefix) { if (null == contextNode.getPrefix()) { return contextNode.getNamespaceURI(); } } else if (namespacePrefix.equals(contextNode.getPrefix())) { return contextNode.getNamespaceURI(); } if (contextNode.getNodeType() == Node.ELEMENT_NODE) { Element contextElement = (Element)contextNode; Attr namespaceDeclaration = null; if(namespacePrefix != null) { namespaceDeclaration = contextElement.getAttributeNode("xmlns:" + namespacePrefix); } else { //look for default namespace declaration for null prefix namespaceDeclaration = contextElement.getAttributeNode(XMLConstants.XMLNS); } if (null != namespaceDeclaration) { return namespaceDeclaration.getValue(); } } Node parentNode = contextNode.getParentNode(); if (parentNode != null && parentNode.getNodeType() == Node.ELEMENT_NODE) { return resolveNamespacePrefix(parentNode, namespacePrefix); } return null; } @Override public XMLParser newXMLParser() { return new XDKParser(); } @Override public XMLParser newXMLParser(Map<String, Boolean> parserFeatures) { return new XDKParser(); } @Override public XMLTransformer newXMLTransformer() { return new XDKTransformer(); } /** * Validates a document against an XML schema * * @param document - the document to be validated * @param xmlSchemaURL - the schema URL * @param errorHandler - the error handler * @return true if the document fragment is valid, false otherwise */ @Override public boolean validateDocument(Document document, URL xmlSchemaURL, ErrorHandler errorHandler) throws XMLPlatformException { XMLSchema xmlSchema = null; XSDValidator validator = null; try { Object[] args = { xmlSchemaURL }; xmlSchema = (XMLSchema)buildSchemaMethod.invoke(new XSDBuilder(), args); validator = new XSDValidator(); } catch (Exception e) { throw XMLPlatformException.xmlPlatformErrorResolvingXMLSchema(xmlSchemaURL, e); } // set the schema to be validated against validator.setXMLProperty(XSDValidator.FIXED_SCHEMA, xmlSchema); XMLError xmlErr = new XMLError(); try { validator.setError(xmlErr); } catch (org.xml.sax.SAXException saxex) { throw XMLPlatformException.xmlPlatformValidationException(saxex); } try { ((XMLDocument)document).validateContent(validator, true); } catch (XMLParseException e) { // Ignore this exception, the XMLError will be used to determine if theree // were any errors. } handleErrors(xmlErr, errorHandler); return true; } /** * Validates a document fragment against a complex type or element in the XML schema * * @param elem - the document which contains the document fragment to be validated * @param xmlDescriptor - the path to the complex type or element to be validated against in the schema * @param errorHandler - the error handler * @return true if the document fragment is valid, false otherwise */ @Override public boolean validate(Element elem, XMLDescriptor xmlDescriptor, ErrorHandler errorHandler) throws XMLPlatformException { XMLSchemaReference schemaReference = xmlDescriptor.getSchemaReference(); NamespaceResolver nsResolver = xmlDescriptor.getNamespaceResolver(); // build a schema using the URL in the schema reference, and setup a validator XMLSchema xmlSchema; XSDValidator validator = null; try { Object[] args = { schemaReference.getURL() }; xmlSchema = (XMLSchema)buildSchemaMethod.invoke(new XSDBuilder(), args); validator = new XSDValidator(); } catch (Exception ex) { throw XMLPlatformException.xmlPlatformValidationException(ex); } // set the schema to be validated against validator.setXMLProperty(XSDValidator.FIXED_SCHEMA, xmlSchema); // set the node to be validated against XSDNode xsdNode = getNodeFromSchemaReference(xmlSchema, schemaReference, nsResolver); // if xsdNode is null, the schema context string is empty or the target could not be found if (xsdNode == null) { validator.setXMLProperty(XSDNode.ROOT_NODE, null); } if (schemaReference.getType() == XMLSchemaReference.ELEMENT) { if (xmlDescriptor.getDefaultRootElement() != null) { validator.setXMLProperty(XSDNode.ROOT_NODE, xsdNode); } else { validator.setXMLProperty(XSDNode.ROOT_NODE, ((XSDElement)xsdNode).getType()); } } else { validator.setXMLProperty(XSDNode.ROOT_NODE, xsdNode); } XMLError xmlErr = new XMLError(); try { validator.setError(xmlErr); } catch (org.xml.sax.SAXException saxex) { throw XMLPlatformException.xmlPlatformValidationException(saxex); } try { ((XMLElement)elem).validateContent(validator, true); } catch (XMLParseException e) { // Ignore this exception, the XMLError will be used to determine if theree // were any errors. } handleErrors(xmlErr, errorHandler); return true; } private void handleErrors(XMLError xmlErr, ErrorHandler errorHandler) { try { int numberOfMessages = xmlErr.getNumMessages(); SAXParseException saxParseException; for (int x = 0; x < numberOfMessages; x++) { saxParseException = new SAXParseException(xmlErr.getMessage(x), xmlErr.getPublicId(x), xmlErr.getSystemId(x), xmlErr.getLineNumber(x), xmlErr.getColumnNumber(x), xmlErr.getException(x)); if (null == errorHandler) { throw saxParseException; } errorHandler.fatalError(saxParseException); } } catch (SAXException xmlex) { throw XMLPlatformException.xmlPlatformValidationException(xmlex); } } /** * This convenience method will parse a schema reference and return the node to be * validated against. * * @param xmlSchema - the schema to be used for validation * @param schemaRef - the schema reference object * @return the node to be validated against, null if not found */ private XSDNode getNodeFromSchemaReference(XMLSchema xmlSchema, XMLSchemaReference schemaRef, NamespaceResolver nsResolver) { if (schemaRef == null) { return null; } // schema context should be in the format '/prefix:nodeName/.." // tokenize the schema context to find the node that is to be validated StringTokenizer nodes = new StringTokenizer(schemaRef.getSchemaContext(), "/"); // if no tokens, then invalid schema context if (!(nodes.hasMoreTokens())) { return null; } String namespace = ""; String nodeName = nodes.nextToken(); // look for a prefix StringTokenizer prefixes = new StringTokenizer(nodeName, ":"); if (prefixes.countTokens() > 1) { // look for a namespace namespace = nsResolver.resolveNamespacePrefix(prefixes.nextToken()); if (namespace == null) { namespace = ""; } } // else no prefix nodeName = prefixes.nextToken(); // handle simple/complex type definitions if (schemaRef.getType() == XMLSchemaReference.SIMPLE_TYPE) { return xmlSchema.getType(namespace, nodeName, XSDConstantValues.DATATYPE); } if (schemaRef.getType() == XMLSchemaReference.COMPLEX_TYPE) { return xmlSchema.getType(namespace, nodeName, XSDConstantValues.TYPE); } // handle elements XSDNode node = xmlSchema.getElement(namespace, nodeName); // loop through schema context tokens - 'node' will contain the target when completed while (nodes.hasMoreTokens()) { node = findChildNode((XSDElement)node, nodes.nextToken()); // if node is null, couldn't find child if (node == null) { return null; } } return node; } /** * This convenience method will iterate through a parent element's children and return the * node corresponding to 'nodeName'. * * @param parent - the parent element * @param childName - the node name to be located * @return the child node with name matching 'childName', null if not found */ protected XSDNode findChildNode(XSDElement parent, String childName) { XSDNode[] children; XSDNode node = null; boolean successful = false; // get the parent node's children children = ((XSDComplexType)parent.getType()).getElementSet(); // iterate over child nodes looking for the child for (int i = 0; i < children.length; i++) { node = children[i]; if (node.getName().equals(childName)) { successful = true; break; } } if (successful) { return node; } return null; } @Override public void namespaceQualifyFragment(Element next) { namespaceQualifyFragment(next, new ArrayList<String>()); } //pass list of prefixes declared and encountered private void namespaceQualifyFragment(Element next, List<String> declaredPrefixes) { String elementUri = next.getNamespaceURI(); String elementPrefix = next.getPrefix(); if (elementPrefix != null) { //see if this prefix is already declared if yes - do nothing, if no declare Attr namespaceDeclaration = next.getAttributeNode(XMLConstants.XMLNS +":" + elementPrefix); if ((null == namespaceDeclaration) && !declaredPrefixes.contains(elementPrefix)) { (next).setAttributeNS(XMLConstants.XMLNS_URL, XMLConstants.XMLNS + ":" + elementPrefix, elementUri); declaredPrefixes.add(elementPrefix); } } //check all attributes prefixes and if any of them arent declared add them also. NamedNodeMap attributes = next.getAttributes(); int attributesSize = attributes.getLength(); for (int i = 0; i < attributesSize; i++) { Attr nextAttribute = (Attr)attributes.item(i); String attributePrefix = nextAttribute.getPrefix(); if (attributePrefix != null) { //if attribute is a namespace declaration add to declared list if (XMLConstants.XMLNS_URL.equals(nextAttribute.getNamespaceURI())) { declaredPrefixes.add(nextAttribute.getLocalName()); } else { Attr namespaceDeclaration = next.getAttributeNode(XMLConstants.XMLNS +":" + attributePrefix); if ((null == namespaceDeclaration) && !declaredPrefixes.contains(attributePrefix)) { String attributeUri = nextAttribute.getNamespaceURI(); (next).setAttributeNS(XMLConstants.XMLNS_URL, XMLConstants.XMLNS + ":" + attributePrefix, attributeUri); declaredPrefixes.add(attributePrefix); } //if xsi:type declaration deal with that value if (XMLConstants.SCHEMA_INSTANCE_URL.equals(nextAttribute.getNamespaceURI()) && XMLConstants.SCHEMA_TYPE_ATTRIBUTE.equals(nextAttribute.getLocalName())) { String value = nextAttribute.getValue(); int colonIndex = value.indexOf(':'); if (colonIndex > -1) { String prefix = value.substring(0, colonIndex); namespaceDeclaration = next.getAttributeNode(XMLConstants.XMLNS +":" + prefix); if ((null == namespaceDeclaration) && !declaredPrefixes.contains(prefix)) { String uri = XMLPlatformFactory.getInstance().getXMLPlatform().resolveNamespacePrefix(next, prefix); (next).setAttributeNS(XMLConstants.XMLNS_URL, XMLConstants.XMLNS + ":" + prefix, uri); declaredPrefixes.add(prefix); } } } } } } NodeList children = next.getChildNodes(); int numberOfNodes = children.getLength(); for (int i = 0; i < numberOfNodes; i++) { Node nextNode = children.item(i); if (nextNode.getNodeType() == Node.ELEMENT_NODE) { Element child = (Element)nextNode; namespaceQualifyFragment(child, declaredPrefixes); } } } @Override public void setDisableSecureProcessing(boolean disableSecureProcessing) { //no-op } @Override public boolean isSecureProcessingDisabled() { return false; } }