blob: c688ec9f7ab98c6cd160771a837137520a182768 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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 v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// 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.
*/
@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
*/
@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;
}
}