org.jbpm.bpel.xml.util.XmlUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.jbpm.bpel.xml.util.XmlUtil.java

Source

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the JBPM BPEL PUBLIC LICENSE AGREEMENT as
 * published by JBoss Inc.; either version 1.0 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.jbpm.bpel.xml.util;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.ibm.wsdl.util.xml.DOMUtils;

import org.jbpm.bpel.endpointref.EndpointReference;
import org.jbpm.bpel.graph.exe.BpelFaultException;
import org.jbpm.bpel.xml.BpelConstants;
import org.jbpm.util.ClassLoaderUtil;

/**
 * Utility methods for dealing with JAXP objects.
 * @author Alejandro Guizar
 * @version $Revision$ $Date: 2007/11/25 13:03:14 $
 */
public class XmlUtil {

    static final String QUALIFIED_VALUE_PREFIX = "valueNS";

    private static final Log log = LogFactory.getLog(XmlUtil.class);
    private static final boolean traceEnabled = log.isTraceEnabled();

    private static final String[] schemaSources = createSchemaSources();

    private static ThreadLocal documentBuilderLocal = new ThreadLocal() {

        protected Object initialValue() {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            factory.setValidating(true);
            factory.setCoalescing(true);
            factory.setIgnoringElementContentWhitespace(true);
            factory.setIgnoringComments(true);
            try {
                // set xml schema as the schema language
                factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
                        BpelConstants.NS_XML_SCHEMA);
                // set schema sources
                factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", schemaSources);
            } catch (IllegalArgumentException e) {
                log.warn("JAXP implementation does not support XML Schema, "
                        + "XML documents will not be checked for grammar errors", e);
            }
            try {
                // validate the document only if a grammar is specified
                factory.setAttribute("http://apache.org/xml/features/validation/dynamic", Boolean.TRUE);
            } catch (IllegalArgumentException e) {
                log.warn("JAXP implementation is not Xerces, cannot enable dynamic validation, "
                        + "XML documents without schema location will not parse", e);
            }
            try {
                DocumentBuilder builder = factory.newDocumentBuilder();
                builder.setEntityResolver(LocalEntityResolver.INSTANCE);
                return builder;
            } catch (ParserConfigurationException e) {
                // should not happen
                throw new AssertionError(e);
            }
        }
    };

    private static ThreadLocal transformerFactoryLocal = new ThreadLocal() {

        protected Object initialValue() {
            return TransformerFactory.newInstance();
        }
    };

    // suppress default constructor, ensuring non-instantiability
    private XmlUtil() {
    }

    private static String[] createSchemaSources() {
        final String[] schemaResources = { "xml.xsd", "wsdl.xsd", "bpel_2_0.xsd", "plnktype_2_0.xsd", "varprop.xsd",
                "serviceref.xsd", "bpel_1_1.xsd", "plnktype_1_1.xsd", "bpel_definition_1_1.xsd",
                "bpel_deployment_1_1.xsd", "addressing.xsd" };
        String[] schemaSources = new String[schemaResources.length];
        for (int i = 0; i < schemaResources.length; i++)
            schemaSources[i] = XmlUtil.class.getResource(schemaResources[i]).toExternalForm();
        return schemaSources;
    }

    /**
     * Gets the first child element of the given node having the specified local name and a
     * <code>null</code> or empty namespace URI.
     * @param parent the parent node to examine
     * @param localName the local name of the desired child element
     * @return the corresponding child element, or <code>null</code> if there is no match
     */
    public static Element getElement(Node parent, String localName) {
        return getElement(parent, null, localName);
    }

    /**
     * Gets the first child element of the given node having the specified namespace URI and local
     * name.
     * @param parent the parent node to examine
     * @param namespaceURI the namespace URI of the desired child element; if <code>null</code> or
     * empty, only elements with a <code>null</code> or empty namespace URI will be considered
     * @param localName the local name of the desired child element
     * @return the corresponding child element, or <code>null</code> if there is no match
     */
    public static Element getElement(Node parent, String namespaceURI, String localName) {
        for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (nodeNameEquals(child, namespaceURI, localName))
                return (Element) child;
        }
        return null;
    }

    /**
     * Gets the first child element of the given node.
     * @param parent the parent node to examine
     * @return the corresponding child element, or <code>null</code> if there is no match
     */
    public static Element getElement(Node parent) {
        for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (child.getNodeType() == Node.ELEMENT_NODE)
                return (Element) child;
        }
        return null;
    }

    /**
     * Gets an iterator over the child elements of the given node with the specified namespace URI and
     * any local name.
     * @param parent the parent node to iterate
     * @param namespaceURI the namespace URI of the desired child elements; if <code>null</code>,
     * only elements with a <code>null</code> or empty namespace URI will be iterated
     * @return an {@link Element} iterator, empty if there is no match
     */
    public static Iterator getElements(Node parent, String namespaceURI) {
        return IteratorUtils.filteredIterator(new NodeIterator(parent),
                new NamespaceElementPredicate(namespaceURI));
    }

    /**
     * Gets an iterator over the child elements of the given node with the specified namespace URI and
     * local name.
     * @param parent the parent node to iterate
     * @param namespaceURI the namespace URI of the desired child elements; if <code>null</code>,
     * only elements with a <code>null</code> or empty namespace URI will be iterated
     * @param localName the local name of the desired child elements
     * @return an {@link Element} iterator, empty if there is no match
     */
    public static Iterator getElements(Node parent, String namespaceURI, String localName) {
        return IteratorUtils.filteredIterator(new NodeIterator(parent),
                new QualifiedNameElementPredicate(namespaceURI, localName));
    }

    /**
     * Gets an iterator over the child elements of the given node.
     * @param parent the parent node to iterate
     * @return an {@link Element} iterator, empty if there is no match
     */
    public static Iterator getElements(Node parent) {
        return IteratorUtils.filteredIterator(new NodeIterator(parent), ElementPredicate.INSTANCE);
    }

    /**
     * Counts the child elements of the given node with the specified namespace URI and local name.
     * @param parent the parent node to iterate
     * @param namespaceURI the namespace URI of the desired child elements; if <code>null</code>,
     * only elements with a <code>null</code> or empty namespace URI will be iterated
     * @param localName the local name of the desired child elements
     * @return the element count, non-negative
     */
    public static int countElements(Node parent, String namespaceURI, String localName) {
        int count = 0;
        for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
            if (nodeNameEquals(child, namespaceURI, localName))
                count++;
        }
        return count;
    }

    /**
     * Gets an attribute value of the given element.
     * @param ownerElem the element that owns the attribute
     * @param attrName the name of the attribute to retrieve
     * @return the attribute value as a string, or <code>null</code> if that attribute does not have
     * a specified value
     */
    public static String getAttribute(Element ownerElem, String attrName) {
        Attr attribute = ownerElem.getAttributeNode(attrName);
        return attribute != null ? attribute.getValue() : null;
    }

    /**
     * Parses the string argument as a {@linkplain QName qualified name} in the context of the given
     * node.
     * @param prefixedName a name that may contain a colon character
     * @param contextNode the node to search for namespace declarations
     * @return a qualified name constructed with the following arguments:
     * <ul>
     * <li><code>localPart</code> substring of <code>prefixedName</code> after the first colon</li>
     * <li><code>prefix</code> substring of <code>prefixedName</code> before the first colon</li>
     * <li><code>namespaceURI</code> namespace associated with <code>prefix</code> in the
     * <code>contextNode</code></li>
     * </ul>
     */
    public static QName parseQName(String prefixedName, Node contextNode) {
        int index = prefixedName.indexOf(':');
        // no colon?
        if (index == -1)
            return new QName(prefixedName); // local name

        String prefix = prefixedName.substring(0, index);
        return new QName(getNamespaceURI(prefix, contextNode), prefixedName.substring(index + 1), prefix);
    }

    /**
     * Parses the text content of the given element as a {@linkplain QName qualified name}.
     * @param elem the element whose text content will be parsed
     * @return the element text content as a {@link QName}
     */
    public static QName getQNameValue(Element elem) {
        return parseQName(DatatypeUtil.toString(elem), elem);
    }

    /**
     * Parses the value of the given attribute as a {@linkplain QName qualified name}.
     * @param attr the attribute whose value will be parsed
     * @return the attribute value as a {@link QName}
     */
    public static QName getQNameValue(Attr attr) {
        return parseQName(attr.getValue(), attr.getOwnerElement());
    }

    /**
     * Tells whether the name of the specified node matches the given qualified name.
     * @param node the node to examine
     * @param name the qualified name to match
     * @return <code>true</code> if the qualified name matches, <code>false</code> otherwise
     */
    public static boolean nodeNameEquals(Node node, QName name) {
        return nodeNameEquals(node, name.getNamespaceURI(), name.getLocalPart());
    }

    /**
     * Tells whether the name of the specified node matches the given namespace URI and local name.
     * @param node the node to examine
     * @param namespaceURI the namespace URI to match
     * @param localName the local name to match
     * @return <code>true</code> if the namespace URI and local name match, <code>false</code>
     * otherwise
     */
    public static boolean nodeNameEquals(Node node, String namespaceURI, String localName) {
        return nodeNamespaceURIEquals(node, namespaceURI) && localName.equals(node.getLocalName());
    }

    /**
     * Tells whether the namespace URI of the specified node matches the given namespace URI.
     * @param node the node to examine
     * @param namespaceURI the namespace URI to match
     * @return <code>true</code> if the namespace URI matches, <code>false</code> otherwise
     */
    public static boolean nodeNamespaceURIEquals(Node node, String namespaceURI) {
        String nodeNamespaceURI = node.getNamespaceURI();
        return nodeNamespaceURI == null || nodeNamespaceURI.length() == 0
                ? namespaceURI == null || namespaceURI.length() == 0
                : nodeNamespaceURI.equals(namespaceURI);
    }

    public static void setObjectValue(Node node, Object value) {
        switch (node.getNodeType()) {
        case Node.ELEMENT_NODE:
            setObjectValue((Element) node, value);
            break;
        case Node.DOCUMENT_NODE:
            setObjectValue(((Document) node).getDocumentElement(), value);
            break;
        default:
            // BPEL-243 throw selectionFailure if the source is an EII with xsi:nil=true
            if (value instanceof Element) {
                String nil = ((Element) value).getAttributeNS(BpelConstants.NS_XML_SCHEMA_INSTANCE,
                        BpelConstants.ATTR_NIL);
                if (DatatypeUtil.parseBoolean(nil) == Boolean.TRUE)
                    throw new BpelFaultException(BpelConstants.FAULT_SELECTION_FAILURE);
            }
            // replace content
            node.setNodeValue(DatatypeUtil.toString(value));
        }
    }

    public static void setObjectValue(Element elem, Object value) {
        if (value instanceof Node) {
            switch (((Node) value).getNodeType()) {
            case Node.ELEMENT_NODE:
                // replace element
                copy(elem, (Element) value);
                break;
            case Node.DOCUMENT_NODE:
                // replace element
                copy(elem, ((Document) value).getDocumentElement());
                break;
            default:
                // replace content
                setStringValue(elem, ((Node) value).getNodeValue());
            }
        } else if (value instanceof EndpointReference) {
            // replace element
            ((EndpointReference) value).writeServiceRef(elem);
        } else {
            // replace content
            setStringValue(elem, DatatypeUtil.toString(value));
        }
    }

    public static void setStringValue(Element elem, String value) {
        // remove jbpm:initialized
        elem.removeAttributeNS(BpelConstants.NS_VENDOR, BpelConstants.ATTR_INITIALIZED);

        Node firstChild = elem.getFirstChild();
        // if first child is a text node, reuse it
        if (firstChild instanceof org.w3c.dom.Text)
            firstChild.setNodeValue(value);
        // otherwise, just create a new text node
        else
            firstChild = elem.getOwnerDocument().createTextNode(value);

        // remove all children
        removeChildNodes(elem);
        // append text
        elem.appendChild(firstChild);
    }

    public static void setQNameValue(Element elem, QName value) {
        setStringValue(elem, formatQName(value, elem));
    }

    public static void setQNameValue(Attr attr, QName value) {
        attr.setValue(formatQName(value, attr.getOwnerElement()));
    }

    private static String formatQName(QName value, Element elem) {
        String namespace = value.getNamespaceURI();
        String localName = value.getLocalPart();

        // easy way out: no namespace
        if (namespace.length() == 0)
            return localName;

        String prefix = getPrefix(namespace, elem);
        if (prefix == null) {
            String givenPrefix = value.getPrefix();
            prefix = generatePrefix(givenPrefix.length() > 0 ? givenPrefix : QUALIFIED_VALUE_PREFIX, elem);
            addNamespaceDeclaration(elem, namespace, prefix);
        }
        return prefix + ':' + localName;
    }

    public static String toTraceString(Element elem) {
        String namespace = elem.getNamespaceURI();
        String localName = elem.getLocalName();

        // easy way out: no namespace
        if (namespace == null || namespace.length() == 0)
            return localName;

        StringBuffer traceBuffer = new StringBuffer(namespace.length() + localName.length());
        traceBuffer.append('{').append(namespace).append('}');

        String prefix = elem.getPrefix();
        if (prefix != null && prefix.length() > 0)
            traceBuffer.append(prefix).append(':');

        return traceBuffer.append(localName).toString();
    }

    public static void copy(Element target, Element source) {
        if (traceEnabled)
            log.trace("copying from: " + toTraceString(source));
        // attributes
        removeAttributes(target);
        copyAttributes(target, source);
        // all namespaces
        copyVisibleNamespaces(target, source);
        ensureOwnNamespaceDeclared(target);
        // child nodes
        removeChildNodes(target);
        copyChildNodes(target, source);
        if (traceEnabled)
            log.trace("copied to: " + toTraceString(target));
    }

    public static void copyVisibleNamespaces(final Element target, Element source) {
        // copy namespaces declared at source element
        copyNamespaces(target, source);
        // go up the element hierarchy
        for (Node parent = source.getParentNode(); parent instanceof Element; parent = parent.getParentNode())
            copyNamespaces(target, (Element) parent);
    }

    public static void copyNamespaces(final Element target, Element source) {
        // easy way out: no attributes
        if (!source.hasAttributes())
            return;
        // traverse attributes to discover namespace declarations
        NamedNodeMap attributes = source.getAttributes();
        for (int i = 0, n = attributes.getLength(); i < n; i++) {
            Node attribute = attributes.item(i);
            // is attribute a namespace declaration?
            if (!BpelConstants.NS_XMLNS.equals(attribute.getNamespaceURI()))
                continue;
            // namespace declaration format
            // xmlns:prefix="namespaceURI" | xmlns="defaultNamespaceURI"
            String namespaceURI = attribute.getNodeValue();
            String prefix = attribute.getLocalName();
            // default namespace declaration?
            if ("xmlns".equals(prefix)) {
                // BPEL-195: prevent addition matching visible declaration at target
                if ("".equals(getPrefix(namespaceURI, target)))
                    continue;
                addNamespaceDeclaration(target, namespaceURI);
                if (traceEnabled)
                    log.trace("added default namespace declaration: " + namespaceURI);
            } else {
                // BPEL-195: prevent addition matching visible declaration at target
                if (prefix.equals(getPrefix(namespaceURI, target)))
                    continue;
                addNamespaceDeclaration(target, namespaceURI, prefix);
                if (traceEnabled)
                    log.trace("added namespace declaration: " + prefix + "->" + namespaceURI);
            }
        }
    }

    public static void copyAttributes(Element target, Element source) {
        // easy way out: no attributes
        if (!source.hasAttributes())
            return;
        // traverse attributes
        NamedNodeMap attributes = source.getAttributes();
        for (int i = 0, n = attributes.getLength(); i < n; i++) {
            Node sourceAttr = attributes.item(i);
            String namespaceURI = sourceAttr.getNamespaceURI();
            String name = sourceAttr.getNodeName();
            // namespace declaration?
            if (BpelConstants.NS_XMLNS.equals(namespaceURI))
                continue;
            // unqualified?
            if (namespaceURI == null || namespaceURI.length() == 0) {
                target.setAttribute(name, sourceAttr.getNodeValue());
                if (traceEnabled)
                    log.trace("set attribute: " + name);
            }
            // qualified
            else {
                Attr targetAttr = target.getOwnerDocument().createAttributeNS(namespaceURI, name);
                targetAttr.setValue(sourceAttr.getNodeValue());
                target.setAttributeNodeNS(targetAttr);
                ensureNamespaceDeclared(targetAttr, namespaceURI, sourceAttr.getPrefix());
                if (traceEnabled)
                    log.trace("set attribute: {" + namespaceURI + '}' + name);
            }
        }
    }

    public static void copyChildNodes(Element target, Element source) {
        Document targetDoc = target.getOwnerDocument();
        for (Node child = source.getFirstChild(); child != null; child = child.getNextSibling()) {
            switch (child.getNodeType()) {
            case Node.ELEMENT_NODE:
                copyChildElement(target, (Element) child);
                break;
            case Node.TEXT_NODE:
            case Node.CDATA_SECTION_NODE:
                target.appendChild(targetDoc.createTextNode(child.getNodeValue()));
                if (traceEnabled)
                    log.trace("appended text: " + child.getNodeValue());
                break;
            default:
                log.debug("discarding child: " + child);
            }
        }
    }

    public static void copyChildElement(Element parent, Element source) {
        Element target = (Element) parent.getOwnerDocument().importNode(source, true);
        parent.appendChild(target);
        // ensure namespace declared after appending in order to consider all visible declarations
        ensureOwnNamespaceDeclared(target);
        if (traceEnabled)
            log.trace("appended element: " + toTraceString(target));
    }

    /**
     * Ensures the given namespace URI is associated to the given prefix in the scope of the given
     * attribute. If the namespace URI is <em>not</em> associated to the prefix, a namespace
     * declaration is {@linkplain #addNamespaceDeclaration(Element, String, String) added} to the
     * attribute's owner element. Otherwise, this call has no effect.
     * @param attribute the attribute to verify
     * @param namespaceURI the namespace URI to ensure; cannot be <code>null</code> or empty
     * @param prefix the prefix to ensure; cannot be <code>null</code> or empty
     */
    public static void ensureNamespaceDeclared(Attr attribute, String namespaceURI, String prefix) {
        if (prefix == null || prefix.length() == 0)
            throw new IllegalArgumentException("prefix cannot be empty");

        if (namespaceURI == null || namespaceURI.length() == 0)
            throw new IllegalArgumentException("namespaceURI cannot be empty");

        Element element = attribute.getOwnerElement();
        if (!prefix.equals(getPrefix(namespaceURI, element))) {
            // prefix is associated with other/no URI, declare locally
            addNamespaceDeclaration(element, namespaceURI, prefix);
        }
    }

    /**
     * Ensures the given namespace URI is associated to the given prefix in the scope of the given
     * element. If the namespace URI is <em>not</em> associated to the prefix, a namespace
     * declaration is {@linkplain #addNamespaceDeclaration(Element, String, String) added} to the
     * element. Otherwise, this call has no effect.
     * @param element the element to verify
     * @param namespaceURI the namespace URI to ensure; cannot be <code>null</code> or empty unless
     * <code>prefix</code> is <code>null</code> or empty as well
     * @param prefix the prefix to ensure; if <code>null</code> or empty, the given namespace URI is
     * ensured to be the default
     * @throws IllegalArgumentException if <code>namespaceURI</code> is <code>null</code> or
     * empty, <em>unless</em> <code>prefix</code> is empty as well
     */
    public static void ensureNamespaceDeclared(Element element, String namespaceURI, String prefix) {
        if (prefix == null || prefix.length() == 0) {
            if (namespaceURI == null)
                namespaceURI = "";

            // verify the given URI is the default namespace
            if (!"".equals(getPrefix(namespaceURI, element))) {
                // the given URI is not the default namespace, declare locally
                addNamespaceDeclaration(element, namespaceURI);
            }
        } else {
            if (namespaceURI == null || namespaceURI.length() == 0)
                throw new IllegalArgumentException("namespaceURI cannot be empty unless prefix is empty");

            // verify given prefix is associated to given URI
            if (!prefix.equals(getPrefix(namespaceURI, element))) {
                // prefix is associated with other/no URI, declare locally
                addNamespaceDeclaration(element, namespaceURI, prefix);
            }
        }
    }

    public static void ensureOwnNamespaceDeclared(Element elem) {
        ensureNamespaceDeclared(elem, elem.getNamespaceURI(), elem.getPrefix());
    }

    public static Node appendForeignChild(Node node, Node foreignChild) {
        return node.appendChild(node.getOwnerDocument().importNode(foreignChild, true));
    }

    public static void addNamespaceDeclaration(Element elem, String namespaceURI) {
        elem.setAttributeNS(BpelConstants.NS_XMLNS, "xmlns", namespaceURI);
    }

    public static void addNamespaceDeclaration(Element elem, String namespaceURI, String prefix) {
        elem.setAttributeNS(BpelConstants.NS_XMLNS, "xmlns:" + prefix, namespaceURI);
    }

    public static void removeAttributes(Element elem) {
        if (elem.hasAttributes()) {
            NamedNodeMap attributeMap = elem.getAttributes();
            // since node maps are live, hold attributes in a separate collection
            int n = attributeMap.getLength();
            Attr[] attributes = new Attr[n];
            for (int i = 0; i < n; i++)
                attributes[i] = (Attr) attributeMap.item(i);
            // now remove each attribute from the element
            for (int i = 0; i < n; i++)
                elem.removeAttributeNode(attributes[i]);
        }
    }

    public static void removeChildNodes(Node node) {
        for (Node current = node.getFirstChild(), next; current != null; current = next) {
            next = current.getNextSibling();
            node.removeChild(current);
        }
    }

    public static Document createDocument() {
        return getDocumentBuilder().newDocument();
    }

    /**
     * Creates an element with the given qualified name. The returned element will be set as the
     * document element of its owner document.
     * @param name the qualified name of the element to create
     * @return a new element with the specified <code>name</code>
     */
    public static Element createElement(QName name) {
        String namespace = name.getNamespaceURI();
        String localName = name.getLocalPart();
        if (namespace.length() == 0)
            return createElement(localName);

        String prefix = name.getPrefix();
        return createElement(namespace, prefix.length() > 0 ? prefix + ':' + localName : localName);
    }

    /**
     * Creates an element with the given namespace URI and prefixed name. The returned element will be
     * set as the document element of its owner document.
     * @param namespaceURI the namespace URI of the element to create
     * @param prefixedName the prefixed name of the element to instantiate
     * @return a new element with the specified <code>namespaceURI</code> and
     * <code>prefixedName</code>.
     */
    public static Element createElement(String namespaceURI, String prefixedName) {
        Document doc = createDocument();
        Element elem = doc.createElementNS(namespaceURI, prefixedName);
        doc.appendChild(elem);

        // some TrAX implementations do not fix up namespaces, declare namespace
        String prefix = elem.getPrefix();
        if (prefix != null)
            addNamespaceDeclaration(elem, namespaceURI, prefix);
        else
            addNamespaceDeclaration(elem, namespaceURI);

        return elem;
    }

    /**
     * Creates an element with the given local name and neither namespace URI nor prefix. The returned
     * element will be set as the document element of its owner document.
     * @param localName the local name of the element to create
     * @return a new element with the specified <code>localName</code>
     */
    public static Element createElement(String localName) {
        Document doc = createDocument();
        Element elem = doc.createElementNS(null, localName);
        doc.appendChild(elem);
        return elem;
    }

    /**
     * Parses the XML document contained in the given string into a DOM document.
     * @param text a string containing the document to parse
     * @return a new DOM document representing the XML content
     * @throws SAXException if any parse errors occur
     */
    public static Element parseText(String text) throws SAXException {
        try {
            return getDocumentBuilder().parse(new InputSource(new StringReader(text))).getDocumentElement();
        } catch (IOException e) {
            // cannot happen, string reader doesn't throw I/O exceptions unless closed
            throw new AssertionError(e);
        }
    }

    public static Element parseResource(String resource) throws SAXException, IOException {
        return parseResource(resource, ClassLoaderUtil.getClassLoader().getResource(resource));
    }

    public static DOMSource parseResource(String resource, Class cl) throws SAXException, IOException {
        URL resourceURL = cl.getResource(resource);
        Element elem = parseResource(resource, resourceURL);
        return new DOMSource(elem, resourceURL.toExternalForm());
    }

    private static Element parseResource(String resource, URL location) throws SAXException, IOException {
        if (location == null)
            throw new FileNotFoundException(resource);

        InputStream inputStream = location.openStream();
        try {
            return getDocumentBuilder().parse(inputStream, resource).getDocumentElement();
        } finally {
            inputStream.close();
        }
    }

    /**
     * Gets a validating document builder local to the current thread.
     * @return a thread-local document builder
     */
    public static DocumentBuilder getDocumentBuilder() {
        return (DocumentBuilder) documentBuilderLocal.get();
    }

    public static Map findNamespaceDeclarations(Element elem) {
        Map namespaces = new HashMap();

        findLocalNamespaceDeclarations(elem, namespaces);

        // go up the parent hierarchy
        for (Node parent = elem.getParentNode(); parent instanceof Element; parent = parent.getParentNode())
            findLocalNamespaceDeclarations((Element) parent, namespaces);

        return namespaces;
    }

    private static void findLocalNamespaceDeclarations(Element elem, Map namespaces) {
        // traverse attributes to discover namespace declarations
        NamedNodeMap attributes = elem.getAttributes();
        for (int i = 0, n = attributes.getLength(); i < n; i++) {
            Node attribute = attributes.item(i);

            // is attribute a namespace declaration?
            if (!BpelConstants.NS_XMLNS.equals(attribute.getNamespaceURI()))
                continue;

            // namespace declaration format:
            // xmlns:prefix="namespaceURI" | xmlns="defaultNamespaceURI"
            String prefix = attribute.getLocalName();

            // exclude default and overriden namespace declarations
            if ("xmlns".equals(prefix) || namespaces.containsKey(prefix))
                continue;

            String namespaceURI = attribute.getNodeValue();
            namespaces.put(prefix, namespaceURI);
        }
    }

    /**
     * Retrieves the prefix associated with a namespace URI in the given context node.
     * @param namespaceURI the namespace whose prefix is required
     * @param contextNode the node where to search for namespace declarations
     * @return the prefix associated with the namespace URI; the empty string indicates the default
     * namespace, while <code>null</code> indicates no association
     */
    public static String getPrefix(String namespaceURI, Node contextNode) {
        switch (contextNode.getNodeType()) {
        case Node.ATTRIBUTE_NODE:
            contextNode = ((Attr) contextNode).getOwnerElement();
            break;
        case Node.ELEMENT_NODE:
            break;
        default:
            contextNode = contextNode.getParentNode();
        }

        while (contextNode != null && contextNode.getNodeType() == Node.ELEMENT_NODE) {
            NamedNodeMap attributes = contextNode.getAttributes();
            for (int i = 0, n = attributes.getLength(); i < n; i++) {
                Node attr = attributes.item(i);

                // is attribute a namespace declaration and matches the given URI?
                if (BpelConstants.NS_XMLNS.equals(attr.getNamespaceURI())
                        && namespaceURI.equals(attr.getNodeValue())) {
                    String prefix = attr.getLocalName();
                    return "xmlns".equals(prefix) ? "" : prefix;
                }
            }
            contextNode = contextNode.getParentNode();
        }
        return null;
    }

    public static String getNamespaceURI(String prefix, Node contextNode) {
        return DOMUtils.getNamespaceURIFromPrefix(contextNode, prefix);
    }

    public static String generatePrefix(String base, Element contextElem) {
        // check possible collision with namespace declarations
        if (!contextElem.hasAttributeNS(BpelConstants.NS_XMLNS, base))
            return base;

        // collision detected, append a discriminator number
        StringBuffer prefixBuffer = new StringBuffer(base);

        for (int i = 1; i < Integer.MAX_VALUE; i++) {
            String prefix = prefixBuffer.append(i).toString();

            if (!contextElem.hasAttributeNS(BpelConstants.NS_XMLNS, prefix))
                return prefix;

            // remove appended number
            prefixBuffer.setLength(base.length());
        }

        throw new Error("could not generate prefix from base: " + base);
    }

    public static TransformerFactory getTransformerFactory() {
        return (TransformerFactory) transformerFactoryLocal.get();
    }

    public static Templates createTemplates(URL templateURL) throws TransformerException {
        return getTransformerFactory().newTemplates(new SAXSource(new InputSource(templateURL.toExternalForm())));
    }

    public static void writeFile(Node node, File file) throws IOException {
        try {
            // prepare identity transformer
            Transformer xmlWriter = XmlUtil.getTransformerFactory().newTransformer();

            // prepare output stream
            OutputStream fileSink = new BufferedOutputStream(new FileOutputStream(file));
            try {
                xmlWriter.transform(new DOMSource(node), new StreamResult(fileSink));
            } finally {
                fileSink.close();
            }
        } catch (TransformerException e) {
            Throwable cause = e.getCause();
            if (cause instanceof IOException)
                throw (IOException) cause;
            // should not happen
            throw new AssertionError(e);
        }
    }

    private static class ElementPredicate implements Predicate {

        static final Predicate INSTANCE = new ElementPredicate();

        private ElementPredicate() {
        }

        public boolean evaluate(Object arg) {
            return evaluate((Node) arg);
        }

        public static boolean evaluate(Node node) {
            return node.getNodeType() == Node.ELEMENT_NODE;
        }
    }

    private static class NamespaceElementPredicate implements Predicate {

        private final String namespaceURI;

        NamespaceElementPredicate(String namespaceURI) {
            this.namespaceURI = namespaceURI;
        }

        public boolean evaluate(Object arg) {
            return evaluate((Node) arg, namespaceURI);
        }

        public static boolean evaluate(Node node, String namespaceURI) {
            return node.getNodeType() == Node.ELEMENT_NODE && nodeNamespaceURIEquals(node, namespaceURI);
        }
    }

    private static class QualifiedNameElementPredicate implements Predicate {

        private final String namespaceURI;
        private final String localName;

        QualifiedNameElementPredicate(String namespaceURI, String localName) {
            this.namespaceURI = namespaceURI;
            this.localName = localName;
        }

        public boolean evaluate(Object arg) {
            return evaluate((Node) arg, namespaceURI, localName);
        }

        public static boolean evaluate(Node node, String namespaceURI, String localName) {
            return node.getNodeType() == Node.ELEMENT_NODE && nodeNameEquals(node, namespaceURI, localName);
        }
    }
}