| /* |
| * 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.record; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession; |
| import org.eclipse.persistence.internal.oxm.Constants; |
| import org.eclipse.persistence.internal.oxm.NamespaceResolver; |
| import org.eclipse.persistence.internal.oxm.Unmarshaller; |
| import org.eclipse.persistence.internal.oxm.mappings.Login; |
| import org.eclipse.persistence.internal.oxm.mappings.Mapping; |
| import org.eclipse.persistence.internal.oxm.record.namespaces.StackUnmarshalNamespaceResolver; |
| import org.eclipse.persistence.oxm.documentpreservation.DocumentPreservationPolicy; |
| |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.CDATASection; |
| 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.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.ext.Locator2; |
| |
| /** |
| * INTERNAL: |
| * <p><b>Purpose:</b> An implementation of XMLReader for parsing DOM Nodes into SAX events. |
| * <p><b>Responsibilities:</b><ul> |
| * <li>Walk the DOM tree and report sax events to the provided content handler</li> |
| * <li>Report lexical events to the lexical handler if it's provided</li> |
| * <li>Listen for callbacks from the Mapping-Level framework to handle caching nodes for document preservation</li> |
| * </ul> |
| * |
| */ |
| public class DOMReader extends XMLReaderAdapter { |
| |
| private Node currentNode; |
| private DocumentPreservationPolicy docPresPolicy; |
| |
| public DOMReader() { |
| super(); |
| } |
| |
| public DOMReader(Unmarshaller xmlUnmarshaller) { |
| super(xmlUnmarshaller); |
| } |
| |
| @Override |
| public void parse(InputSource input) throws SAXException { |
| if(input instanceof DOMInputSource) { |
| Node node = ((DOMInputSource) input).getNode(); |
| if(contentHandler != null && contentHandler.getClass() == SAXUnmarshallerHandler.class){ |
| ((SAXUnmarshallerHandler)contentHandler).setUnmarshalNamespaceResolver(new StackUnmarshalNamespaceResolver()); |
| } |
| parse(node); |
| } |
| } |
| |
| public void parse (Node node, String newURI, String newName) throws SAXException { |
| if(null == contentHandler) { |
| return; |
| } |
| Element rootNode = null; |
| if(node.getNodeType() == Node.DOCUMENT_NODE) { |
| rootNode = ((Document)node).getDocumentElement(); |
| } else { |
| rootNode = (Element)node; |
| } |
| if(rootNode == null) { |
| return; |
| } |
| processParentNamespaces(rootNode); |
| startDocument(); |
| setupLocator(rootNode.getOwnerDocument()); |
| |
| reportElementEvents(rootNode, newURI, newName); |
| |
| |
| endDocument(); |
| } |
| |
| public void parse (Node node) throws SAXException { |
| parse(node, null, null); |
| } |
| |
| /** |
| * Process namespace declarations on parent elements if not the root. |
| * For each parent node from current to root push each onto a stack, |
| * then pop each off, calling startPrefixMapping for each XMLNS |
| * attribute. Using a stack ensures that the parent nodes are |
| * processed top down. |
| * |
| */ |
| protected void processParentNamespaces(Element element) throws SAXException { |
| Node parent = element.getParentNode(); |
| // If we're already at the root, do nothing |
| if (parent != null && parent.getNodeType() == Node.DOCUMENT_NODE) { |
| return; |
| } |
| // Add each parent node up to root to the stack |
| List<Node> parentElements = new ArrayList<>(); |
| while (parent != null && parent.getNodeType() != Node.DOCUMENT_NODE) { |
| parentElements.add(parent); |
| parent = parent.getParentNode(); |
| } |
| // Pop off each node and call startPrefixMapping for each XMLNS attribute |
| for (Iterator<Node> stackIt = parentElements.iterator(); stackIt.hasNext(); ) { |
| NamedNodeMap attrs = parentElements.remove(parentElements.size() - 1).getAttributes(); |
| if (attrs != null) { |
| for (int i=0, length = attrs.getLength(); i < length; i++) { |
| Attr next = (Attr)attrs.item(i); |
| String attrPrefix = next.getPrefix(); |
| if (attrPrefix != null && attrPrefix.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) { |
| contentHandler.startPrefixMapping(next.getLocalName(), next.getValue()); |
| } |
| } |
| } |
| } |
| } |
| protected void reportElementEvents(Element elem) throws SAXException { |
| reportElementEvents(elem, null, null); |
| } |
| protected void reportElementEvents(Element elem, String newUri, String newName) throws SAXException { |
| this.currentNode = elem; |
| IndexedAttributeList attributes = buildAttributeList(elem); |
| String namespaceUri = null; |
| String qname = null; |
| String lname = null; |
| |
| if(newName == null){ |
| // Handle null local name |
| lname = elem.getLocalName(); |
| if (lname == null) { |
| // If local name is null, use the node name |
| lname = elem.getNodeName(); |
| qname = lname; |
| handlePrefixedAttribute(elem); |
| } else { |
| qname = getQName(elem); |
| } |
| namespaceUri = elem.getNamespaceURI(); |
| if(namespaceUri == null) { |
| namespaceUri = ""; |
| } |
| } else { |
| namespaceUri = newUri; |
| lname = newName; |
| qname = newName; |
| if(namespaceUri != null && isNamespaceAware()){ |
| NamespaceResolver tmpNR = new NamespaceResolver(); |
| tmpNR.setDOM(elem); |
| |
| String prefix = tmpNR.resolveNamespaceURI(namespaceUri); |
| if(prefix == null || prefix.length() == 0){ |
| String defaultNamespace = elem.getAttributeNS(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE); |
| |
| if(defaultNamespace == null){ |
| prefix = tmpNR.generatePrefix(); |
| contentHandler.startPrefixMapping(prefix, namespaceUri); |
| }else if(defaultNamespace != namespaceUri){ |
| prefix = tmpNR.generatePrefix(); |
| contentHandler.startPrefixMapping(prefix, namespaceUri); |
| }else{ |
| prefix = Constants.EMPTY_STRING; |
| } |
| } |
| |
| if(prefix != null && prefix.length() >0){ |
| qname = prefix + Constants.COLON + qname; |
| } |
| } |
| |
| } |
| |
| |
| |
| contentHandler.startElement(namespaceUri, lname, qname, attributes); |
| |
| handleChildNodes(elem.getChildNodes()); |
| contentHandler.endElement(namespaceUri, lname, qname); |
| endPrefixMappings(elem); |
| } |
| |
| protected IndexedAttributeList buildAttributeList(Element elem) throws SAXException { |
| IndexedAttributeList attributes = new IndexedAttributeList(); |
| NamedNodeMap attrs = elem.getAttributes(); |
| for (int i = 0, length = attrs.getLength(); i < length; i++) { |
| Attr next = (Attr)attrs.item(i); |
| String attrPrefix = next.getPrefix(); |
| if(attrPrefix != null && attrPrefix.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) { |
| contentHandler.startPrefixMapping(next.getLocalName(), next.getValue()); |
| // Handle XMLNS prefixed attributes |
| handleNewNamespaceDeclaration(elem, next.getLocalName(), next.getValue()); |
| |
| } else if(attrPrefix == null) { |
| String name = next.getLocalName(); |
| if(name == null) { |
| name = next.getNodeName(); |
| } |
| if(name != null && name.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) { |
| contentHandler.startPrefixMapping(Constants.EMPTY_STRING, next.getValue()); |
| handleNewNamespaceDeclaration(elem, Constants.EMPTY_STRING, next.getValue()); |
| } |
| } |
| if(next.getNamespaceURI() != null && next.getNamespaceURI().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI) && next.getLocalName().equals("type")) { |
| handleXsiTypeAttribute(next); |
| } |
| attributes.addAttribute(next); |
| } |
| return attributes; |
| } |
| |
| protected void endPrefixMappings(Element elem) throws SAXException { |
| NamedNodeMap attrs = elem.getAttributes(); |
| for(int i = 0, numOfAtts = attrs.getLength(); i < numOfAtts; i++) { |
| Attr next = (Attr)attrs.item(i); |
| String attrPrefix = next.getPrefix(); |
| if (attrPrefix != null && attrPrefix.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) { |
| contentHandler.endPrefixMapping(next.getLocalName()); |
| } else if(attrPrefix == null) { |
| String name = next.getLocalName(); |
| if(name == null) { |
| name = next.getNodeName(); |
| } |
| if(javax.xml.XMLConstants.XMLNS_ATTRIBUTE.equals(name)) { |
| contentHandler.endPrefixMapping(Constants.EMPTY_STRING); |
| } |
| } |
| } |
| } |
| |
| protected String getQName(Element elem) throws SAXException { |
| handlePrefixedAttribute(elem); |
| String prefix = elem.getPrefix(); |
| if (prefix != null && prefix.length() > 0) { |
| String qname = prefix + Constants.COLON + elem.getLocalName(); |
| return qname; |
| } else { |
| return elem.getLocalName(); |
| } |
| } |
| |
| protected void handleNewNamespaceDeclaration(Element elem, String emptyString, String value) { |
| // DO NOTHING |
| } |
| |
| protected void handleXsiTypeAttribute(Attr attr) throws SAXException { |
| |
| } |
| |
| /** |
| * Handle prefixed attribute - may need to declare the namespace |
| * URI locally. |
| * |
| */ |
| protected void handlePrefixedAttribute(Element elem) throws SAXException { |
| // DO NOTHING |
| } |
| |
| protected void handleChildNodes(NodeList children) throws SAXException { |
| Node nextChild = null; |
| if(children.getLength() > 0) { |
| nextChild = children.item(0); |
| } |
| while(nextChild != null) { |
| if(nextChild.getNodeType() == Node.TEXT_NODE) { |
| char[] value = nextChild.getNodeValue().toCharArray(); |
| contentHandler.characters(value, 0, value.length); |
| } else if(nextChild.getNodeType() == Node.COMMENT_NODE) { |
| char[] value = nextChild.getNodeValue().toCharArray(); |
| if (lexicalHandler != null) { |
| lexicalHandler.comment(value, 0, value.length); |
| } |
| } else if(nextChild.getNodeType() == Node.ELEMENT_NODE) { |
| Element childElement = (Element)nextChild; |
| reportElementEvents(childElement); |
| } else if(nextChild.getNodeType() == Node.CDATA_SECTION_NODE) { |
| if(lexicalHandler != null) { |
| lexicalHandler.startCDATA(); |
| } |
| char[] value = ((CDATASection)nextChild).getData().toCharArray(); |
| contentHandler.characters(value, 0, value.length); |
| if(lexicalHandler != null) { |
| lexicalHandler.endCDATA(); |
| } |
| } |
| nextChild = nextChild.getNextSibling(); |
| } |
| } |
| |
| /** |
| * Trigger an endDocument event on the contenthandler. |
| */ |
| protected void endDocument() throws SAXException { |
| contentHandler.endDocument(); |
| } |
| |
| /** |
| * Trigger a startDocument event on the contenthandler. |
| */ |
| protected void startDocument() throws SAXException { |
| contentHandler.startDocument(); |
| } |
| |
| /** |
| * An EclipseLink specific callback into the Reader. This allows Objects to be |
| * associated with the XML Nodes they came from. |
| */ |
| @Override |
| public void newObjectEvent(Object object, Object parent, Mapping selfRecordMapping) { |
| docPresPolicy.addObjectToCache(object, currentNode, selfRecordMapping); |
| } |
| |
| @Override |
| public Object getCurrentObject(CoreAbstractSession session, Mapping selfRecordMapping) { |
| //if session == null then this is a marshal of a non-root |
| //if docPres policy is null, then we never unmarshalled anything, and can |
| //safely return null; |
| if(session == null && docPresPolicy == null) { |
| return null; |
| } |
| if(docPresPolicy == null) { |
| Login login = (Login)session.getDatasourceLogin(); |
| docPresPolicy = login.getDocumentPreservationPolicy(); |
| } |
| return docPresPolicy.getObjectForNode(currentNode, selfRecordMapping); |
| } |
| |
| public DocumentPreservationPolicy getDocPresPolicy() { |
| return docPresPolicy; |
| } |
| |
| public void setDocPresPolicy(DocumentPreservationPolicy policy) { |
| docPresPolicy = policy; |
| } |
| |
| protected void setupLocator(Document doc) { |
| LocatorImpl locator = new LocatorImpl(); |
| try { |
| locator.setEncoding(doc.getXmlEncoding()); |
| locator.setXMLVersion(doc.getXmlVersion()); |
| } catch(Exception ex) { |
| //if unable to invoke these methods, just return and don't invoke |
| return; |
| } |
| this.contentHandler.setDocumentLocator(locator); |
| } |
| |
| // Made static final for performance reasons. |
| /** |
| * Implementation of Attributes - used to pass along a given node's attributes |
| * to the startElement method of the reader's content handler. |
| */ |
| protected static final class IndexedAttributeList implements org.xml.sax.Attributes { |
| |
| private List<Attr> attrs; |
| |
| public IndexedAttributeList() { |
| attrs = new ArrayList(); |
| } |
| |
| public void addAttribute(Attr attribute) { |
| attrs.add(attribute); |
| } |
| |
| @Override |
| public String getQName(int index) { |
| try { |
| Attr item = attrs.get(index); |
| if (item.getName() != null) { |
| return item.getName(); |
| } |
| return Constants.EMPTY_STRING; |
| } catch (IndexOutOfBoundsException iobe) { |
| return null; |
| } |
| } |
| |
| @Override |
| public String getType(String namespaceUri, String localName) { |
| return Constants.CDATA; |
| } |
| |
| @Override |
| public String getType(int index) { |
| return Constants.CDATA; |
| } |
| |
| @Override |
| public String getType(String qname) { |
| return Constants.CDATA; |
| } |
| |
| @Override |
| public int getIndex(String qname) { |
| for (int i=0, size = attrs.size(); i<size; i++) { |
| if (attrs.get(i).getName().equals(qname)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| @Override |
| public int getIndex(String uri, String localName) { |
| for (int i=0, size = attrs.size(); i<size; i++) { |
| Attr item = attrs.get(i); |
| try { |
| if (item.getNamespaceURI().equals(uri) && item.getLocalName().equals(localName)) { |
| return i; |
| } |
| } catch (Exception x) {} |
| } |
| return -1; |
| } |
| |
| @Override |
| public int getLength() { |
| return attrs.size(); |
| } |
| |
| @Override |
| public String getLocalName(int index) { |
| try { |
| Attr item = attrs.get(index); |
| if (item.getLocalName() != null) { |
| return item.getLocalName(); |
| } |
| return item.getName(); |
| } catch (IndexOutOfBoundsException iobe) { |
| return null; |
| } |
| } |
| |
| @Override |
| public String getURI(int index) { |
| String uri = attrs.get(index).getNamespaceURI(); |
| if(uri == null) { |
| uri = Constants.EMPTY_STRING; |
| } |
| return uri; |
| } |
| |
| @Override |
| public String getValue(int index) { |
| return (attrs.get(index)).getValue(); |
| } |
| |
| @Override |
| public String getValue(String qname) { |
| for (int i=0, size = attrs.size(); i<size; i++) { |
| Attr item = attrs.get(i); |
| if (item.getName().equals(qname)) { |
| return item.getValue(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public String getValue(String uri, String localName) { |
| for (int i=0, size = attrs.size(); i<size; i++) { |
| Attr item = attrs.get(i); |
| if (item != null) { |
| String itemNS = item.getNamespaceURI(); |
| // Need to handle null/empty URI |
| if (item.getNamespaceURI() == null) { |
| itemNS = Constants.EMPTY_STRING; |
| } |
| |
| String itemName = item.getLocalName(); |
| if(itemName == null){ |
| itemName = item.getNodeName(); |
| } |
| if ((itemNS.equals(uri)) && (itemName != null && itemName.equals(localName))) { |
| return item.getValue(); |
| } |
| } |
| } |
| return null; |
| } |
| } |
| |
| // Made static final for performance reasons. |
| protected static final class LocatorImpl implements Locator2 { |
| |
| private String encoding; |
| private String version; |
| |
| public LocatorImpl() { |
| encoding = "UTF-8"; |
| version = "1.0"; |
| } |
| |
| @Override |
| public String getEncoding() { |
| return encoding; |
| } |
| |
| @Override |
| public int getColumnNumber() { |
| //not supported here |
| return 0; |
| } |
| |
| @Override |
| public String getSystemId() { |
| return Constants.EMPTY_STRING; |
| } |
| |
| @Override |
| public String getPublicId() { |
| return Constants.EMPTY_STRING; |
| } |
| |
| @Override |
| public String getXMLVersion() { |
| return version; |
| } |
| |
| @Override |
| public int getLineNumber() { |
| //not supported |
| return 0; |
| } |
| |
| protected void setEncoding(String enc) { |
| this.encoding = enc; |
| } |
| |
| protected void setXMLVersion(String xmlVersion) { |
| this.version = xmlVersion; |
| } |
| |
| } |
| |
| } |