/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ws.core.soap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import org.jboss.logging.Logger;
import org.jboss.util.NotImplementedException;
import org.jboss.ws.WSException;
import org.jboss.wsf.common.DOMUtils;
import org.jboss.wsf.common.DOMWriter;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;
/**
* A representation of a node (element) in an XML document.
* This interface extends the standard DOM Node interface with methods for getting and setting the value of a node,
* for getting and setting the parent of a node, and for removing a node.
*
* When creating a DOM2 tree the objects maintained by the tree are <code>org.w3c.dom.Node</code> objects
* and not <code>javax.xml.soap.Node</code> objects.
* <p/>
* This implementation shields the client from the the underlying DOM2 tree, returning <code>javax.xml.soap.Node</code>
* objects.
*
* @author Thomas.Diesler@jboss.org
*/
public class NodeImpl implements javax.xml.soap.Node
{
// provide logging
private static Logger log = Logger.getLogger(NodeImpl.class);
// The parent of this Node
protected SOAPElementImpl soapParent;
// This org.w3c.dom.Node
protected org.w3c.dom.Node domNode;
// A list of soap children
private List<NodeImpl> soapChildren = new ArrayList<NodeImpl>();
// A hash with the user data
private Map<String, Object> userData;
/** Construct the Node for a given org.w3c.dom.Node
*
* This constructor is used:
*
* 1) SOAPElement construction
* 2) Text construction
*/
NodeImpl(org.w3c.dom.Node node)
{
// Method selection in Java is done at compile time
// Late binding does not work in this case
if (node instanceof NodeImpl)
throw new IllegalArgumentException("Copy constructor should be used");
domNode = node;
// SOAP child elements should be constructed externally
if (DOMUtils.hasChildElements(node))
throw new IllegalArgumentException("Node cannot have child elements");
}
/** The copy constructor used when converting types (i.e. SOAPElement -> SOAPHeaderElement)
*/
NodeImpl(NodeImpl node)
{
soapParent = node.soapParent;
domNode = node.domNode;
Iterator i = node.soapChildren.iterator();
while (i.hasNext())
{
NodeImpl childNode = (NodeImpl)i.next();
childNode.soapParent = (SOAPElementImpl)this;
soapChildren.add(childNode);
}
}
// javax.xml.soap.Node *********************************************************************************************
/**
* Removes this Node object from the tree.
*/
public void detachNode()
{
org.w3c.dom.Node domParent = domNode.getParentNode();
if (domParent != null)
domParent.removeChild(domNode);
if (soapParent != null)
((NodeImpl)soapParent).soapChildren.remove(this);
soapParent = null;
}
/**
* Returns the parent node 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
*/
public SOAPElement getParentElement()
{
return soapParent;
}
/**
* 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 node
*/
public void setParentElement(SOAPElement parent) throws SOAPException
{
// detach from the old parent
if (soapParent != null)
detachNode();
soapParent = (SOAPElementImpl)parent;
}
/**
* Returns the value of this node if this is a Text node or the value of the immediate child of this node otherwise.
* <p/>
* If there is an immediate child of this Node that it is a Text node then it's value will be returned.
* If there is more than one Text node then the value of the first Text Node will be returned.
* Otherwise null is returned.
*
* @return a String with the text of this node if this is a Text node or the text contained by the first immediate
* child of this Node object that is a Text object if such a child exists; null otherwise.
*/
public String getValue()
{
// The Text node should overwrite getValue
if (this instanceof javax.xml.soap.Text)
throw new WSException("javax.xml.soap.Text should take care of this");
String nodeValue = null;
org.w3c.dom.Node child = (org.w3c.dom.Node)getFirstChild();
if (child instanceof org.w3c.dom.Text)
nodeValue = ((org.w3c.dom.Text)child).getNodeValue();
return nodeValue;
}
/**
* 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.
* <p/>
* 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 A value string
* @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)
{
// The Text node should overwrite setValue
if (this instanceof javax.xml.soap.Text)
throw new WSException("javax.xml.soap.Text should take care of this");
org.w3c.dom.Node child = (org.w3c.dom.Node)getFirstChild();
if (child instanceof org.w3c.dom.Text)
((org.w3c.dom.Text)child).setNodeValue(value);
if (child == null)
{
child = domNode.getOwnerDocument().createTextNode(value);
appendChild(new TextImpl(child));
}
}
/**
* 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()
{
}
private List<NodeImpl> convertDocumentFragment(DocumentFragment docFragment) throws DOMException
{
List<NodeImpl> list = new ArrayList<NodeImpl>();
try
{
SOAPFactoryImpl soapFactory = new SOAPFactoryImpl();
for (Node node = docFragment.getFirstChild(); node != null; node = node.getNextSibling())
{
switch (node.getNodeType())
{
case Node.ELEMENT_NODE:
{
SOAPElementImpl soapChild = (SOAPElementImpl)soapFactory.createElement((Element)node);
list.add(soapChild);
break;
}
case Node.TEXT_NODE:
{
TextImpl text = new TextImpl(node);
list.add(text);
break;
}
case Node.CDATA_SECTION_NODE:
{
TextImpl text = new TextImpl(node);
list.add(text);
break;
}
}
}
}
catch (SOAPException ex)
{
throw new DOMException(DOMException.INVALID_STATE_ERR, "Could not convert a document fragment to a node");
}
return list;
}
// BEGIN org.w3c.dom.Node *******************************************************************************************
public String getNodeName()
{
return domNode.getNodeName();
}
public String getNodeValue() throws DOMException
{
return domNode.getNodeValue();
}
public void setNodeValue(String nodeValue) throws DOMException
{
domNode.setNodeValue(nodeValue);
}
public short getNodeType()
{
return domNode.getNodeType();
}
public org.w3c.dom.Node getParentNode()
{
assertSOAPParent();
return soapParent;
}
public NodeList getChildNodes()
{
return new NodeListImpl(soapChildren);
}
public org.w3c.dom.Node getFirstChild()
{
NodeImpl child = null;
org.w3c.dom.Node domChild = domNode.getFirstChild();
if (domChild != null)
{
child = (NodeImpl)soapChildren.get(0);
if (domChild != child.domNode)
throw new WSException("Inconsistent node, child lists not synchronized");
}
return child;
}
public org.w3c.dom.Node getLastChild()
{
NodeImpl child = null;
org.w3c.dom.Node domChild = domNode.getLastChild();
if (domChild != null)
{
child = (NodeImpl)soapChildren.get(soapChildren.size() - 1);
if (domChild != child.domNode)
throw new WSException("Inconsistent node, child lists not synchronized");
}
return child;
}
public org.w3c.dom.Node getPreviousSibling()
{
assertSOAPParent();
NodeImpl sibling = null;
if (soapParent != null)
{
List children = ((NodeImpl)soapParent).soapChildren;
for (int i = 0; i < children.size(); i++)
{
NodeImpl node = (NodeImpl)children.get(i);
if (node == this && i > 0)
{
sibling = (NodeImpl)children.get(i - 1);
break;
}
}
if (sibling != null && sibling.domNode != domNode.getPreviousSibling())
throw new WSException("Inconsistent node, child lists not synchronized");
}
return sibling;
}
public org.w3c.dom.Node getNextSibling()
{
assertSOAPParent();
NodeImpl sibling = null;
if (soapParent != null)
{
List children = ((NodeImpl)soapParent).soapChildren;
for (int i = 0; i < children.size(); i++)
{
NodeImpl node = (NodeImpl)children.get(i);
if (node == this && (i + 1) < children.size())
{
sibling = (NodeImpl)children.get(i + 1);
break;
}
}
if (sibling != null && sibling.domNode != domNode.getNextSibling())
throw new WSException("Inconsistent node, child lists not synchronized");
}
return sibling;
}
public NamedNodeMap getAttributes()
{
return domNode.getAttributes();
}
public Document getOwnerDocument()
{
// Climb the tree in hopes of finding the soap envelope.
// If it's not there (a detached subtree), then we return a non-associated document
if (soapParent == null)
return new SOAPDocument();
return soapParent.getOwnerDocument();
}
public org.w3c.dom.Node insertBefore(org.w3c.dom.Node newChild, org.w3c.dom.Node refChild) throws DOMException
{
// DOM says that if refChild is null, an append is performed
if (refChild == null)
return appendChild(newChild);
newChild = convertDOMNode(newChild);
refChild = convertDOMNode(refChild);
if (newChild instanceof DocumentFragment)
{
List<NodeImpl> list = convertDocumentFragment((DocumentFragment)newChild);
for (NodeImpl node : list)
{
insertBefore(node, refChild);
}
return newChild;
}
int index = soapChildren.indexOf(refChild);
if (index < 0)
throw new IllegalArgumentException("Cannot find refChild in list of javax.xml.soap.Node children");
NodeImpl soapNewNode = (NodeImpl)newChild;
soapNewNode.detachNode();
NodeImpl soapRefNode = (NodeImpl)refChild;
domNode.insertBefore(soapNewNode.domNode, soapRefNode.domNode);
soapChildren.add(index, soapNewNode);
soapNewNode.soapParent = (SOAPElementImpl)this;
return newChild;
}
public org.w3c.dom.Node replaceChild(org.w3c.dom.Node newChild, org.w3c.dom.Node oldChild) throws DOMException
{
newChild = convertDOMNode(newChild);
oldChild = convertDOMNode(oldChild);
if (newChild instanceof DocumentFragment)
{
insertBefore(newChild, oldChild);
((NodeImpl)oldChild).detachNode();
return newChild;
}
int index = soapChildren.indexOf(oldChild);
if (index < 0)
throw new DOMException(DOMException.NOT_FOUND_ERR, "Cannot find oldChild in list of javax.xml.soap.Node children");
NodeImpl soapNewNode = (NodeImpl)newChild;
NodeImpl soapOldNode = (NodeImpl)oldChild;
soapNewNode.detachNode();
if (soapNewNode.domNode != soapOldNode.domNode)
domNode.replaceChild(soapNewNode.domNode, soapOldNode.domNode);
soapChildren.remove(index);
soapChildren.add(index, soapNewNode);
soapNewNode.soapParent = soapOldNode.soapParent;
soapOldNode.soapParent = null;
return newChild;
}
public org.w3c.dom.Node removeChild(org.w3c.dom.Node oldChild) throws DOMException
{
oldChild = convertDOMNode(oldChild);
int index = soapChildren.indexOf(oldChild);
if (index < 0)
throw new DOMException(DOMException.NOT_FOUND_ERR, "Cannot find oldChild in list of javax.xml.soap.Node children");
NodeImpl soapOldNode = (NodeImpl)oldChild;
domNode.removeChild(soapOldNode.domNode);
soapChildren.remove(index);
soapOldNode.soapParent = null;
return oldChild;
}
public org.w3c.dom.Node appendChild(org.w3c.dom.Node newChild) throws DOMException
{
newChild = convertDOMNode(newChild);
if (newChild instanceof DocumentFragment)
{
List<NodeImpl> list = convertDocumentFragment((DocumentFragment)newChild);
for (NodeImpl node : list)
{
appendChild(node);
}
return newChild;
}
if ((this instanceof SOAPElementImpl) == false)
throw new DOMException(DOMException.INVALID_ACCESS_ERR, "Cannot append child to this node: " + this);
NodeImpl soapNode = (NodeImpl)newChild;
soapNode.detachNode();
domNode.appendChild(soapNode.domNode);
soapNode.soapParent = (SOAPElementImpl)this;
soapChildren.add(soapNode);
return newChild;
}
public boolean hasChildNodes()
{
return domNode.hasChildNodes();
}
public org.w3c.dom.Node cloneNode(boolean deep)
{
return domNode.cloneNode(deep);
}
public void normalize()
{
domNode.normalize();
}
public boolean isSupported(String feature, String version)
{
return domNode.isSupported(feature, version);
}
public String getNamespaceURI()
{
return domNode.getNamespaceURI();
}
public String getPrefix()
{
return domNode.getPrefix();
}
public void setPrefix(String prefix) throws DOMException
{
domNode.setPrefix(prefix);
}
public String getLocalName()
{
return domNode.getLocalName();
}
public boolean hasAttributes()
{
return domNode.hasAttributes();
}
public int hashCode()
{
return domNode.hashCode();
}
public String toString()
{
return super.toString() + "[" + domNode.toString() + "]";
}
private Node convertDOMNode(org.w3c.dom.Node node)
{
Node retNode;
if (node instanceof NodeImpl)
{
retNode = node;
}
else if (node instanceof DocumentFragment)
{
retNode = new DocumentFragmentImpl((DocumentFragment)node);
}
else if (node instanceof org.w3c.dom.Text)
{
retNode = new TextImpl(node);
}
else if (node instanceof org.w3c.dom.Comment)
{
retNode = new TextImpl(node);
}
else if (node instanceof org.w3c.dom.Element)
{
try
{
retNode = new SOAPFactoryImpl().createElement((Element)node);
}
catch (SOAPException ex)
{
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "CAnnot convert to SOAP element: " + node);
}
}
else
{
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Operation not supported on this type of node: " + node);
}
return retNode;
}
private void assertSOAPParent()
{
org.w3c.dom.Node domParent = domNode.getParentNode();
if (domParent != null && soapParent == null)
throw new WSException("Inconsistent node, has a DOM parent but no SOAP parent [" + this + "] " + DOMWriter.printNode(this, false));
if (domParent != null && soapParent != null && domParent != soapParent.domNode)
throw new WSException("Inconsistent node, SOAP parent is not identical with DOM parent [" + this + "] " + DOMWriter.printNode(this, false));
}
// END org.w3c.dom.Node *******************************************************************************************
// BEGIN org.w3c.dom.Node DOM Level 3 *****************************************************************************
public short compareDocumentPosition(Node other) throws DOMException
{
// FIXME compareDocumentPosition
throw new NotImplementedException("compareDocumentPosition");
}
public String getBaseURI()
{
// FIXME getBaseURI
throw new NotImplementedException("getBaseURI");
}
public Object getFeature(String feature, String version)
{
// FIXME getFeature
throw new NotImplementedException("getFeature");
}
public String getTextContent() throws DOMException
{
return domNode.getTextContent();
}
public Object getUserData(String key)
{
if (userData != null)
{
return userData.get(key);
}
return null;
}
public boolean isDefaultNamespace(String namespaceURI)
{
// FIXME isDefaultNamespace
throw new NotImplementedException("isDefaultNamespace");
}
public boolean isEqualNode(Node arg)
{
return domNode.isEqualNode(arg);
}
public boolean isSameNode(Node other)
{
// FIXME isSameNode
throw new NotImplementedException("isSameNode");
}
public String lookupNamespaceURI(String prefix)
{
// FIXME lookupNamespaceURI
throw new NotImplementedException("lookupNamespaceURI");
}
public String lookupPrefix(String namespaceURI)
{
// FIXME lookupPrefix
throw new NotImplementedException("lookupPrefix");
}
public void setTextContent(String textContent) throws DOMException
{
NodeList nodes = getChildNodes();
for (int i = 0; i < nodes.getLength(); i++)
{
removeChild((Node)nodes.item(i));
}
if (textContent != null && textContent.length() > 0)
{
Node node = domNode.getOwnerDocument().createTextNode(textContent);
appendChild(node);
}
}
public Object setUserData(String key, Object data, UserDataHandler handler)
{
if (userData == null)
{
userData = new HashMap<String, Object>();
}
return userData.put(key, data);
}
// END org.w3c.dom.Node DOM Level 3 *****************************************************************************
}