/******************************************************************************* * Copyright (c) 1998, 2015 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.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 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(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()); } }