/* * Copyright 2001-2004 The Apache Software Foundation. * * Licensed 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. */ package org.apache.axis.message; import org.apache.axis.components.logger.LogFactory; import org.apache.axis.encoding.SerializationContext; import org.apache.axis.i18n.Messages; import org.apache.commons.logging.Log; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.CharacterData; import org.w3c.dom.Comment; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.Attributes; import org.xml.sax.helpers.AttributesImpl; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPException; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; /** * This is our implementation of the DOM node */ public class NodeImpl implements org.w3c.dom.Node, javax.xml.soap.Node, Serializable, Cloneable { protected static Log log = LogFactory.getLog(NodeImpl.class.getName()); protected String name; protected String prefix; protected String namespaceURI; protected transient Attributes attributes = NullAttributes.singleton; protected Document document = null; protected NodeImpl parent = null; protected ArrayList children = null; // ...or as DOM protected CharacterData textRep = null; protected boolean _isDirty = false; private static final String NULL_URI_NAME = "intentionalNullURI"; /** * empty constructor */ public NodeImpl() { } /** * constructor which adopts the name and NS of the char data, and its text * @param text */ public NodeImpl(CharacterData text) { textRep = text; namespaceURI = text.getNamespaceURI(); name = text.getLocalName(); } /** * A code representing the type of the underlying object, as defined above. */ public short getNodeType() { if (this.textRep != null) { if (textRep instanceof Comment) { return COMMENT_NODE; } else if (textRep instanceof CDATASection) { return CDATA_SECTION_NODE; } else { return TEXT_NODE; } } else if (false) { return DOCUMENT_FRAGMENT_NODE; } else if (false) { return Node.ELEMENT_NODE; } else { // most often but we cannot give prioeity now return Node.ELEMENT_NODE; } } /** * Puts all Text nodes in the full depth of the sub-tree * underneath this Node, including attribute nodes, into a * "normal" form where only structure (e.g., elements, comments, * processing instructions, CDATA sections, and entity references) * separates Text nodes, i.e., there are neither adjacent * Text nodes nor empty Text nodes. This can * be used to ensure that the DOM view of a document is the same as if * it were saved and re-loaded, and is useful when operations (such as * XPointer lookups) that depend on a particular document tree * structure are to be used.In cases where the document contains * CDATASections, the normalize operation alone may not be * sufficient, since XPointers do not differentiate between * Text nodes and CDATASection nodes. */ public void normalize() { //TODO: Fix this for SAAJ 1.2 Implementation } /** * Returns whether this node (if it is an element) has any attributes. * * @return true if this node has any attributes, * false otherwise. * @since DOM Level 2 */ public boolean hasAttributes() { return attributes.getLength() > 0; } /** * Returns whether this node has any children. * * @return true if this node has any children, * false otherwise. */ public boolean hasChildNodes() { return (children != null && !children.isEmpty()); } /** * Returns the local part of the qualified name of this node. *
For nodes of any type other than ELEMENT_NODE and * ATTRIBUTE_NODE and nodes created with a DOM Level 1 * method, such as createElement from the * Document interface, this is always null. * * @since DOM Level 2 */ public String getLocalName() { return name; } /** * The namespace URI of this node, or null if it is * unspecified. *
This is not a computed value that is the result of a namespace * lookup based on an examination of the namespace declarations in * scope. It is merely the namespace URI given at creation time. *
For nodes of any type other than ELEMENT_NODE and * ATTRIBUTE_NODE and nodes created with a DOM Level 1 * method, such as createElement from the * Document interface, this is always null.Per * the Namespaces in XML Specification an attribute does not inherit * its namespace from the element it is attached to. If an attribute is * not explicitly given a namespace, it simply has no namespace. * * @since DOM Level 2 */ public String getNamespaceURI() { return (namespaceURI); } /** * The name of this node, depending on its type; see the table above. */ public String getNodeName() { return (prefix != null && prefix.length() > 0) ? prefix + ":" + name : name; } /** * The value of this node, depending on its type; see the table above. * When it is defined to be null, setting it has no effect. * * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly. * @throws org.w3c.dom.DOMException DOMSTRING_SIZE_ERR: Raised when it would return more characters than * fit in a DOMString variable on the implementation * platform. */ public String getNodeValue() throws DOMException { if (textRep == null) { return null; } else { return textRep.getData(); } } /** * The namespace prefix of this node, or null if it is * unspecified. *
Note that setting this attribute, when permitted, changes the * nodeName attribute, which holds the qualified name, as * well as the tagName and name attributes of * the Element and Attr interfaces, when * applicable. *
Note also that changing the prefix of an attribute that is known to * have a default value, does not make a new attribute with the default * value and the original prefix appear, since the * namespaceURI and localName do not change. *
For nodes of any type other than ELEMENT_NODE and * ATTRIBUTE_NODE and nodes created with a DOM Level 1 * method, such as createElement from the * Document interface, this is always null. * * @throws org.w3c.dom.DOMException INVALID_CHARACTER_ERR: Raised if the specified prefix contains an * illegal character, per the XML 1.0 specification . *
NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. *
NAMESPACE_ERR: Raised if the specified prefix is * malformed per the Namespaces in XML specification, if the * namespaceURI of this node is null, if the * specified prefix is "xml" and the namespaceURI of this * node is different from "http://www.w3.org/XML/1998/namespace", if * this node is an attribute and the specified prefix is "xmlns" and * the namespaceURI of this node is different from " * http://www.w3.org/2000/xmlns/", or if this node is an attribute and * the qualifiedName of this node is "xmlns" . * @since DOM Level 2 */ public String getPrefix() { return (prefix); } /** * The value of this node, depending on its type; see the table above. * When it is defined to be null, setting it has no effect. * * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR: Raised when the node is readonly. * @throws org.w3c.dom.DOMException DOMSTRING_SIZE_ERR: Raised when it would return more characters than * fit in a DOMString variable on the implementation * platform. */ public void setNodeValue(String nodeValue) throws DOMException { throw new DOMException(DOMException.NO_DATA_ALLOWED_ERR, "Cannot use TextNode.set in " + this); } /** * The namespace prefix of this node, or null if it is * unspecified. *
Note that setting this attribute, when permitted, changes the * nodeName attribute, which holds the qualified name, as * well as the tagName and name attributes of * the Element and Attr interfaces, when * applicable. *
Note also that changing the prefix of an attribute that is known to * have a default value, does not make a new attribute with the default * value and the original prefix appear, since the * namespaceURI and localName do not change. *
For nodes of any type other than ELEMENT_NODE and * ATTRIBUTE_NODE and nodes created with a DOM Level 1 * method, such as createElement from the * Document interface, this is always null. * * @throws org.w3c.dom.DOMException INVALID_CHARACTER_ERR: Raised if the specified prefix contains an * illegal character, per the XML 1.0 specification . *
NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. *
NAMESPACE_ERR: Raised if the specified prefix is * malformed per the Namespaces in XML specification, if the * namespaceURI of this node is null, if the * specified prefix is "xml" and the namespaceURI of this * node is different from "http://www.w3.org/XML/1998/namespace", if * this node is an attribute and the specified prefix is "xmlns" and * the namespaceURI of this node is different from " * http://www.w3.org/2000/xmlns/", or if this node is an attribute and * the qualifiedName of this node is "xmlns" . * @since DOM Level 2 */ public void setPrefix(String prefix) { this.prefix = prefix; } /** * Set the owner document * * @param doc */ public void setOwnerDocument(Document doc) { document = doc; } /** * The Document object associated with this node. This is * also the Document object used to create new nodes. When * this node is a Document or a DocumentType * which is not used with any Document yet, this is * null. */ public Document getOwnerDocument() { if(document == null) { NodeImpl node = getParent(); if (node != null) { return node.getOwnerDocument(); } } return document; } /** * A NamedNodeMap containing the attributes of this node (if * it is an Element) or null otherwise. */ public NamedNodeMap getAttributes() { // make first it is editable. makeAttributesEditable(); return convertAttrSAXtoDOM(attributes); } /** * The first child of this node. If there is no such node, this returns * null. */ public Node getFirstChild() { if (children != null && !children.isEmpty()) { return (Node) children.get(0); } else { return null; } } /** * The last child of this node. If there is no such node, this returns * null. */ public Node getLastChild() { if (children != null && !children.isEmpty()) { return (Node) children.get(children.size() - 1); } else { return null; } } /** * The node immediately following this node. If there is no such node, * this returns null. */ public Node getNextSibling() { SOAPElement parent = getParentElement(); if (parent == null) { return null; } Iterator iter = parent.getChildElements(); Node nextSibling = null; while (iter.hasNext()) { if (iter.next() == this) { if (iter.hasNext()) { return (Node) iter.next(); } else { return null; } } } return nextSibling; // should be null. } /** * The parent of this node. All nodes, except Attr, * Document, DocumentFragment, * Entity, and Notation may have a parent. * However, if a node has just been created and not yet added to the * tree, or if it has been removed from the tree, this is * null. */ public Node getParentNode() { return (Node) getParent(); } /** * The node immediately preceding this node. If there is no such node, * this returns null. */ public Node getPreviousSibling() { SOAPElement parent = getParentElement(); if (parent == null) { return null; } NodeList nl = parent.getChildNodes(); int len = nl.getLength(); int i = 0; Node previousSibling = null; while (i < len) { if (nl.item(i) == this) { return previousSibling; } previousSibling = nl.item(i); i++; } return previousSibling; // should be null. } /** * Returns a duplicate of this node, i.e., serves as a generic copy * constructor for nodes. The duplicate node has no parent; ( * parentNode is null.). *
Cloning an Element copies all attributes and their * values, including those generated by the XML processor to represent * defaulted attributes, but this method does not copy any text it * contains unless it is a deep clone, since the text is contained in a * child Text node. Cloning an Attribute * directly, as opposed to be cloned as part of an Element * cloning operation, returns a specified attribute ( * specified is true). Cloning any other type * of node simply returns a copy of this node. *
Note that cloning an immutable subtree results in a mutable copy, * but the children of an EntityReference clone are readonly * . In addition, clones of unspecified Attr nodes are * specified. And, cloning Document, * DocumentType, Entity, and * Notation nodes is implementation dependent. * * @param deep If true, recursively clone the subtree under * the specified node; if false, clone only the node * itself (and its attributes, if it is an Element). * @return The duplicate node. */ public Node cloneNode(boolean deep) { return new NodeImpl(textRep); } /** * A NodeList that contains all children of this node. If * there are no children, this is a NodeList containing no * nodes. */ public NodeList getChildNodes() { if (children == null) { return NodeListImpl.EMPTY_NODELIST; } else { return new NodeListImpl(children); } } /** * Tests whether the DOM implementation implements a specific feature and * that feature is supported by this node. * * @param feature The name of the feature to test. This is the same name * which can be passed to the method hasFeature on * DOMImplementation. * @param version This is the version number of the feature to test. In * Level 2, version 1, this is the string "2.0". If the version is not * specified, supporting any version of the feature will cause the * method to return true. * @return Returns true if the specified feature is * supported on this node, false otherwise. * @since DOM Level 2 */ public boolean isSupported(String feature, String version) { return false; //TODO: Fix this for SAAJ 1.2 Implementation } /** * Adds the node newChild to the end of the list of children * of this node. If the newChild is already in the tree, it * is first removed. * * @param newChild The node to add.If it is a * DocumentFragment object, the entire contents of the * document fragment are moved into the child list of this node * @return The node added. * @throws org.w3c.dom.DOMException HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not * allow children of the type of the newChild node, or if * the node to append is one of this node's ancestors or this node * itself. *
WRONG_DOCUMENT_ERR: Raised if newChild was created * from a different document than the one that created this node. *
NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly or * if the previous parent of the node being inserted is readonly. * */ public Node appendChild(Node newChild) throws DOMException { if (newChild == null) { throw new DOMException (DOMException.HIERARCHY_REQUEST_ERR, "Can't append a null node."); } initializeChildren(); // per DOM spec - must remove from tree. If newChild.parent == null, // detachNode() does nothing. So this shouldn't hurt performace of // serializers. ((NodeImpl) newChild).detachNode(); children.add(newChild); ((NodeImpl) newChild).parent = this; setDirty(); return newChild; } /** * Removes the child node indicated by oldChild from the list * of children, and returns it. * * @param oldChild The node being removed. * @return The node removed. * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. *
NOT_FOUND_ERR: Raised if oldChild is not a child of * this node. */ public Node removeChild(Node oldChild) throws DOMException { if (removeNodeFromChildList((NodeImpl) oldChild)) { setDirty(); return oldChild; } throw new DOMException(DOMException.NOT_FOUND_ERR, "NodeImpl Not found"); } private boolean removeNodeFromChildList(NodeImpl n) { boolean removed = false; initializeChildren(); final Iterator itr = children.iterator(); while (itr.hasNext()) { final NodeImpl node = (NodeImpl) itr.next(); if (node == n) { removed = true; itr.remove(); } } return removed; } /** * Inserts the node newChild before the existing child node * refChild. If refChild is null, * insert newChild at the end of the list of children. *
If newChild is a DocumentFragment object, * all of its children are inserted, in the same order, before * refChild. If the newChild is already in the * tree, it is first removed. * * @param newChild The node to insert. * @param refChild The reference node, i.e., the node before which the * new node must be inserted. * @return The node being inserted. * @throws org.w3c.dom.DOMException HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not * allow children of the type of the newChild node, or if * the node to insert is one of this node's ancestors or this node * itself. *
WRONG_DOCUMENT_ERR: Raised if newChild was created * from a different document than the one that created this node. *
NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly or * if the parent of the node being inserted is readonly. *
NOT_FOUND_ERR: Raised if refChild is not a child of * this node. */ public Node insertBefore(Node newChild, Node refChild) throws DOMException { initializeChildren(); int position = children.indexOf(refChild); if (position < 0) { position = 0; } children.add(position, newChild); setDirty(); return newChild; } /** * Replaces the child node oldChild with newChild * in the list of children, and returns the oldChild node. *
If newChild is a DocumentFragment object, * oldChild is replaced by all of the * DocumentFragment children, which are inserted in the * same order. If the newChild is already in the tree, it * is first removed. * * @param newChild The new node to put in the child list. * @param oldChild The node being replaced in the list. * @return The node replaced. * @throws org.w3c.dom.DOMException HIERARCHY_REQUEST_ERR: Raised if this node is of a type that does not * allow children of the type of the newChild node, or if * the node to put in is one of this node's ancestors or this node * itself. *
WRONG_DOCUMENT_ERR: Raised if newChild was created * from a different document than the one that created this node. *
NO_MODIFICATION_ALLOWED_ERR: Raised if this node or the parent of * the new node is readonly. *
NOT_FOUND_ERR: Raised if oldChild is not a child of * this node. */ public Node replaceChild(Node newChild, Node oldChild) throws DOMException { initializeChildren(); int position = children.indexOf(oldChild); if (position < 0) { throw new DOMException(DOMException.NOT_FOUND_ERR, "NodeImpl Not found"); } children.remove(position); children.add(position, newChild); setDirty(); return oldChild; } /** * Returns the the value of the immediate child of this Node * object if a child exists and its value is text. * * @return a String with the text of the immediate child of * this Node object if (1) there is a child and * (2) the child is a Text object; * null otherwise */ public String getValue() { return textRep.getNodeValue(); } /** * Sets the parent of this Node object to the given * SOAPElement object. * * @param parent the SOAPElement object to be set as * the parent of this Node object * @throws javax.xml.soap.SOAPException if there is a problem in setting the * parent to the given element * @see #getParentElement() getParentElement() */ public void setParentElement(SOAPElement parent) throws SOAPException { if (parent == null) throw new IllegalArgumentException( Messages.getMessage("nullParent00")); try { setParent((NodeImpl) parent); } catch (Throwable t) { throw new SOAPException(t); } } /** * Returns the parent element of this Node object. * This method can throw an UnsupportedOperationException * if the tree is not kept in memory. * * @return the SOAPElement object that is the parent of * this Node object or null if this * Node object is root * @throws UnsupportedOperationException if the whole tree is not kept in memory * @see #setParentElement(javax.xml.soap.SOAPElement) setParentElement(javax.xml.soap.SOAPElement) */ public SOAPElement getParentElement() { return (SOAPElement) getParent(); } /** * Removes this Node object from the tree. Once * removed, this node can be garbage collected if there are no * application references to it. */ public void detachNode() { setDirty(); if (parent != null) { parent.removeChild(this); parent = null; } } /** * Notifies the implementation that this Node * object is no longer being used by the application and that the * implementation is free to reuse this object for nodes that may * be created later. *

* Calling the method recycleNode implies that the method * detachNode has been called previously. */ public void recycleNode() { //TODO: Fix this for SAAJ 1.2 Implementation } /** * If this is a Text node then this method will set its value, otherwise it * sets the value of the immediate (Text) child of this node. The value of * the immediate child of this node can be set only if, there is one child * node and that node is a Text node, or if there are no children in which * case a child Text node will be created. * * @param value the text to set * @throws IllegalStateException if the node is not a Text node and * either has more than one child node or has a child node that * is not a Text node */ public void setValue(String value) { if (this instanceof org.apache.axis.message.Text) { setNodeValue(value); } else if (children != null) { if (children.size() != 1) { throw new IllegalStateException( "setValue() may not be called on a non-Text node with more than one child." ); } javax.xml.soap.Node child = (javax.xml.soap.Node) children.get(0); if (!(child instanceof org.apache.axis.message.Text)) { throw new IllegalStateException( "setValue() may not be called on a non-Text node with a non-Text child." ); } ((javax.xml.soap.Text)child).setNodeValue(value); } else { appendChild(new org.apache.axis.message.Text(value)); } } /** * make the attributes editable * * @return AttributesImpl */ protected AttributesImpl makeAttributesEditable() { if (attributes == null || attributes instanceof NullAttributes) { attributes = new AttributesImpl(); } else if (!(attributes instanceof AttributesImpl)) { attributes = new AttributesImpl(attributes); } return (AttributesImpl) attributes; } /** * The internal representation of Attributes cannot help being changed * It is because Attribute is not immutible Type, so if we keep out value and * just return it in another form, the application may chnae it, which we cannot * detect without some kind back track method (call back notifying the chnage.) * I am not sure which approach is better. */ protected NamedNodeMap convertAttrSAXtoDOM(Attributes saxAttr) { try { org.w3c.dom.Document doc = org.apache.axis.utils.XMLUtils.newDocument(); AttributesImpl saxAttrs = (AttributesImpl) saxAttr; NamedNodeMap domAttributes = new NamedNodeMapImpl(); for (int i = 0; i < saxAttrs.getLength(); i++) { String uri = saxAttrs.getURI(i); String qname = saxAttrs.getQName(i); String value = saxAttrs.getValue(i); if (uri != null && uri.trim().length() > 0) { // filterring out the tricky method to differentiate the null namespace // -ware case if (NULL_URI_NAME.equals(uri)) { uri = null; } Attr attr = doc.createAttributeNS(uri, qname); attr.setValue(value); domAttributes.setNamedItemNS(attr); } else { Attr attr = doc.createAttribute(qname); attr.setValue(value); domAttributes.setNamedItem(attr); } } return domAttributes; } catch (Exception ex) { log.error(Messages.getMessage("saxToDomFailed00"),ex); return null; } } /** * Initialize the children array */ protected void initializeChildren() { if (children == null) { children = new ArrayList(); } } /** * get the parent node * @return parent node */ protected NodeImpl getParent() { return parent; } /** * Set the parent node and invoke appendChild(this) to * add this node to the parent's list of children. * @param parent * @throws SOAPException */ protected void setParent(NodeImpl parent) throws SOAPException { if (this.parent == parent) { return; } if (this.parent != null) { this.parent.removeChild(this); } if (parent != null) { parent.appendChild(this); } this.setDirty(); this.parent = parent; } /** * print the contents of this node * @param context * @throws Exception */ public void output(SerializationContext context) throws Exception { if (textRep == null) return; boolean oldPretty = context.getPretty(); context.setPretty(false); if (textRep instanceof CDATASection) { context.writeString(""); } else if (textRep instanceof Comment) { context.writeString(""); } else if (textRep instanceof Text) { context.writeSafeString(((Text) textRep).getData()); } context.setPretty(oldPretty); } /** * get the dirty bit * @return */ public boolean isDirty() { return _isDirty; } /** * set the dirty bit. will also set our parent as dirty, if there is one. * Note that clearing the dirty bit does not propagate upwards. * @param dirty new value of the dirty bit */ public void setDirty(boolean dirty) { _isDirty = dirty; if (_isDirty && parent != null) { ((NodeImpl) parent).setDirty(); } } public void setDirty() { _isDirty = true; if (parent != null) { ((NodeImpl) parent).setDirty(); } } /* clear dirty flag recursively */ public void reset() { if (children != null) { for (int i=0; i