| /* |
| * Copyright (c) 1998, 2020 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.Enumeration; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.platform.xml.XMLNamespaceResolver; |
| import org.eclipse.persistence.platform.xml.XMLPlatformFactory; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| |
| /** |
| * <p >It is common for an XML document to include one or more namespaces. |
| * TopLink supports this using its NamespaceResolver. The namespace resolver maintains |
| * pairs of namespace prefixes and URIs. TopLink uses these prefixes in conjunction with the |
| * XPath statements you specify on EIS mappings to XML records and XML mappings. |
| * |
| * <p>Although TopLink captures namespace prefixes in the XPath statements for mappings (if applicable), |
| * the input document is not required to use the same namespace prefixes. TopLink will use the namespace |
| * prefixes specified in the mapping when creating new documents. |
| * |
| * <p><em>Code Sample</em><br> |
| * <code> |
| * <b>NamespaceResolver resolver = new NamespaceResolver();<br> |
| * resolver.put( "ns", "urn:namespace-example");<br><br></b> |
| * |
| * XMLDescriptor descriptor = new XMLDescriptor();<br> |
| * descriptor.setJavaClass(Customer.class); <br> |
| * descriptor.setDefaultRootElement("<b>ns</b>:customer");<br> |
| * descriptor.setNamespaceResolver(resolver);<br><br> |
| * |
| * XMLDirectMapping mapping = new XMLDirectMapping();<br> |
| * mapping.setAttributeName("id");<br> |
| * mapping.setXPath("<b>ns</b>:id/text()");<br> |
| * descriptor.addMapping(mapping); |
| * </code> |
| * |
| * @see org.eclipse.persistence.oxm.XMLDescriptor |
| * @see org.eclipse.persistence.eis.EISDescriptor |
| * |
| */ |
| public class NamespaceResolver implements XMLNamespaceResolver { |
| private static final String BASE_PREFIX = "ns"; |
| private static final Vector EMPTY_VECTOR = VectorUtils.emptyVector(); |
| |
| private String defaultNamespaceURI; |
| private NamespaceResolverStorage prefixesToNamespaces; |
| private int prefixCounter; |
| private Node dom; |
| |
| /** |
| * Default constructor, creates a new NamespaceResolver. |
| */ |
| public NamespaceResolver() { |
| super(); |
| } |
| |
| /** |
| * Copy Constructor |
| * @since EclipseLink 2.5.0 |
| */ |
| public NamespaceResolver(NamespaceResolver namespaceResolver) { |
| this.defaultNamespaceURI = namespaceResolver.defaultNamespaceURI; |
| setPrefixesToNamespaces(namespaceResolver.prefixesToNamespaces); |
| |
| this.prefixCounter = namespaceResolver.prefixCounter; |
| this.dom = namespaceResolver.dom; |
| } |
| |
| private void setPrefixesToNamespaces(Map<String, String> input) { |
| if (input == null) { |
| return; |
| } |
| prefixesToNamespaces = new NamespaceResolverStorage(input.size()); |
| prefixesToNamespaces.putAll(input); |
| } |
| |
| public Map<String, String> getPrefixesToNamespaces() { |
| if (null == prefixesToNamespaces) { |
| prefixesToNamespaces = new NamespaceResolverStorage(); |
| } |
| return prefixesToNamespaces; |
| } |
| |
| public boolean hasPrefixesToNamespaces() { |
| return null != prefixesToNamespaces; |
| } |
| |
| /** |
| * Indicates whether given {@code prefix} is assigned to a name-space. |
| * @param prefix name-space prefix |
| * @return {@code true} if {@code prefix} is present in prefix to name-space map |
| * ({@link #getPrefixesToNamespaces()} |
| */ |
| public boolean hasPrefix(String prefix) { |
| return null != prefixesToNamespaces ? prefixesToNamespaces.containsKey(prefix) : false; |
| } |
| |
| public void setDOM(Node dom) { |
| this.dom = dom; |
| } |
| |
| /** |
| * Returns the namespace URI associated with a specified namespace prefix |
| * @param prefix The prefix to lookup a namespace URI for |
| * @return The namespace URI associated with the specified prefix |
| */ |
| @Override |
| public String resolveNamespacePrefix(String prefix) { |
| if (null == prefix || prefix.length() == 0) { |
| return defaultNamespaceURI; |
| } |
| String uri = null; |
| if (null != prefixesToNamespaces) { |
| uri = prefixesToNamespaces.get(prefix); |
| } |
| if (null != uri) { |
| return uri; |
| } else if (javax.xml.XMLConstants.XML_NS_PREFIX.equals(prefix)) { |
| return javax.xml.XMLConstants.XML_NS_URI; |
| } else if (javax.xml.XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) { |
| return javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI; |
| } |
| if (dom != null) { |
| return XMLPlatformFactory.getInstance().getXMLPlatform().resolveNamespacePrefix(dom, prefix); |
| } |
| return null; |
| } |
| |
| /** |
| * Return the namespace prefix associated with a namespace URI. |
| * @param uri A namespace URI. |
| * @return The prefix associated with the namespace URI. |
| */ |
| public String resolveNamespaceURI(String uri) { |
| if (null == uri) { |
| return null; |
| } |
| if (null != prefixesToNamespaces) { |
| for (Entry<String, String> entry : prefixesToNamespaces.entrySet()) { |
| if (uri.equals(entry.getValue())) { |
| return entry.getKey(); |
| } |
| } |
| } |
| if (uri.equalsIgnoreCase(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) { |
| return javax.xml.XMLConstants.XMLNS_ATTRIBUTE; |
| } else if (uri.equalsIgnoreCase(javax.xml.XMLConstants.XML_NS_URI)) { |
| return javax.xml.XMLConstants.XML_NS_PREFIX; |
| } |
| return resolveNamespaceURI(dom, uri); |
| } |
| |
| private String resolveNamespaceURI(Node node, String uri) { |
| if (null == node) { |
| return null; |
| } |
| |
| // If the element is of the same namespace URI, then return the prefix. |
| if (uri.equals(node.getNamespaceURI())) { |
| return node.getPrefix(); |
| } |
| |
| // Check the namespace URI declarations. |
| NamedNodeMap namedNodeMap = node.getAttributes(); |
| if (null != namedNodeMap) { |
| int namedNodeMapSize = namedNodeMap.getLength(); |
| for (int x = 0; x < namedNodeMapSize; x++) { |
| Node attr = namedNodeMap.item(x); |
| if (javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attr.getNamespaceURI())) { |
| if (uri.equals(attr.getNodeValue())) { |
| if (attr.getLocalName() != null && (!(attr.getLocalName() |
| .equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)))) { |
| return attr.getLocalName(); |
| } else { |
| return ""; |
| } |
| } |
| } |
| } |
| } |
| |
| // Repeat the process on the parent node. |
| return resolveNamespaceURI(node.getParentNode(), uri); |
| } |
| |
| /** |
| * Adds a namespace to the collection of namespaces on the NamespaceResolver |
| * @param prefix The prefix for a namespace |
| * @param namespaceURI The namespace URI associated with the specified prefix |
| */ |
| public void put(String prefix, String namespaceURI) { |
| if (null == prefix || 0 == prefix.length()) { |
| defaultNamespaceURI = namespaceURI; |
| } else { |
| //Replace same namespace with given prefix and put them to the end of list. |
| |
| //If you have prefix xmlns:oxm="namespace1" defined on the schema root, |
| //and you (programmatically via namespace resolver) inject prefix xmlns:myns="namespace1" on some element (more deeply) |
| //in the schema, you want this element (in the xml instance) to be prefixed with this myns (because it was defined more closely to the given element). |
| //This can probably be setup in different way (declaratively maybe with some JAXB spec support or MOXy external xml mechanism). |
| //This behavior is preserved, but it is now working independently on the JDK (because we are using HashMap and we are changing the order |
| //of items in the LinkedHashMap so the resolver always find the prefix which is more closely (in xml schema) to the given element. |
| ///@see XMLRootComplexDifferentPrefixTestCases |
| List<String> removedKeys = null; |
| final String cachedJvmValue = namespaceURI.intern(); |
| if (getPrefixesToNamespaces().containsValue(cachedJvmValue)) { |
| removedKeys = new ArrayList<>(); |
| for (Map.Entry<String, String> prefixEntry : prefixesToNamespaces.entrySet()) { |
| if (cachedJvmValue.equals(prefixEntry.getValue())) { |
| removedKeys.add(prefixEntry.getKey()); |
| } |
| } |
| } |
| if (null != removedKeys) { |
| for (String key : removedKeys) { |
| prefixesToNamespaces.remove(key); |
| } |
| } |
| prefixesToNamespaces.put(prefix, cachedJvmValue); |
| if (null != removedKeys) { |
| for (String key : removedKeys) { |
| prefixesToNamespaces.put(key, cachedJvmValue); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the list of prefixes in the NamespaceResolver |
| * @return An Enumeration containing the prefixes in the NamespaceResolver |
| */ |
| public Enumeration getPrefixes() { |
| if (hasPrefixesToNamespaces()) { |
| return new IteratorEnumeration(getPrefixesToNamespaces().keySet().iterator()); |
| } else { |
| return new IteratorEnumeration(null); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns a Vector of of Namespace objects in the current Namespace Resolver |
| * Used for deployment XML |
| * @return A Vector containing the namespace URIs in the namespace resolver |
| */ |
| public Vector getNamespaces() { |
| if (!hasPrefixesToNamespaces()) { |
| return EMPTY_VECTOR; |
| } |
| return prefixesToNamespaces.getNamespaces(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the namespaces on the namespace resolver based on the specified Vector of Namespace objects |
| * Used for deployment XML |
| * @param names A Vector of namespace URIs |
| */ |
| public void setNamespaces(Vector names) { |
| prefixesToNamespaces = new NamespaceResolverStorage(names.size()); |
| prefixesToNamespaces.setNamespaces(names); |
| } |
| |
| public String generatePrefix() { |
| return generatePrefix(getNextPrefix()); |
| } |
| |
| private String getNextPrefix() { |
| return BASE_PREFIX + prefixCounter++; |
| } |
| |
| public String generatePrefix(String defaultPrefix) { |
| String lookup = resolveNamespacePrefix(defaultPrefix); |
| while (lookup != null) { |
| defaultPrefix = getNextPrefix(); |
| lookup = resolveNamespacePrefix(defaultPrefix); |
| } |
| return defaultPrefix; |
| } |
| |
| public void removeNamespace(String prefix) { |
| if (null != prefixesToNamespaces) { |
| prefixesToNamespaces.remove(prefix); |
| } |
| } |
| |
| public void setDefaultNamespaceURI(String namespaceUri) { |
| if (namespaceUri == null) { |
| defaultNamespaceURI = null; |
| } else { |
| defaultNamespaceURI = namespaceUri.intern(); |
| } |
| } |
| |
| public String getDefaultNamespaceURI() { |
| if (null != defaultNamespaceURI) { |
| return defaultNamespaceURI; |
| } else if (dom != null) { |
| return XMLPlatformFactory.getInstance().getXMLPlatform().resolveNamespacePrefix(dom, null); |
| } |
| return null; |
| } |
| |
| private static class IteratorEnumeration implements Enumeration { |
| |
| private Iterator iterator; |
| |
| public IteratorEnumeration(Iterator iterator) { |
| this.iterator = iterator; |
| } |
| |
| @Override |
| public boolean hasMoreElements() { |
| if (null == iterator) { |
| return false; |
| } |
| return iterator.hasNext(); |
| } |
| |
| @Override |
| public Object nextElement() { |
| return iterator.next(); |
| } |
| |
| } |
| } |