/******************************************************************************* | |
* Copyright (c) 1998, 2013 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.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.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 namespaceResolver used to resolve namespace prefixes to the corresponding namespace URI | |
* @return the first node located matching the XPath statement | |
* @throws XMLPlatformException | |
*/ | |
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 namespaceResolver used to resolve namespace prefixes to the corresponding namespace URI | |
* @return a list of nodes matching the XPath statement | |
* @throws XMLPlatformException | |
*/ | |
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<XMLEntry>(); | |
List<XPathFragment> firstFragments = new ArrayList<XPathFragment>(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<XPathFragment>(); | |
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(n); | |
} | |
} 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()); | |
} | |
} |