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); 
  }
  
}

Reply via email to