blob: 590b3d37c3ddb19f1b5baee21a7bfd3fce54dd01 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
*/
/*
* $Id$
*/
package org.apache.jcp.xml.dsig.internal.dom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec;
import javax.xml.crypto.dsig.spec.XPathFilter2ParameterSpec;
import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec;
import javax.xml.crypto.dsig.spec.XPathType;
import javax.xml.crypto.dsig.spec.XSLTTransformParameterSpec;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Useful static DOM utility methods.
*
*/
public final class DOMUtils {
// class cannot be instantiated
private DOMUtils() {}
/**
* Returns the owner document of the specified node.
*
* @param node the node
* @return the owner document
*/
public static Document getOwnerDocument(Node node) {
if (node.getNodeType() == Node.DOCUMENT_NODE) {
return (Document)node;
} else {
return node.getOwnerDocument();
}
}
/**
* Create a QName string from a prefix and local name.
*
* @param prefix The prefix, if any. Can be either null or empty.
* @param localName The local name.
*
* @return The string for the qName, for example, "xsd:element".
*/
public static String getQNameString(String prefix, String localName) {
String qName = prefix == null || prefix.length() == 0
? localName : prefix + ":" + localName;
return qName;
}
/**
* Creates an element in the specified namespace, with the specified tag
* and namespace prefix.
*
* @param doc the owner document
* @param tag the tag
* @param nsURI the namespace URI
* @param prefix the namespace prefix
* @return the newly created element
*/
public static Element createElement(Document doc, String tag,
String nsURI, String prefix)
{
String qName = (prefix == null || prefix.length() == 0)
? tag : prefix + ":" + tag;
return doc.createElementNS(nsURI, qName);
}
/**
* Sets an element's attribute (using DOM level 2) with the
* specified value and namespace prefix.
*
* @param elem the element to set the attribute on
* @param name the name of the attribute
* @param value the attribute value. If null, no attribute is set.
*/
public static void setAttribute(Element elem, String name, String value) {
if (value == null) {
return;
}
elem.setAttributeNS(null, name, value);
}
/**
* Sets an element's attribute (using DOM level 2) with the
* specified value and namespace prefix AND registers the ID value with
* the specified element. This is for resolving same-document
* ID references.
*
* @param elem the element to set the attribute on
* @param name the name of the attribute
* @param value the attribute value. If null, no attribute is set.
*/
public static void setAttributeID(Element elem, String name, String value) {
if (value == null) {
return;
}
elem.setAttributeNS(null, name, value);
elem.setIdAttributeNS(null, name, true);
}
/**
* Returns the first child element of the specified node, or null if there
* is no such element.
*
* @param node the node
* @return the first child element of the specified node, or null if there
* is no such element
* @throws NullPointerException if <code>node == null</code>
*/
public static Element getFirstChildElement(Node node) {
Node child = node.getFirstChild();
while (child != null && child.getNodeType() != Node.ELEMENT_NODE) {
child = child.getNextSibling();
}
return (Element)child;
}
/**
* Returns the first child element of the specified node and checks that
* the local name is equal to {@code localName} and the namespace is equal to
* {@code namespaceURI}
*
* @param node the node
* @return the first child element of the specified node
* @throws NullPointerException if {@code node == null}
* @throws MarshalException if no such element or the local name is not
* equal to {@code localName}
*/
public static Element getFirstChildElement(Node node, String localName, String namespaceURI)
throws MarshalException
{
return verifyElement(getFirstChildElement(node), localName, namespaceURI);
}
private static Element verifyElement(Element elem, String localName, String namespaceURI)
throws MarshalException
{
if (elem == null) {
throw new MarshalException("Missing " + localName + " element");
}
String name = elem.getLocalName();
String namespace = elem.getNamespaceURI();
if (!name.equals(localName) || namespace == null && namespaceURI != null
|| namespace != null && !namespace.equals(namespaceURI)) {
throw new MarshalException("Invalid element name: " +
namespace + ":" + name + ", expected " + namespaceURI + ":" + localName);
}
return elem;
}
/**
* Returns the last child element of the specified node, or null if there
* is no such element.
*
* @param node the node
* @return the last child element of the specified node, or null if there
* is no such element
* @throws NullPointerException if <code>node == null</code>
*/
public static Element getLastChildElement(Node node) {
Node child = node.getLastChild();
while (child != null && child.getNodeType() != Node.ELEMENT_NODE) {
child = child.getPreviousSibling();
}
return (Element)child;
}
/**
* Returns the next sibling element of the specified node, or null if there
* is no such element.
*
* @param node the node
* @return the next sibling element of the specified node, or null if there
* is no such element
* @throws NullPointerException if <code>node == null</code>
*/
public static Element getNextSiblingElement(Node node) {
Node sibling = node.getNextSibling();
while (sibling != null && sibling.getNodeType() != Node.ELEMENT_NODE) {
sibling = sibling.getNextSibling();
}
return (Element)sibling;
}
/**
* Returns the next sibling element of the specified node and checks that
* the local name is equal to {@code localName} and the namespace is equal to
* {@code namespaceURI}
*
* @param node the node
* @return the next sibling element of the specified node
* @throws NullPointerException if {@code node == null}
* @throws MarshalException if no such element or the local name is not
* equal to {@code localName}
*/
public static Element getNextSiblingElement(Node node, String localName, String namespaceURI)
throws MarshalException
{
return verifyElement(getNextSiblingElement(node), localName, namespaceURI);
}
/**
* Returns the attribute value for the attribute with the specified name.
* Returns null if there is no such attribute, or
* the empty string if the attribute value is empty.
*
* <p>This works around a limitation of the DOM
* <code>Element.getAttributeNode</code> method, which does not distinguish
* between an unspecified attribute and an attribute with a value of
* "" (it returns "" for both cases).
*
* @param elem the element containing the attribute
* @param name the name of the attribute
* @return the attribute value (may be null if unspecified)
*/
public static String getAttributeValue(Element elem, String name) {
Attr attr = elem.getAttributeNodeNS(null, name);
return (attr == null) ? null : attr.getValue();
}
/**
* Returns the attribute value for the attribute with the specified name.
* Returns null if there is no such attribute, or
* the empty string if the attribute value is empty.
*
* <p>This works around a limitation of the DOM
* <code>Element.getAttributeNode</code> method, which does not distinguish
* between an unspecified attribute and an attribute with a value of
* "" (it returns "" for both cases).
*
* @param elem the element containing the attribute
* @param name the name of the attribute
* @return the attribute value (may be null if unspecified)
*/
public static <N> String getIdAttributeValue(Element elem, String name) {
Attr attr = elem.getAttributeNodeNS(null, name);
if (attr != null && !attr.isId()) {
elem.setIdAttributeNode(attr, true);
}
return (attr == null) ? null : attr.getValue();
}
/**
* Returns a Set of <code>Node</code>s, backed by the specified
* <code>NodeList</code>.
*
* @param nl the NodeList
* @return a Set of Nodes
*/
public static Set<Node> nodeSet(NodeList nl) {
return new NodeSet(nl);
}
static class NodeSet extends AbstractSet<Node> {
private NodeList nl;
public NodeSet(NodeList nl) {
this.nl = nl;
}
public int size() { return nl.getLength(); }
public Iterator<Node> iterator() {
return new Iterator<Node>() {
private int index;
public void remove() {
throw new UnsupportedOperationException();
}
public Node next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return nl.item(index++);
}
public boolean hasNext() {
return index < nl.getLength();
}
};
}
}
/**
* Returns the prefix associated with the specified namespace URI
*
* @param context contains the namespace map
* @param nsURI the namespace URI
* @return the prefix associated with the specified namespace URI, or
* null if not set
*/
public static String getNSPrefix(XMLCryptoContext context, String nsURI) {
if (context != null) {
return context.getNamespacePrefix
(nsURI, context.getDefaultNamespacePrefix());
} else {
return null;
}
}
/**
* Returns the prefix associated with the XML Signature namespace URI
*
* @param context contains the namespace map
* @return the prefix associated with the specified namespace URI, or
* null if not set
*/
public static String getSignaturePrefix(XMLCryptoContext context) {
return getNSPrefix(context, XMLSignature.XMLNS);
}
/**
* Removes all children nodes from the specified node.
*
* @param node the parent node whose children are to be removed
*/
public static void removeAllChildren(Node node) {
Node firstChild = node.getFirstChild();
while (firstChild != null) {
Node nodeToRemove = firstChild;
firstChild = firstChild.getNextSibling();
node.removeChild(nodeToRemove);
}
}
/**
* Compares 2 nodes for equality. Implementation is not complete.
*/
public static boolean nodesEqual(Node thisNode, Node otherNode) {
if (thisNode == otherNode) {
return true;
}
// FIXME - test content, etc
return thisNode.getNodeType() == otherNode.getNodeType();
}
/**
* Checks if child element has same owner document before
* appending to the parent, and imports it to the parent's document
* if necessary.
*/
public static void appendChild(Node parent, Node child) {
Document ownerDoc = getOwnerDocument(parent);
if (child.getOwnerDocument() != ownerDoc) {
parent.appendChild(ownerDoc.importNode(child, true));
} else {
parent.appendChild(child);
}
}
public static boolean paramsEqual(AlgorithmParameterSpec spec1,
AlgorithmParameterSpec spec2) {
if (spec1 == spec2) {
return true;
}
if (spec1 instanceof XPathFilter2ParameterSpec &&
spec2 instanceof XPathFilter2ParameterSpec) {
return paramsEqual((XPathFilter2ParameterSpec)spec1,
(XPathFilter2ParameterSpec)spec2);
}
if (spec1 instanceof ExcC14NParameterSpec &&
spec2 instanceof ExcC14NParameterSpec) {
return paramsEqual((ExcC14NParameterSpec) spec1,
(ExcC14NParameterSpec)spec2);
}
if (spec1 instanceof XPathFilterParameterSpec &&
spec2 instanceof XPathFilterParameterSpec) {
return paramsEqual((XPathFilterParameterSpec)spec1,
(XPathFilterParameterSpec)spec2);
}
if (spec1 instanceof XSLTTransformParameterSpec &&
spec2 instanceof XSLTTransformParameterSpec) {
return paramsEqual((XSLTTransformParameterSpec)spec1,
(XSLTTransformParameterSpec)spec2);
}
return false;
}
private static boolean paramsEqual(XPathFilter2ParameterSpec spec1,
XPathFilter2ParameterSpec spec2)
{
@SuppressWarnings("unchecked")
List<XPathType> types = spec1.getXPathList();
@SuppressWarnings("unchecked")
List<XPathType> otypes = spec2.getXPathList();
int size = types.size();
if (size != otypes.size()) {
return false;
}
for (int i = 0; i < size; i++) {
XPathType type = types.get(i);
XPathType otype = otypes.get(i);
if (!type.getExpression().equals(otype.getExpression()) ||
!type.getNamespaceMap().equals(otype.getNamespaceMap()) ||
type.getFilter() != otype.getFilter()) {
return false;
}
}
return true;
}
private static boolean paramsEqual(ExcC14NParameterSpec spec1,
ExcC14NParameterSpec spec2)
{
return spec1.getPrefixList().equals(spec2.getPrefixList());
}
private static boolean paramsEqual(XPathFilterParameterSpec spec1,
XPathFilterParameterSpec spec2)
{
return spec1.getXPath().equals(spec2.getXPath()) &&
spec1.getNamespaceMap().equals(spec2.getNamespaceMap());
}
private static boolean paramsEqual(XSLTTransformParameterSpec spec1,
XSLTTransformParameterSpec spec2)
{
XMLStructure ostylesheet = spec2.getStylesheet();
if (!(ostylesheet instanceof javax.xml.crypto.dom.DOMStructure)) {
return false;
}
Node ostylesheetElem =
((javax.xml.crypto.dom.DOMStructure) ostylesheet).getNode();
XMLStructure stylesheet = spec1.getStylesheet();
Node stylesheetElem =
((javax.xml.crypto.dom.DOMStructure) stylesheet).getNode();
return nodesEqual(stylesheetElem, ostylesheetElem);
}
public static boolean isNamespace(Node node)
{
if (Node.ATTRIBUTE_NODE == node.getNodeType()) {
final String namespaceURI = node.getNamespaceURI();
return XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI);
}
return false;
}
}