Friday, July 20, 2007

JAXB xs:any xml content missing from marshalled output

Keywords:
JAXB xs:any xml content missing from marshalled output processContents="lax"

Problem:
I'm defining an XML schema that needs to be able to include any XML - "xs:any" seems perfect for the job. Also need to generate JAXB objects from this schema ... everything seems fine on compilation and in constructing the JAXB objects. The problem is at runtime, it appears that the "any" content is missing from the JAXB objects.

example schema

<xs:schema
    targetNamespace="http://example.com/xml"
    xmlns="http://example.com/xml"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    elementFormDefault="qualified"
    attributeFormDefault="unqualified">
    <xs:element name="MyXmlObject">
        <xs:complexType mixed="true">
            <xs:sequence minOccurs="0">
                <xs:any namespace="##any" processContents="lax" minOccurs="0"/>
            </xs:sequence>
            <xs:attribute name="firstName" type="xs:string" use="required"/>
            <xs:attribute name="lastName" type="xs:string" use="required"/>
            <xs:attribute name="happy" type="xs:boolean" use="optional"/>
        </xs:complexType>
    </xs:element>
</xs:schema>

example JAXB object usage

String xmlContent = "<foo>misc. text</foo>";

InputSource source = new InputSource(new StringReader(xmlContent));
Element domElem = (Element)NodeUtils.parseStream(source, true);
myXmlObject.getContent().add(domElem);

JAXBContext jc = JAXBContext.newInstance("com.example.xml");
// marshal to System.out
Marshaller m = jc.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.marshal( response, System.out);

example XML output (missing my any content)

<MyXmlObject xmlns="http://example.com/xml" firstName="bar" lastName="bar" happy="true">
</MyXmlObject>

Solution:
The JAXB 1.0.x FAQ had the answer - Can I access <xs:any> as a DOM node?. There's detailed notes here with links to example schema and the Bug report, but basically "the spec doesn't support the mapping to DOM, so the RI is not allowed to do this" ... but it will if you put it in "extension mode".

This involves adding some extra elements to your schema and compiling with the "-extension" switch (or extension="true" attribute if using the XJC ant task).

new example schema (with JAXB customisation)

<xs:schema
    targetNamespace="http://example.com/xml"
    xmlns="http://example.com/xml"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    elementFormDefault="qualified"
    attributeFormDefault="unqualified"
    jaxb:extensionBindingPrefixes="xjc"
    jaxb:version="1.0">
    <xs:element name="MyXmlObject">
        <xs:complexType mixed="true">
            <xs:sequence minOccurs="0">
                <xs:any namespace="##any" processContents="lax" minOccurs="0">
                    <xs:annotation>
                        <xs:appinfo>
                            <xjc:dom/>
                        </xs:appinfo>
                    </xs:annotation>
                </xs:any>
            </xs:sequence>
            <xs:attribute name="firstName" type="xs:string" use="required"/>
            <xs:attribute name="lastName" type="xs:string" use="required"/>
            <xs:attribute name="happy" type="xs:boolean" use="optional"/>
        </xs:complexType>
    </xs:element>
</xs:schema>

Wednesday, July 18, 2007

NullPointerException tomcat5 realWriteChars

Keywords:
NullPointerException tomcat5 realWriteChars servlet

Problem:
Getting this stack trace on each access of a servlet:

java.lang.NullPointerException
 at org.apache.coyote.tomcat5.OutputBuffer.realWriteChars(OutputBuffer.java:569)
 at org.apache.tomcat.util.buf.CharChunk.flushBuffer(CharChunk.java:435)
 at org.apache.tomcat.util.buf.CharChunk.append(CharChunk.java:366)
 at org.apache.coyote.tomcat5.OutputBuffer.write(OutputBuffer.java:516)
 at org.apache.coyote.tomcat5.CoyoteWriter.write(CoyoteWriter.java:149)
 at org.apache.coyote.tomcat5.CoyoteWriter.write(CoyoteWriter.java:158)
 at org.apache.coyote.tomcat5.CoyoteWriter.print(CoyoteWriter.java:208)
 at org.apache.coyote.tomcat5.CoyoteWriter.println(CoyoteWriter.java:265)
 at com.example.MyServlet.doGet(MyServlet.java:56)


The line number in "MyServlet" code that's kicking this off is a simple PrintWriter.println() ... what it's writing to the stream is definitely not null. How could a NPE be caused in tomcat?

Solution:
I wouldn't have guessed at the issue if not trying the same servlet on WebSphere ... then you get a more useful error message:
Invalid character encoding "UTF=8"

There's a typo (ie '=' instead of '-') in the call to set the content type on the HttpResponse object! Correcting this to "UTF-8" fixes the issue:
response.setContentType("text/html; charset=UTF-8");