/*
* 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