Announcement Announcement Module
Collapse
No announcement yet.
Getting Castor to use SAX Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Getting Castor to use SAX

    We have been struggling getting Castor to work as our marshalling framework, only to run across this in the org.springframework.oxm.castor.AbstractMarshallerT estCase class this morning:

    Code:
    public void testMarshalDOMResult() throws Exception {
        // Unfortunately, Castor creates illegal DOM Documents, which makes it fail this test
        // Hence the override 
    }
    But it appears that Castor can work fine with SAX. So, here is my question - how can I force Castor to use SAX instead of DOM? I am sure that this is independent of Spring-WS and is a config somewhere (env config, JVM config???). I am struggling with this, so any nudge in the right direction would be greatly appreciated.

  • #2
    With regard to that test: I don't know why it was required to override it in the past, but it no longer seems necessary: the testMarshalDOMResult passes for Castor now.

    So Castor does work with DOM. What are the problems you have been struggling with?

    Comment


    • #3
      I will try to get you all of the relevant info. First, the code:

      Domain object:
      Code:
      package com.countrywide.businesscalendar.domain;
      
      import java.util.Date;
      
      /**
       * Represents a single date and whether that date is a Countrywide business day.
       */
      public class CalendarDay {
      
          private Date date;
          private String type; // calendar type name
          private boolean workDay;
      
          public CalendarDay() {
          }
      
          public CalendarDay(Date date, String type, boolean workDay) {
              this.date = date;
              this.type = type;
              this.workDay = workDay;
          }
      
          public Date getDate() {
              return date;
          }
      
          public void setDate(Date date) {
              this.date = date;
          }
      
          public boolean isWorkDay() {
              return workDay;
          }
      
          public void setWorkDay(boolean workDay) {
              this.workDay = workDay;
          }
      
          public String getType() {
              return type;
          }
      
          public void setType(String type) {
              this.type = type;
          }
      
      }
      Castor mapping file (where I assume the problem lies):
      Code:
      <?xml version="1.0"?>
      
      <mapping xmlns="http://castor.exolab.org/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://castor.exolab.org/
                                   http://www.castor.org/mapping.xsd">
      
          <class name="com.countrywide.businesscalendar.domain.CalendarDay">
              <map-to xml="CalendarDay" 
                      ns-uri="http://www.countrywide.com/calendar/schemas" ns-prefix="cal" />
              <field name="date" type="date" required="true">
                  <bind-xml name="cal:Date" node="element" QName-prefix="cal"
                            xmlns:cal="http://www.countrywide.com/calendar/schemas" />
              </field>
              <field name="type" type="string" required="true">
                  <bind-xml name="cal:Type" node="element" QName-prefix="cal"
                            xmlns:cal="http://www.countrywide.com/calendar/schemas" />
              </field>
              <field name="workDay" type="boolean" required="true">
                  <bind-xml name="cal:WorkDay" node="element" QName-prefix="cal"
                            xmlns:cal="http://www.countrywide.com/calendar/schemas" /> 
              </field>
          </class>
      
      </mapping>
      The schema:
      Code:
      <?xml version="1.0" encoding="UTF-8"?>
      <schema xmlns="http://www.w3.org/2001/XMLSchema"
              targetNamespace="http://www.countrywide.com/calendar/schemas"
              xmlns:tns="http://www.countrywide.com/calendar/schemas"
              attributeFormDefault="unqualified"
              elementFormDefault="qualified">
      
          <element name="CalendarDay">
              <complexType>
                  <sequence>
                      <element name="Date" type="date" />
                      <element name="Type" type="string" />
                      <element name="WorkDay" type="boolean" />
                  </sequence>
              </complexType>
          </element>
      
      </schema>
      The stack trace when the Calendar object is being marshalled:
      Code:
      org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.
              at com.sun.org.apache.xerces.internal.dom.ElementNSImpl.setName(ElementNSImpl.java:176)
              at com.sun.org.apache.xerces.internal.dom.ElementNSImpl.<init>(ElementNSImpl.java:112)
              at com.sun.xml.messaging.saaj.soap.impl.ElementImpl.<init>(ElementImpl.java:88)
              at com.sun.xml.messaging.saaj.soap.impl.ElementFactory.createElement(ElementFactory.java:81)
              at com.sun.xml.messaging.saaj.soap.SOAPDocumentImpl.createElement(SOAPDocumentImpl.java:100)
              at org.exolab.castor.xml.util.SAX2DOMHandler.startElement(SAX2DOMHandler.java:76)
              at org.exolab.castor.xml.util.DocumentHandlerAdapter.startElement(DocumentHandlerAdapter.java:197)
              at org.exolab.castor.xml.Marshaller.marshal(Marshaller.java:1508)
              at org.exolab.castor.xml.Marshaller.marshal(Marshaller.java:821)
              at org.springframework.oxm.castor.CastorMarshaller.marshal(CastorMarshaller.java:155)
      Not terribly helpful All I can really tell is there is something going on with how namespaces are being handled.

      I omitted the Spring mapping file (nothing really special there that I saw - strightforward wiring). Using Castor 1.0. The mapping file that I created I derived from your flights castor mapping example. Everything looks right to me. I have tried several permutations of this mapping file - everything results in the same stacktrace. I am pretty sure the problem is my current lack of understanding of XML namespaces

      I would like to be able to send a unit test, but I don't know enough about the DOM APIs to properly set up a test - can't instantiate a proper DomResult object and I am not familiar enought to create a good mock. I am not sure where the latest Spring-WS source code is, or I would have taken the lead from the latest Castor unit test (is the latest publicly available? Sourceforge? No link on the web site that I could find.)

      Thanks for the help.

      Ryan

      Comment


      • #4
        (Appended: FWIW we changed from SaajSoapMessageContextFactory to AxiomSoapMessageContextFactory, which effectively goes from DOM to SAX, and everything worked like a charm. Hmmmm.... While we are still going forward with this, I would still like to know if there is an issue with DOM + Castor. There seems to be on this end. Perhaps is has to do with our DOM implementation. We are using what comes with JKD 1.5.0_07.)

        This should be more helpful. I found the SVN repository for Spring-WS and derived a test case from the flight castor test case. So, here it is...

        Code:
        <?xml version="1.0"?>
        
        <mapping xmlns="http://castor.exolab.org/"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://castor.exolab.org/
                                     http://www.castor.org/mapping.xsd">
        
            <class name="com.countrywide.businesscalendar.domain.CalendarDay">
                <map-to xml="CalendarDay" 
                        ns-uri="http://www.countrywide.com/calendar/schemas" ns-prefix="cal" />
                <field name="date" type="date" required="true">
                    <bind-xml name="cal:Date" node="element" QName-prefix="cal"
                              xmlns:cal="http://www.countrywide.com/calendar/schemas" />
                </field>
                <field name="type" type="string" required="true">
                    <bind-xml name="cal:Type" node="element" QName-prefix="cal"
                              xmlns:cal="http://www.countrywide.com/calendar/schemas" />
                </field>
                <field name="workDay" type="boolean" required="true">
                    <bind-xml name="cal:WorkDay" node="element" QName-prefix="cal"
                              xmlns:cal="http://www.countrywide.com/calendar/schemas" /> 
                </field>
            </class>
        
        </mapping>
        Code:
        package com.countrywide.businesscalendar.ws;
        
        import java.text.DateFormat;
        import java.text.SimpleDateFormat;
        import java.util.Date;
        
        import javax.xml.parsers.DocumentBuilder;
        import javax.xml.parsers.DocumentBuilderFactory;
        import javax.xml.transform.dom.DOMResult;
        
        import org.custommonkey.xmlunit.XMLTestCase;
        import org.springframework.core.io.ClassPathResource;
        import org.springframework.oxm.castor.CastorMarshaller;
        import org.w3c.dom.Document;
        import org.w3c.dom.Element;
        
        import com.countrywide.businesscalendar.domain.CalendarDay;
        
        public class CastorMarshallerTest extends XMLTestCase {
        
            private final static String NS = "http://www.countrywide.com/calendar/schemas";
        
            private CastorMarshaller marshaller;
        
            private CalendarDay calendarDay;
        
            @Override
            public void setUp() throws Exception {
                marshaller = new CastorMarshaller();
                ClassPathResource mappingLocation = new ClassPathResource("mapping.xml");
                marshaller.setMappingLocation(mappingLocation);
                marshaller.afterPropertiesSet();
                
                DateFormat format = new SimpleDateFormat("MM/dd/yyyy");
                Date date = format.parse("01/01/2006");
                calendarDay = new CalendarDay(date, "CFC", true);
            }
        
            public void testMarshaell() throws Exception {
                DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
                
                Document document = builder.newDocument();
                DOMResult domResult = new DOMResult(document);
                marshaller.marshal(calendarDay, domResult);
                
                Document expected = builder.newDocument();
                Element calendarDayElement = expected.createElementNS(NS, "cal:CalendarDay");
                expected.appendChild(calendarDayElement);
                Element dateElement = expected.createElementNS(NS, "cal:Date");
                dateElement.setTextContent("2006-01-01"); // not sure if this is what to expect
                calendarDayElement.appendChild(dateElement);
                Element typeElement = expected.createElementNS(NS, "cal:Type");
                typeElement.setTextContent("CFC");
                calendarDayElement.appendChild(typeElement);
                Element workDayElement = expected.createElementNS(NS, "cal:WorkDay");
                workDayElement.setTextContent("true");
                calendarDayElement.appendChild(workDayElement);
                assertXMLEqual("Marshaller writes invalid DOMResult", expected, document);
            }
        
        }
        Code:
        package com.countrywide.businesscalendar.domain;
        
        import java.util.Date;
        
        /**
         * Represents a single date and whether that date is a Countrywide business day.
         */
        public class CalendarDay {
        
            private Date date;
        
            private String type; // calendar type name
        
            private boolean workDay;
        
            public CalendarDay() {
            }
        
            public CalendarDay(Date date, String type, boolean workDay) {
                this.date = date;
                this.type = type;
                this.workDay = workDay;
            }
        
            public Date getDate() {
                return date;
            }
        
            public void setDate(Date date) {
                this.date = date;
            }
        
            public boolean isWorkDay() {
                return workDay;
            }
        
            public void setWorkDay(boolean workDay) {
                this.workDay = workDay;
            }
        
            public String getType() {
                return type;
            }
        
            public void setType(String type) {
                this.type = type;
            }
        
        }
        When I run the unit test, I get this:

        Code:
        [different] Expected namespace URI 'http://www.countrywide.com/calendar/schemas' but was 'null' - comparing <cal:CalendarDay...> at /CalendarDay[1] to <cal:CalendarDay...> at /cal:CalendarDay[1]
        
        	at junit.framework.Assert.fail(Assert.java:47)
        	at org.custommonkey.xmlunit.XMLAssert.assertXMLEqual(XMLAssert.java:123)
        	at org.custommonkey.xmlunit.XMLAssert.assertXMLEqual(XMLAssert.java:222)
        	at org.custommonkey.xmlunit.XMLTestCase.assertXMLEqual(XMLTestCase.java:290)
        	at com.countrywide.businesscalendar.ws.CastorMarshallerTest.testMarshaell(CastorMarshallerTest.java:59)
        Again, it looks like the namespace is not being set correctly. Thanks again.
        Last edited by breidenr; Aug 25th, 2006, 10:54 AM.

        Comment


        • #5
          Try and set the DocumentBuilderFactory to be namespace aware.

          So do:

          Code:
          public void testMarshaell() throws Exception {
            DocumentBuilderFactory documentBuilderFactory DocumentBuilderFactory.newInstance();
            documentBuilderFactory.setNamespaceAware(true);
            DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
          ...
          and see if that helps...

          Comment


          • #6
            The funny thing is that I can reproduce the namespace error using Maven, but not in my IDE. I will investigate this further.

            Comment


            • #7
              Originally posted by poutsma
              The funny thing is that I can reproduce the namespace error using Maven, but not in my IDE. I will investigate this further.
              Interesting. This sounds like it could be possibly be caused by your IDE using a different parser? I will try your suggestion of setting setNamespaceAware = true and give that a shot.

              Thanks for the follow up.

              Comment


              • #8
                FWIW - I tried
                Code:
                        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
                        documentBuilderFactory.setNamespaceAware(true);
                        DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
                Same results . However, like I said before we moved from DOM to SAX and everything worked like a charm. So, the mapping apprears to be right. Maybe it is our parser? These XML parser issues can lead to so many wild goose chases.

                Comment


                • #9
                  If you update your svn code, you will see that I made some changes to the DOM test case in AbstractMarshallerTestCase. Now it suceeds for Castor. For some reason, the namespace is declared, but no picked up by XMLUnit when it does a DOM document comparison. When you do a string comparison, it does work.

                  I have no idea how this is possible, but at least the test code runs now (both in IDE & Maven)

                  Comment


                  • #10
                    For what it's worth, this seems to be the exact same problem that I was describing in http://forum.springframework.org/showthread.php?t=27476. When using Saaj, the response XML came back in a really bad way:

                    Code:
                    <!-- NOTE: This was reformatted for readability -->
                    <soapenv:Envelope 
                        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                        xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                      <soapenv:Body>
                        <tns: xmlns:tns="http://www.habuma.com/poker/schemas">
                          <tns: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                              xsi:type="java:java.lang.String" xmlns:tns="handName">
                            FLUSH
                          </tns:>
                        </tns:>
                      </soapenv:Body>
                    </soapenv:Envelope>
                    Notice that the XML elements in the message are only namespaces without element names. Also, the namespace URI for one of the elements is "handName" which should've been the element name.

                    But when I did as Ryan suggested and used AxiomSoapMessageContextFactory (instead of SaajSoapMessageContextFactory), it came back this way:

                    Code:
                    <!-- REFORMATTED FOR READABILITY -->
                    <?xml version='1.0' encoding='UTF-8'?>
                    <soapenv:Envelope 
                        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
                      <soapenv:Header></soapenv:Header>
                      <soapenv:Body>
                        <tns:EvaluateHandResponse xmlns:tns="http://www.habuma.com/poker/schemas">
                          <tns:handName 
                              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                              xsi:type="java:java.lang.String">
                            FLUSH
                          </tns:handName>
                        </tns:EvaluateHandResponse>
                      </soapenv:Body>
                    </soapenv:Envelope>
                    Now everything seems fine. Incidentally, this is with Spring-WS 1.0-m2.

                    I'm happy that it works now...but why does it not work with Saaj?

                    Comment


                    • #11
                      After some digging into the Castor codebase, I think I've figured out why this is the case. While marshalling to DOM nodes, castor uses a SAX2DOMHandler to publish SAX events into the DOM tree. This handler is a SAX 1 DocumentHandler, which has no native namespace support. There is a DocumentHandlerAdapter, which tries to correct this, but obviously that is failing.

                      Now that I've found the reason, it should be relatively easy to fix it, and get Castor working with SAAJ. Once again, it just shows that XML parsing in Java is a mess .

                      Comment


                      • #12
                        I've created an issue for this, which has been fixed. Using Castor with SAAJ should now work, let me know if it does for you.

                        Comment

                        Working...
                        X