1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/**
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
21 * under the License.
22 */
23/*
24 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
25 */
26/*
27 * $Id: DOMUtils.java 1333415 2012-05-03 12:03:51Z coheigea $
28 */
29package org.jcp.xml.dsig.internal.dom;
30
31import java.util.*;
32import java.security.spec.AlgorithmParameterSpec;
33import org.w3c.dom.Attr;
34import org.w3c.dom.Document;
35import org.w3c.dom.Element;
36import org.w3c.dom.Node;
37import org.w3c.dom.NodeList;
38import javax.xml.crypto.*;
39import javax.xml.crypto.dsig.*;
40import javax.xml.crypto.dsig.spec.*;
41
42/**
43 * Useful static DOM utility methods.
44 *
45 * @author Sean Mullan
46 */
47public class DOMUtils {
48
49    // class cannot be instantiated
50    private DOMUtils() {}
51
52    /**
53     * Returns the owner document of the specified node.
54     *
55     * @param node the node
56     * @return the owner document
57     */
58    public static Document getOwnerDocument(Node node) {
59        if (node.getNodeType() == Node.DOCUMENT_NODE) {
60            return (Document)node;
61        } else {
62            return node.getOwnerDocument();
63        }
64    }
65
66    /**
67     * Creates an element in the specified namespace, with the specified tag
68     * and namespace prefix.
69     *
70     * @param doc the owner document
71     * @param tag the tag
72     * @param nsURI the namespace URI
73     * @param prefix the namespace prefix
74     * @return the newly created element
75     */
76    public static Element createElement(Document doc, String tag,
77                                        String nsURI, String prefix)
78    {
79        String qName = (prefix == null || prefix.length() == 0)
80                       ? tag : prefix + ":" + tag;
81        return doc.createElementNS(nsURI, qName);
82    }
83
84    /**
85     * Sets an element's attribute (using DOM level 2) with the
86     * specified value and namespace prefix.
87     *
88     * @param elem the element to set the attribute on
89     * @param name the name of the attribute
90     * @param value the attribute value. If null, no attribute is set.
91     */
92    public static void setAttribute(Element elem, String name, String value) {
93        if (value == null) {
94            return;
95        }
96        elem.setAttributeNS(null, name, value);
97    }
98
99    /**
100     * Sets an element's attribute (using DOM level 2) with the
101     * specified value and namespace prefix AND registers the ID value with
102     * the specified element. This is for resolving same-document
103     * ID references.
104     *
105     * @param elem the element to set the attribute on
106     * @param name the name of the attribute
107     * @param value the attribute value. If null, no attribute is set.
108     */
109    public static void setAttributeID(Element elem, String name, String value) {
110        if (value == null) {
111            return;
112        }
113        elem.setAttributeNS(null, name, value);
114        elem.setIdAttributeNS(null, name, true);
115    }
116
117    /**
118     * Returns the first child element of the specified node, or null if there
119     * is no such element.
120     *
121     * @param node the node
122     * @return the first child element of the specified node, or null if there
123     *    is no such element
124     * @throws NullPointerException if <code>node == null</code>
125     */
126    public static Element getFirstChildElement(Node node) {
127        Node child = node.getFirstChild();
128        while (child != null && child.getNodeType() != Node.ELEMENT_NODE) {
129            child = child.getNextSibling();
130        }
131        return (Element)child;
132    }
133
134    /**
135     * Returns the first child element of the specified node and checks that
136     * the local name is equal to {@code localName}.
137     *
138     * @param node the node
139     * @return the first child element of the specified node
140     * @throws NullPointerException if {@code node == null}
141     * @throws MarshalException if no such element or the local name is not
142     *    equal to {@code localName}
143     */
144    public static Element getFirstChildElement(Node node, String localName)
145        throws MarshalException
146    {
147        return verifyElement(getFirstChildElement(node), localName);
148    }
149
150    private static Element verifyElement(Element elem, String localName)
151        throws MarshalException
152    {
153        if (elem == null) {
154            throw new MarshalException("Missing " + localName + " element");
155        }
156        String name = elem.getLocalName();
157        if (!name.equals(localName)) {
158            throw new MarshalException("Invalid element name: " +
159                                       name + ", expected " + localName);
160        }
161        return elem;
162    }
163
164    /**
165     * Returns the last child element of the specified node, or null if there
166     * is no such element.
167     *
168     * @param node the node
169     * @return the last child element of the specified node, or null if there
170     *    is no such element
171     * @throws NullPointerException if <code>node == null</code>
172     */
173    public static Element getLastChildElement(Node node) {
174        Node child = node.getLastChild();
175        while (child != null && child.getNodeType() != Node.ELEMENT_NODE) {
176            child = child.getPreviousSibling();
177        }
178        return (Element)child;
179    }
180
181    /**
182     * Returns the next sibling element of the specified node, or null if there
183     * is no such element.
184     *
185     * @param node the node
186     * @return the next sibling element of the specified node, or null if there
187     *    is no such element
188     * @throws NullPointerException if <code>node == null</code>
189     */
190    public static Element getNextSiblingElement(Node node) {
191        Node sibling = node.getNextSibling();
192        while (sibling != null && sibling.getNodeType() != Node.ELEMENT_NODE) {
193            sibling = sibling.getNextSibling();
194        }
195        return (Element)sibling;
196    }
197
198    /**
199     * Returns the next sibling element of the specified node and checks that
200     * the local name is equal to {@code localName}.
201     *
202     * @param node the node
203     * @return the next sibling element of the specified node
204     * @throws NullPointerException if {@code node == null}
205     * @throws MarshalException if no such element or the local name is not
206     *    equal to {@code localName}
207     */
208    public static Element getNextSiblingElement(Node node, String localName)
209        throws MarshalException
210    {
211        return verifyElement(getNextSiblingElement(node), localName);
212    }
213
214    /**
215     * Returns the attribute value for the attribute with the specified name.
216     * Returns null if there is no such attribute, or
217     * the empty string if the attribute value is empty.
218     *
219     * <p>This works around a limitation of the DOM
220     * <code>Element.getAttributeNode</code> method, which does not distinguish
221     * between an unspecified attribute and an attribute with a value of
222     * "" (it returns "" for both cases).
223     *
224     * @param elem the element containing the attribute
225     * @param name the name of the attribute
226     * @return the attribute value (may be null if unspecified)
227     */
228    public static String getAttributeValue(Element elem, String name) {
229        Attr attr = elem.getAttributeNodeNS(null, name);
230        return (attr == null) ? null : attr.getValue();
231    }
232
233    /**
234     * Returns a Set of <code>Node</code>s, backed by the specified
235     * <code>NodeList</code>.
236     *
237     * @param nl the NodeList
238     * @return a Set of Nodes
239     */
240    public static Set<Node> nodeSet(NodeList nl) {
241        return new NodeSet(nl);
242    }
243
244    static class NodeSet extends AbstractSet<Node> {
245        private NodeList nl;
246        public NodeSet(NodeList nl) {
247            this.nl = nl;
248        }
249
250        public int size() { return nl.getLength(); }
251        public Iterator<Node> iterator() {
252            return new Iterator<Node>() {
253                int index = 0;
254
255                public void remove() {
256                    throw new UnsupportedOperationException();
257                }
258                public Node next() {
259                    if (!hasNext()) {
260                        throw new NoSuchElementException();
261                    }
262                    return nl.item(index++);
263                }
264                public boolean hasNext() {
265                    return index < nl.getLength() ? true : false;
266                }
267            };
268        }
269    }
270
271    /**
272     * Returns the prefix associated with the specified namespace URI
273     *
274     * @param context contains the namespace map
275     * @param nsURI the namespace URI
276     * @return the prefix associated with the specified namespace URI, or
277     *    null if not set
278     */
279    public static String getNSPrefix(XMLCryptoContext context, String nsURI) {
280        if (context != null) {
281            return context.getNamespacePrefix
282                (nsURI, context.getDefaultNamespacePrefix());
283        } else {
284            return null;
285        }
286    }
287
288    /**
289     * Returns the prefix associated with the XML Signature namespace URI
290     *
291     * @param context contains the namespace map
292     * @return the prefix associated with the specified namespace URI, or
293     *    null if not set
294     */
295    public static String getSignaturePrefix(XMLCryptoContext context) {
296        return getNSPrefix(context, XMLSignature.XMLNS);
297    }
298
299    /**
300     * Removes all children nodes from the specified node.
301     *
302     * @param node the parent node whose children are to be removed
303     */
304    public static void removeAllChildren(Node node) {
305        NodeList children = node.getChildNodes();
306        for (int i = 0, length = children.getLength(); i < length; i++) {
307            node.removeChild(children.item(i));
308        }
309    }
310
311    /**
312     * Compares 2 nodes for equality. Implementation is not complete.
313     */
314    public static boolean nodesEqual(Node thisNode, Node otherNode) {
315        if (thisNode == otherNode) {
316            return true;
317        }
318        if (thisNode.getNodeType() != otherNode.getNodeType()) {
319            return false;
320        }
321        // FIXME - test content, etc
322        return true;
323    }
324
325    /**
326     * Checks if child element has same owner document before
327     * appending to the parent, and imports it to the parent's document
328     * if necessary.
329     */
330    public static void appendChild(Node parent, Node child) {
331        Document ownerDoc = getOwnerDocument(parent);
332        if (child.getOwnerDocument() != ownerDoc) {
333            parent.appendChild(ownerDoc.importNode(child, true));
334        } else {
335            parent.appendChild(child);
336        }
337    }
338
339    public static boolean paramsEqual(AlgorithmParameterSpec spec1,
340        AlgorithmParameterSpec spec2) {
341        if (spec1 == spec2) {
342            return true;
343        }
344        if (spec1 instanceof XPathFilter2ParameterSpec &&
345            spec2 instanceof XPathFilter2ParameterSpec) {
346            return paramsEqual((XPathFilter2ParameterSpec)spec1,
347                               (XPathFilter2ParameterSpec)spec2);
348        }
349        if (spec1 instanceof ExcC14NParameterSpec &&
350            spec2 instanceof ExcC14NParameterSpec) {
351            return paramsEqual((ExcC14NParameterSpec) spec1,
352                               (ExcC14NParameterSpec)spec2);
353        }
354        if (spec1 instanceof XPathFilterParameterSpec &&
355            spec2 instanceof XPathFilterParameterSpec) {
356            return paramsEqual((XPathFilterParameterSpec)spec1,
357                               (XPathFilterParameterSpec)spec2);
358        }
359        if (spec1 instanceof XSLTTransformParameterSpec &&
360            spec2 instanceof XSLTTransformParameterSpec) {
361            return paramsEqual((XSLTTransformParameterSpec)spec1,
362                               (XSLTTransformParameterSpec)spec2);
363        }
364        return false;
365    }
366
367    private static boolean paramsEqual(XPathFilter2ParameterSpec spec1,
368                                       XPathFilter2ParameterSpec spec2)
369    {
370        List<XPathType> types = spec1.getXPathList();
371        List<XPathType> otypes = spec2.getXPathList();
372        int size = types.size();
373        if (size != otypes.size()) {
374            return false;
375        }
376        for (int i = 0; i < size; i++) {
377            XPathType type = types.get(i);
378            XPathType otype = otypes.get(i);
379            if (!type.getExpression().equals(otype.getExpression()) ||
380                !type.getNamespaceMap().equals(otype.getNamespaceMap()) ||
381                type.getFilter() != otype.getFilter()) {
382                return false;
383            }
384        }
385        return true;
386    }
387
388    private static boolean paramsEqual(ExcC14NParameterSpec spec1,
389                                       ExcC14NParameterSpec spec2)
390    {
391        return spec1.getPrefixList().equals(spec2.getPrefixList());
392    }
393
394    private static boolean paramsEqual(XPathFilterParameterSpec spec1,
395                                       XPathFilterParameterSpec spec2)
396    {
397        return (spec1.getXPath().equals(spec2.getXPath()) &&
398                spec1.getNamespaceMap().equals(spec2.getNamespaceMap()));
399    }
400
401    private static boolean paramsEqual(XSLTTransformParameterSpec spec1,
402                                       XSLTTransformParameterSpec spec2)
403    {
404
405        XMLStructure ostylesheet = spec2.getStylesheet();
406        if (!(ostylesheet instanceof javax.xml.crypto.dom.DOMStructure)) {
407            return false;
408        }
409        Node ostylesheetElem =
410            ((javax.xml.crypto.dom.DOMStructure) ostylesheet).getNode();
411        XMLStructure stylesheet = spec1.getStylesheet();
412        Node stylesheetElem =
413            ((javax.xml.crypto.dom.DOMStructure) stylesheet).getNode();
414        return nodesEqual(stylesheetElem, ostylesheetElem);
415    }
416}
417