blob: 29fb44bc321ba1433c1368c74d4a1815b80350c0 [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.internal.oxm;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy;
import org.eclipse.persistence.oxm.mappings.nullpolicy.XMLNullRepresentationType;
import org.eclipse.persistence.oxm.record.XMLEntry;
import org.eclipse.persistence.oxm.record.XMLRecord;
import org.eclipse.persistence.platform.xml.XMLNamespaceResolver;
import org.eclipse.persistence.platform.xml.XMLNodeList;
import org.eclipse.persistence.platform.xml.XMLPlatform;
import org.eclipse.persistence.platform.xml.XMLPlatformException;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* INTERNAL:
* <p><b>Purpose</b>: Utility class for finding XML nodes using XPath
* expressions.</p>
* @since Oracle TopLink 10.1.3
*/
public class UnmarshalXPathEngine <
XML_FIELD extends Field
> {
private static UnmarshalXPathEngine instance = null;
private XMLPlatform xmlPlatform;
/**
* The default constructor creates a platform instance
*/
public UnmarshalXPathEngine() {
xmlPlatform = XMLPlatformFactory.getInstance().getXMLPlatform();
}
/**
* Return the <code>XPathEngine</code> singleton.
*/
public static UnmarshalXPathEngine getInstance() {
if (instance == null) {
instance = new UnmarshalXPathEngine();
}
return instance;
}
/**
* Execute the XPath statement relative to the context node.
*
* @param contextNode the node relative to which the XPath statement will be executed
* @param xmlField the field containing the XPath statement to be executed
* @param xmlNamespaceResolver used to resolve namespace prefixes to the corresponding namespace URI
* @return the first node located matching the XPath statement
*/
public Object selectSingleNode(Node contextNode, XML_FIELD xmlField, XMLNamespaceResolver xmlNamespaceResolver, boolean checkForXsiNil) throws XMLMarshalException {
try {
if (contextNode == null) {
return null;
}
XPathFragment xPathFragment = xmlField.getXPathFragment();
// allow the platform to handle the advanced case
if (xPathFragment.shouldExecuteSelectNodes()) {
return xmlPlatform.selectSingleNodeAdvanced(contextNode, xmlField.getXPath(), xmlNamespaceResolver);
}
Object result = selectSingleNode(contextNode, xPathFragment, xmlNamespaceResolver, checkForXsiNil);
if(result == XMLRecord.noEntry) {
if(xmlField.getLastXPathFragment().nameIsText() || xmlField.getLastXPathFragment().isAttribute()) {
return result;
} else {
return null;
}
}
return result;
} catch (Exception x) {
throw XMLMarshalException.invalidXPathString(xmlField.getXPath(), x);
}
}
public Object selectSingleNode(Node contextNode, XML_FIELD xmlField, XMLNamespaceResolver xmlNamespaceResolver) throws XMLMarshalException {
return this.selectSingleNode(contextNode, xmlField, xmlNamespaceResolver, false);
}
private Object selectSingleNode(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver, boolean checkForXsiNil) {
Node resultNode = getSingleNode(contextNode, xPathFragment, xmlNamespaceResolver);
if (checkForXsiNil) {
// Check for presence of xsi:nil="true"
String nil = ((Element) contextNode).getAttributeNS(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_NIL_ATTRIBUTE);
if (nil.equals(Constants.BOOLEAN_STRING_TRUE)) {
return XMLRecord.NIL;
}
}
if (resultNode == null) {
if(!xPathFragment.nameIsText()) {
return XMLRecord.noEntry;
}
return null;
}
if(xPathFragment.getNextFragment() == null) {
return resultNode;
}
return selectSingleNode(resultNode, xPathFragment.getNextFragment(), xmlNamespaceResolver, checkForXsiNil);
}
/**
* Execute the XPath statement relative to the context node.
*
* @param contextNode the node relative to which the XPath statement will be executed
* @param xmlField the field containing the XPath statement to be executed
* @param xmlNamespaceResolver used to resolve namespace prefixes to the corresponding namespace URI
* @return a list of nodes matching the XPath statement
*/
public NodeList selectNodes(Node contextNode, XML_FIELD xmlField, XMLNamespaceResolver xmlNamespaceResolver) throws XMLMarshalException {
return this.selectNodes(contextNode, xmlField, xmlNamespaceResolver, null);
}
public NodeList selectNodes(Node contextNode, XML_FIELD xmlField, XMLNamespaceResolver xmlNamespaceResolver, AbstractNullPolicy nullPolicy) throws XMLMarshalException {
return selectNodes(contextNode, xmlField, xmlNamespaceResolver, nullPolicy, false);
}
public NodeList selectNodes(Node contextNode, XML_FIELD xmlField, XMLNamespaceResolver xmlNamespaceResolver, AbstractNullPolicy nullPolicy, boolean omitText) throws XMLMarshalException {
return selectNodes(contextNode, xmlField, xmlNamespaceResolver, nullPolicy, omitText, true);
}
public NodeList selectNodes(Node contextNode, XML_FIELD xmlField, XMLNamespaceResolver xmlNamespaceResolver, AbstractNullPolicy nullPolicy, boolean omitText, boolean concatinateTextNodes) {
try {
if (contextNode == null) {
return null;
}
XPathFragment xPathFragment = xmlField.getXPathFragment();
// allow the platform to handle the advanced case
if (xPathFragment.shouldExecuteSelectNodes()) {
return xmlPlatform.selectNodesAdvanced(contextNode, xmlField.getXPath(), xmlNamespaceResolver);
}
return selectNodes(contextNode, xPathFragment, xmlNamespaceResolver, nullPolicy, omitText, concatinateTextNodes);
} catch (Exception x) {
throw XMLMarshalException.invalidXPathString(xmlField.getXPath(), x);
}
}
public List<XMLEntry> selectNodes(Node contextNode, List<XML_FIELD> xmlFields, XMLNamespaceResolver xmlNamespaceResolver) throws XMLMarshalException {
List<XMLEntry> nodes = new ArrayList<>();
List<XPathFragment> firstFragments = new ArrayList<>(xmlFields.size());
for(XML_FIELD nextField:xmlFields) {
firstFragments.add(nextField.getXPathFragment());
}
selectNodes(contextNode, firstFragments, xmlNamespaceResolver, nodes);
return nodes;
}
private void selectNodes(Node contextNode, List<XPathFragment> xpathFragments, XMLNamespaceResolver xmlNamespaceResolver, List<XMLEntry> entries) {
NodeList childNodes = contextNode.getChildNodes();
for(int i = 0, size = childNodes.getLength(); i < size; i++) {
Node nextChild = childNodes.item(i);
List<XPathFragment> matchingFragments = new ArrayList<>();
for(XPathFragment<XML_FIELD> nextFragment:xpathFragments) {
if((nextChild.getNodeType() == Node.TEXT_NODE || nextChild.getNodeType() == Node.CDATA_SECTION_NODE) && nextFragment.nameIsText()) {
addXMLEntry(nextChild, nextFragment.getXMLField(), entries);
} else if(nextChild.getNodeType() == Node.ATTRIBUTE_NODE && nextFragment.isAttribute()) {
String attributeNamespaceURI = null;
if (nextFragment.hasNamespace()) {
attributeNamespaceURI = xmlNamespaceResolver.resolveNamespacePrefix(nextFragment.getPrefix());
}
if(sameName(nextChild, nextFragment.getLocalName()) && sameNamespaceURI(nextChild, attributeNamespaceURI)) {
addXMLEntry(nextChild, nextFragment.getXMLField(), entries);
}
} else if(nextChild.getNodeType() == Node.ELEMENT_NODE) {
String elementNamespaceURI = null;
if(nextFragment.hasNamespace()) {
elementNamespaceURI = xmlNamespaceResolver.resolveNamespacePrefix(nextFragment.getPrefix());
}
if(sameName(nextChild, nextFragment.getLocalName()) && sameNamespaceURI(nextChild, elementNamespaceURI)) {
if(nextFragment.getNextFragment() == null) {
addXMLEntry(nextChild, nextFragment.getXMLField(), entries);
} else {
matchingFragments.add(nextFragment.getNextFragment());
}
}
}
}
if(matchingFragments.size() > 0) {
selectNodes(nextChild, matchingFragments, xmlNamespaceResolver, entries);
}
}
}
private void addXMLEntry(Node entryNode, XML_FIELD xmlField, List<XMLEntry> entries) {
XMLEntry entry = new XMLEntry();
entry.setValue(entryNode);
entry.setXMLField(xmlField);
entries.add(entry);
}
private NodeList selectNodes(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver, AbstractNullPolicy nullPolicy, boolean omitText, boolean concatText) {
NodeList resultNodes = getNodes(contextNode, xPathFragment, xmlNamespaceResolver, nullPolicy, concatText);
if (xPathFragment.getNextFragment() != null && !(omitText && xPathFragment.getNextFragment().nameIsText())) {
Node resultNode;
XMLNodeList result = new XMLNodeList();
int numberOfResultNodes = resultNodes.getLength();
for (int x = 0; x < numberOfResultNodes; x++) {
resultNode = resultNodes.item(x);
result.addAll(selectNodes(resultNode, xPathFragment.getNextFragment(), xmlNamespaceResolver, nullPolicy, omitText, concatText));
}
return result;
}
return resultNodes;
}
private Node getSingleNode(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver) {
if (xPathFragment.isAttribute()) {
return selectSingleAttribute(contextNode, xPathFragment, xmlNamespaceResolver);
} else if (xPathFragment.nameIsText()) {
return selectSingleText(contextNode);
} else if (xPathFragment.isSelfFragment()) {
return contextNode;
}
if (xPathFragment.containsIndex()) {
return selectSingleElement(contextNode, xPathFragment, xmlNamespaceResolver, xPathFragment.getIndexValue());
}
return selectSingleElement(contextNode, xPathFragment, xmlNamespaceResolver);
}
private NodeList getNodes(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver, AbstractNullPolicy nullPolicy, boolean concatText) {
if (xPathFragment.isAttribute()) {
return selectAttributeNodes(contextNode, xPathFragment, xmlNamespaceResolver);
} else if (xPathFragment.nameIsText()) {
return selectTextNodes(contextNode, nullPolicy, concatText);
} else if (xPathFragment.isSelfFragment()) {
XMLNodeList xmlNodeList = new XMLNodeList(1);
xmlNodeList.add(contextNode);
return xmlNodeList;
}
if (xPathFragment.containsIndex()) {
return selectElementNodes(contextNode, xPathFragment, xmlNamespaceResolver, xPathFragment.getIndexValue());
}
return selectElementNodes(contextNode, xPathFragment, xmlNamespaceResolver);
}
private Node selectSingleAttribute(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver) {
if (xPathFragment.hasNamespace()) {
if(Node.ELEMENT_NODE == contextNode.getNodeType()) {
String attributeNamespaceURI = xmlNamespaceResolver.resolveNamespacePrefix(xPathFragment.getPrefix());
return contextNode.getAttributes().getNamedItemNS(attributeNamespaceURI, xPathFragment.getLocalName());
} else {
return null;
}
} else {
if(Node.ELEMENT_NODE == contextNode.getNodeType()) {
return contextNode.getAttributes().getNamedItem(xPathFragment.getShortName());
} else {
return null;
}
}
}
private NodeList selectAttributeNodes(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver) {
XMLNodeList xmlNodeList = new XMLNodeList();
Node child = selectSingleAttribute(contextNode, xPathFragment, xmlNamespaceResolver);
if (null != child) {
xmlNodeList.add(child);
}
return xmlNodeList;
}
private Node selectSingleElement(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver) {
Node child = contextNode.getFirstChild();
while (null != child) {
String elementNamespaceURI = null;
if(xmlNamespaceResolver != null) {
elementNamespaceURI = xmlNamespaceResolver.resolveNamespacePrefix(xPathFragment.getPrefix());
}
if ((child.getNodeType() == Node.ELEMENT_NODE) && sameName(child, xPathFragment.getLocalName()) && sameNamespaceURI(child, elementNamespaceURI)) {
return child;
}
child = child.getNextSibling();
}
return null;
}
public NodeList selectElementNodes(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver) {
XMLNodeList xmlNodeList = new XMLNodeList();
Node child = contextNode.getFirstChild();
while (null != child) {
String elementNamespaceURI = null;
if(xmlNamespaceResolver != null) {
elementNamespaceURI = xmlNamespaceResolver.resolveNamespacePrefix(xPathFragment.getPrefix());
}
if ((child.getNodeType() == Node.ELEMENT_NODE) && sameName(child, xPathFragment.getLocalName()) && sameNamespaceURI(child, elementNamespaceURI)) {
XPathPredicate predicate = xPathFragment.getPredicate();
if(predicate != null) {
XPathFragment predicateFragment = predicate.getXPathFragment();
if(predicateFragment.isAttribute() && child.getAttributes() != null) {
Attr attr = (Attr)child.getAttributes().getNamedItemNS(predicateFragment.getNamespaceURI(), predicateFragment.getLocalName());
if(attr != null) {
String attribute = attr.getValue();
if(xPathFragment.getPredicate().getValue().equals(attribute)) {
xmlNodeList.add(child);
}
}
}
} else {
xmlNodeList.add(child);
}
}
child = child.getNextSibling();
}
return xmlNodeList;
}
private Node selectSingleElement(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver, int position) {
Node child = contextNode.getFirstChild();
while (null != child) {
String elementNamespaceURI = null;
if(xmlNamespaceResolver != null) {
elementNamespaceURI = xmlNamespaceResolver.resolveNamespacePrefix(xPathFragment.getPrefix());
}
if ((child.getNodeType() == Node.ELEMENT_NODE) && sameName(child, xPathFragment.getShortName()) && sameNamespaceURI(child, elementNamespaceURI)) {
if (0 == --position) {
return child;
}
}
child = child.getNextSibling();
}
return null;
}
private NodeList selectElementNodes(Node contextNode, XPathFragment xPathFragment, XMLNamespaceResolver xmlNamespaceResolver, int position) {
XMLNodeList xmlNodeList = new XMLNodeList();
Node child = selectSingleElement(contextNode, xPathFragment, xmlNamespaceResolver, position);
if (null != child) {
xmlNodeList.add(child);
}
return xmlNodeList;
}
private Node selectSingleText(Node contextNode) {
NodeList childrenNodes = contextNode.getChildNodes();
int numberOfNodes = childrenNodes.getLength();
if (numberOfNodes == 0) {
return null;
}
if (numberOfNodes == 1) {
Node child = childrenNodes.item(0);
if (child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE) {
return child;
}
return null;
}
String returnVal = null;
for (int i = 0; i < numberOfNodes; i++) {
Node next = childrenNodes.item(i);
if (next.getNodeType() == Node.TEXT_NODE || next.getNodeType() == Node.CDATA_SECTION_NODE) {
String val = next.getNodeValue();
if (val != null) {
if (returnVal == null) {
returnVal = "";
}
if(next.getNodeType() == Node.CDATA_SECTION_NODE) {
val = val.trim();
}
returnVal += val;
}
}
}
//bug#4515249 a new text node was being created when null should have been returned
//case where contextNode had several children but no Text children
if (returnVal != null) {
return contextNode.getOwnerDocument().createTextNode(returnVal);
}
return null;
}
private NodeList selectTextNodes(Node contextNode, AbstractNullPolicy nullPolicy, boolean concatText) {
if(!concatText) {
return selectAllText(contextNode);
}
Node n = selectSingleText(contextNode);
XMLNodeList xmlNodeList = new XMLNodeList();
if (n == null && nullPolicy != null) {
if (nullPolicy.valueIsNull((Element) contextNode)) {
if (nullPolicy.getMarshalNullRepresentation() != XMLNullRepresentationType.ABSENT_NODE) {
xmlNodeList.add(null);
}
} else {
xmlNodeList.add(contextNode.getOwnerDocument().createTextNode(Constants.EMPTY_STRING));
}
} else {
if (nullPolicy != null && nullPolicy.isNullRepresentedByXsiNil() && nullPolicy.valueIsNull((Element) contextNode)) {
xmlNodeList.add(null);
}else if (n != null) {
xmlNodeList.add(n);
}
}
return xmlNodeList;
}
private NodeList selectAllText(Node contextNode) {
XMLNodeList nodes = new XMLNodeList();
NodeList children = contextNode.getChildNodes();
for(int i = 0; i < children.getLength(); i++) {
Node next = children.item(i);
if (next.getNodeType() == Node.TEXT_NODE || next.getNodeType() == Node.CDATA_SECTION_NODE) {
nodes.add(next);
}
}
return nodes;
}
private boolean sameNamespaceURI(Node node, String namespaceURI) {
// HANDLE THE NULL CASE
String nodeNamespaceURI = node.getNamespaceURI();
if (nodeNamespaceURI == namespaceURI) {
return true;
}
if ((nodeNamespaceURI == null) && namespaceURI.length() == 0) {
return true;
}
if ((namespaceURI == null) && nodeNamespaceURI.length() == 0) {
return true;
}
// HANDLE THE NON-NULL CASE
return (null != nodeNamespaceURI) && nodeNamespaceURI.equals(namespaceURI);
}
private boolean sameName(Node node, String name) {
return name.equals(node.getLocalName()) || name.equals(node.getNodeName());
}
}