/******************************************************************************* * 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.record; import java.lang.reflect.Method; 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.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.oxm.documentpreservation.DocumentPreservationPolicy; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; 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.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. * * @param element */ 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<Node>(); 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 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 = ((Text)nextChild).getNodeValue().toCharArray(); contentHandler.characters(value, 0, value.length); } else if(nextChild.getNodeType() == Node.COMMENT_NODE) { char[] value = ((Comment)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 { Method getEncoding = PrivilegedAccessHelper.getMethod(doc.getClass(), "getXmlEncoding", new Class[]{}, true); Method getVersion = PrivilegedAccessHelper.getMethod(doc.getClass(), "getXmlVersion", new Class[]{}, true); String encoding = (String)PrivilegedAccessHelper.invokeMethod(getEncoding, doc, new Object[]{}); String version = (String)PrivilegedAccessHelper.invokeMethod(getVersion, doc, new Object[]{}); locator.setEncoding(encoding); locator.setXMLVersion(version); } 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); } 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; } } public String getType(String namespaceUri, String localName) { return Constants.CDATA; } public String getType(int index) { return Constants.CDATA; } public String getType(String qname) { return Constants.CDATA; } 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; } 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; } public int getLength() { return attrs.size(); } 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; } } public String getURI(int index) { String uri = attrs.get(index).getNamespaceURI(); if(uri == null) { uri = Constants.EMPTY_STRING; } return uri; } public String getValue(int index) { return (attrs.get(index)).getValue(); } 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; } 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"; } public String getEncoding() { return encoding; } public int getColumnNumber() { //not supported here return 0; } public String getSystemId() { return Constants.EMPTY_STRING; } public String getPublicId() { return Constants.EMPTY_STRING; } public String getXMLVersion() { return version; } public int getLineNumber() { //not supported return 0; } protected void setEncoding(String enc) { this.encoding = enc; } protected void setXMLVersion(String xmlVersion) { this.version = xmlVersion; } } }