Hi axis-users,
The problem (null getters javax.xml.soap.Node) was me and not Axis (how
surprising). One detail is that if you are producing the SOAPElement
from a DOM tree, you have to construct the DOM tree with a
namespace-aware parser.
I attach the following corrected code for turning a SOAPElement into a
String, plus unit tests. Hope the cause for embarrasment isn't too
great.
I'll shut up now. Cheers,
--eric
Eric originally posted:
> I was wondering if anybody has experienced a similar problem. I am
> trying to convert from a SOAPElement to a String, but i seem to be
> getting empty strings when i use the getLocalName(), getURI(), etc
> functions in javax.xml.soap.Name.
/* -*- Mode: java; indent-tabs-mode:nil; c-basic-offset: 2 -*-
* ex: set sw=2 expandtab:
* $Id:$
*
* This software is released under a BSD-style license.
* Please see the LICENSE file in this distribution.
*/
package soapical.util;
import com.megginson.sax.XMLWriter;
import java.io.StringWriter;
import java.lang.IllegalArgumentException;
import java.util.Iterator;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFactory;
import javax.xml.soap.Text;
import org.apache.log4j.Logger;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* Miscellaneous utilities for converting between SOAP and other stuff
*
* @version
* $Revision:$<br>
* $Date:$<br>
* @author Eric Kow (kow at loria point fr)
*/
public class SOAPUtil {
static Logger _logger = Logger.getLogger(SOAPUtil.class);
final static String XMLNS = "xmlns";
/**
* I stole this from Sun at
* http://access1.sun.com/techarticles/SOAP_DOM/SOAP_DOM.html
* thanks to their use of the BSD license (same as this one!)
*
* Standard recursion, folks, copying the tree, nothing to see. Why i
* didn't think to do this myself makes me feel like a very very very
* dumb programmer
*/
public static SOAPElement convertDOMToSOAPElement(SOAPEnvelope env,
org.w3c.dom.Node DOMNode)
throws SOAPException{
//Test that DOMNode is of type org.w3c.dom.Node.ELEMENT_NODE.
if((DOMNode.getNodeType()) != org.w3c.dom.Node.ELEMENT_NODE)
throw new SOAPException("DOMNode must of type ELEMENT_NODE");
SOAPFactory factory = SOAPFactory.newInstance();
Name sn = factory.createName(DOMNode.getLocalName(),
DOMNode.getPrefix(),
DOMNode.getNamespaceURI());
SOAPElement se = factory.createElement(sn);
if (DOMNode.hasAttributes()){
NamedNodeMap DOMAttributes = DOMNode.getAttributes();
int noOfAttributes = DOMAttributes.getLength();
for(int i = 0; i < noOfAttributes; i++){
org.w3c.dom.Node attr = DOMAttributes.item(i);
se.addAttribute(env.createName(attr.getLocalName(), attr.getPrefix(),
attr.getNamespaceURI()), attr.getNodeValue());
}
}
if(DOMNode.hasChildNodes()){
NodeList children = DOMNode.getChildNodes();
for(int i = 0; i< children.getLength(); i++){
org.w3c.dom.Node child = children.item(i);
switch(child.getNodeType()){
case org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE: break;
case org.w3c.dom.Node.DOCUMENT_TYPE_NODE: break;
case org.w3c.dom.Node.CDATA_SECTION_NODE:
case org.w3c.dom.Node.COMMENT_NODE:
case org.w3c.dom.Node.TEXT_NODE:
se.addTextNode(child.getNodeValue());
break;
default:
se.addChildElement(convertDOMToSOAPElement(env, child));
}
}//end of for
}//end of if
return se;
}
/**
* Convert a SOAPElement to XML string
*/
public static String convertSOAPToString(SOAPElement element) {
StringWriter writer = new StringWriter();
XMLWriter xmlWriter = new XMLWriter(writer);
try {
xmlWriter.startDocument();
convertSOAPElementToString_helper(element, xmlWriter);
xmlWriter.endDocument();
return writer.toString();
} catch (SAXException e) {
// FIXME: i wonder if we should do something with this exception;
// we're basically not expecting to catch it, and it really
// wouldn't make any sense to throw it since it wouldn't be the
// users fault, but our own...
_logger.error("problem converting SOAP to String: " + e);
return null;
}
}
/**
* Convert a SOAP Node to XML string.
*/
private static void
convertSOAPElementToString_helper(javax.xml.soap.Node node,
XMLWriter handler)
throws SAXException
{
// a null node is unlikely but theoretically possible
if (node == null) return;
if (handler == null) throw new IllegalArgumentException("null xml writer");
if ( Text.class.isInstance(node) ) {
// if the node is text
String value = null;
SOAPElement parent = node.getParentElement();
if (parent == null) {
_logger.warn("Text node with null parent: " + node);
// this is not guaranteed to work
value = node.toString();
} else {
value = parent.getValue();
}
if (value == null) {
_logger.error("null value for text node: " + node);
} else {
handler.characters( value.toCharArray(), 0, value.length() );
}
} else if ( SOAPElement.class.isInstance(node) ) {
// if the node is a SOAP DOM tree
SOAPElement element = (SOAPElement)node;
// -- [ handle SOAPElement node ]
Name name = element.getElementName();
//_logger.debug( (org.apache.axis.message.PrefixedQName)name );
String URI = name.getURI();
String localName = name.getLocalName();
String qName = name.getQualifiedName();
// detect elemens in the local prefix
handler.setPrefix(URI, name.getPrefix() );
//_logger.debug(node + " u[" + URI + "] " + " l[" + localName + "]" + " q[" +
qName + "]");
// get the attributes
AttributesImpl attrs = new AttributesImpl();
for (Iterator iter = element.getAllAttributes();
iter.hasNext(); )
{
Name attrName = (Name)(iter.next());
String attrURI = attrName.getURI();
String attrLocalName = attrName.getLocalName();
String attrQName = attrName.getQualifiedName();
String attrPrefix = attrName.getPrefix();
String attrValue = element.getAttributeValue(attrName);
handler.setPrefix( attrURI, attrPrefix );
//_logger.debug("attribute : " + attrQName + " value: " + attrValue);
if (! XMLNS.equals(attrPrefix) &&
! XMLNS.equals(attrLocalName))
{
// FIXME: i don't know what to put down for attr type...
// would this information be in the SOAPElement? would it matter?
attrs.addAttribute(attrURI, attrLocalName, attrQName,
"",
attrValue);
}
}
// open the current node
handler.startElement(URI, localName, qName, attrs);
// write the node's children
for (Iterator iter = element.getChildElements();
iter.hasNext(); )
{
javax.xml.soap.Node child = (javax.xml.soap.Node)(iter.next());
convertSOAPElementToString_helper(child, handler);
}
// close off the current node
handler.endElement(URI, localName, qName);
// -- end [ handle SOAP Node ]
}
}
}
package soapical.util;
import junit.framework.TestCase;
// JUnitDoclet begin import
import soapical.util.SOAPUtil;
import java.io.File;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPElement;
import org.xml.sax.InputSource;
import java.io.StringReader;
import org.apache.log4j.Logger;
import java.io.StringWriter;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.LineSeparator;
import org.apache.xml.serialize.XMLSerializer;
// JUnitDoclet end import
/**
* Generated by JUnitDoclet, a tool provided by
* ObjectFab GmbH under LGPL.
* Please see www.junitdoclet.org, www.gnu.org
* and www.objectfab.de for informations about
* the tool, the licence and the authors.
*/
public class SOAPUtilTest
// JUnitDoclet begin extends_implements
extends TestCase
// JUnitDoclet end extends_implements
{
// JUnitDoclet begin class
static final File TEST_ROOT = new File("etc/testing");
static Logger _logger = Logger.getLogger(SOAPUtilTest.class);
soapical.util.SOAPUtil soaputil = null;
// JUnitDoclet end class
public SOAPUtilTest(String name) {
// JUnitDoclet begin method SOAPUtilTest
super(name);
// JUnitDoclet end method SOAPUtilTest
}
public soapical.util.SOAPUtil createInstance() throws Exception {
// JUnitDoclet begin method testcase.createInstance
return new soapical.util.SOAPUtil();
// JUnitDoclet end method testcase.createInstance
}
protected void setUp() throws Exception {
// JUnitDoclet begin method testcase.setUp
super.setUp();
soaputil = createInstance();
// JUnitDoclet end method testcase.setUp
}
protected void tearDown() throws Exception {
// JUnitDoclet begin method testcase.tearDown
soaputil = null;
super.tearDown();
// JUnitDoclet end method testcase.tearDown
}
public void testConvertDOMToSOAPElement() throws Exception {
// JUnitDoclet begin method convertDOMToSOAPElement
// JUnitDoclet end method convertDOMToSOAPElement
}
public void testConvertSOAPToString() throws Exception {
// JUnitDoclet begin method convertSOAPToString
// we do this test in a roundabout way: by reading in a test
// document, converting it to SOAP, converting it back, and
// making sure that the resulting XML trees are the same.
//
// a fundamental flaw in this is that it only tests that
// convertDOMtoSOAP (and SOAP back to DOM) give the same thing,
// not really that each individual function works
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
MessageFactory msgFactory = MessageFactory.newInstance();
// read in and parse some sample data... this gives us a
// DOM tree
File file = new File(TEST_ROOT, "sample_mmil.xml");
Document parse = builder.parse(file);
Element parseRoot = parse.getDocumentElement();
assertTrue(parseRoot != null);
StringWriter parseWriter = new StringWriter();
OutputFormat format = new OutputFormat();
XMLSerializer serializer = new XMLSerializer (parseWriter, format);
serializer.asDOMSerializer();
serializer.serialize(parse);
_logger.info("DOM: " + parseWriter.toString() );
// convert the DOM tree to SOAP
// ideally we would have a seperate function for reading a file
// directly into a SOAP element
SOAPEnvelope soapEnvelope =
msgFactory.createMessage().getSOAPPart().getEnvelope();
SOAPElement soap =
SOAPUtil.convertDOMToSOAPElement( soapEnvelope, parseRoot );
// convert the SOAP to a String (this is the part we want to test)
String soapString =
SOAPUtil.convertSOAPToString( soap );
_logger.info("SOAP -> String: " + soapString);
assertTrue( "SOAPElement is non-null, nonempty",
soapString.length() > 0 );
// parse the string back to a DOM tree and compare this with the
// original DOM tree
Document parse2 =
builder.parse( new InputSource(new StringReader(soapString)) );
assertTrue( "SOAPElement converted successfully to String",
SOAPTestUtils.equals(parse, parse2) );
// JUnitDoclet end method convertSOAPToString
}
/**
* JUnitDoclet moves marker to this method, if there is not match
* for them in the regenerated code and if the marker is not empty.
* This way, no test gets lost when regenerating after renaming.
* Method testVault is supposed to be empty.
*/
public void testVault() throws Exception {
// JUnitDoclet begin method testcase.testVault
// JUnitDoclet end method testcase.testVault
}
public static void main(String[] args) {
// JUnitDoclet begin method testcase.main
junit.textui.TestRunner.run(SOAPUtilTest.class);
// JUnitDoclet end method testcase.main
}
}
/* -*- Mode: java; indent-tabs-mode:nil; c-basic-offset: 2 -*-
* ex: set sw=2 expandtab:
* $Id:$
*
* This software is released under a BSD-style license.
* Please see the LICENSE file in this distribution.
*/
package soapical.util;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.log4j.Logger;
import org.w3c.dom.Attr;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
* Tools for unit testing Soapical stuff
*
* @version
* $Revision:$<br>
* $Date:$<br>
* @author Eric Kow (kow at loria point fr)
*/
public class SOAPTestUtils {
static Logger _logger = Logger.getLogger(SOAPTestUtils.class);
/**
* Does a deep comparison of nodes a and b; returns true if they have
* the same labels, attributes, inside text, and namespace URIs; or if
* these are text nodes, the same text
*/
public static boolean equals(Node a, Node b) {
_logger.debug("comparing\n" + a + " and\n" + b);
// base-base case (all those nulls and stuff)
// this makes sure they are either both null or both nonnull
if (a == null) {
return (b == null);
} else if (b == null) return false;
if (a.getNodeType() != b.getNodeType()) return false;
switch(a.getNodeType()){
case Node.PROCESSING_INSTRUCTION_NODE:
case Node.DOCUMENT_TYPE_NODE:
return true;
case Node.CDATA_SECTION_NODE:
case Node.COMMENT_NODE:
case Node.TEXT_NODE:
return equals( a.getNodeValue(), b.getNodeValue() );
default:
// compare the nodes themselves (base case)
if (! equals( a.getLocalName(), b.getLocalName() ) ||
! equals( a.getNamespaceURI(), b.getNamespaceURI() ) ||
! equals( a.getAttributes(), b.getAttributes() ))
{
return false;
}
// compare the nodes's siblings (recursion)
Node aSibling = a.getNextSibling();
Node bSibling = b.getNextSibling();
if (! equals(aSibling, bSibling) ) return false;
// compare the node's children (recursion)
// we only compare the first child because this function will
// recurse on its siblings
Node aFirstChild = a.getFirstChild();
Node bFirstChild = b.getFirstChild();
if (! equals(aFirstChild, bFirstChild) ) return false;
}
return true;
}
/**
* Does a deep comparison of attribute lists a and b; returns true if
* all the attributes in the list are the name.
*/
public static boolean equals(NamedNodeMap a, NamedNodeMap b)
{
_logger.debug("comparing attr-lists\n" + a + " and\n" + b);
if (a == null) {
_logger.debug("a is null");
return (b == null);
} else if (b == null) {
_logger.debug("b is null");
return false;
}
int aLength = a.getLength();
int bLength = b.getLength();
SortedMap aMap = new TreeMap();
SortedMap bMap = new TreeMap();
for (int i = 0; i < aLength; i++) {
addAttrToMap( aMap, (Attr)(a.item(i)) );
}
for (int i = 0; i < bLength; i++) {
addAttrToMap( bMap, (Attr)(b.item(i)) );
}
_logger.debug(aMap + " vs. " + bMap);
if (aLength != bLength) return false;
if (! aMap.keySet().equals(bMap.keySet())) return false;
_logger.debug("attr-lists are same length");
Set keys = aMap.keySet();
for (Iterator aIter = keys.iterator(); aIter.hasNext(); ) {
String key = (String)(aIter.next());
Attr aAttr = (Attr)(aMap.get(key));
Attr bAttr = (Attr)(bMap.get(key));
if (! equals(aAttr, bAttr)) return false;
}
return true;
}
/**
* Compares two attributes. Returns true if they have the same label,
* value and namespace URI.
*/
public static boolean equals(Attr a, Attr b)
{
_logger.debug("comparing attrs\n" + a + " and\n" + b);
if (a == null) {
return (b == null);
} else if (b == null) return false;
return
equals(a.getNamespaceURI(), b.getNamespaceURI()) &&
equals(a.getLocalName(), b.getLocalName()) &&
equals(a.getValue(), b.getValue());
}
/**
* Compares two attributes. Returns true if they are both null,
* or contain the same characters.
*/
public static boolean equals(String a, String b)
{
_logger.debug("comparing strs\n" + a + " and\n" + b);
if (a == null) {
return (b == null);
} else if (b == null) return false;
return a.equals(b);
}
private static void addAttrToMap(Map map, Attr attr) {
map.put(attr.getName(), attr);
}
}