12

I apologize if this has been answered, but the search terms I have been using (i.e. JAXB @XmlAttribute condensed or JAXB XML marshal to String different results) aren't coming up with anything.

I am using JAXB to un/marshal objects annotated with @XmlElement and @XmlAttribute annotations. I have a formatter class which provides two methods -- one wraps the marshal method and accepts the object to marshal and an OutputStream, the other just accepts the object and returns the XML output as a String. Unfortunately, these methods do not provide the same output for the same objects. When marshaling to a file, simple object fields internally marked with @XmlAttribute are printed as:

<element value="VALUE"></element>

while when marshaling to a String, they are:

<element value="VALUE"/>

I would prefer the second format for both cases, but I am curious as to how to control the difference, and would settle for them being the same regardless. I even created one static marshaller that both methods use to eliminate different instance values. The formatting code follows:

/** Marker interface for classes which are listed in jaxb.index */
public interface Marshalable {}

/** Local exception class */
public class XMLMarshalException extends BaseException {}

/** Class which un/marshals objects to XML */
public class XmlFormatter {
    private static Marshaller marshaller = null;
    private static Unmarshaller unmarshaller = null;

    static {
        try {
            JAXBContext context = JAXBContext.newInstance("path.to.package");
            marshaller = context.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");

            unmarshaller = context.createUnmarshaller();
        } catch (JAXBException e) {
            throw new RuntimeException("There was a problem creating a JAXBContext object for formatting the object to XML.");
        }
    }

    public void marshal(Marshalable obj, OutputStream os) throws XMLMarshalException {
        try {
            marshaller.marshal(obj, os);
        } catch (JAXBException jaxbe) {
            throw new XMLMarshalException(jaxbe);
        }
    }

    public String marshalToString(Marshalable obj) throws XMLMarshalException {
        try {
            StringWriter sw = new StringWriter();
            return marshaller.marshal(obj, sw);
        } catch (JAXBException jaxbe) {
            throw new XMLMarshalException(jaxbe);
        }
    }
}

/** Example data */
@XmlType
@XmlAccessorType(XmlAccessType.FIELD)
public class Data {

    @XmlAttribute(name = value)
    private String internalString;
}

/** Example POJO */
@XmlType
@XmlRootElement(namespace = "project/schema")
@XmlAccessorType(XmlAccessType.FIELD)
public class Container implements Marshalable {

    @XmlElement(required = false, nillable = true)
    private int number;

    @XmlElement(required = false, nillable = true)
    private String word;

    @XmlElement(required = false, nillable = true)
    private Data data;
}

The result of calling marshal(container, new FileOutputStream("output.xml")) and marshalToString(container) are as follows:

Output to file

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
<ns2:container xmlns:ns2="project/schema">  
    <number>1</number>  
    <word>stackoverflow</word>  
    <data value="This is internal"></data>  
</ns2:container>

and

Output to String

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
<ns2:container xmlns:ns2="project/schema">  
    <number>1</number>  
    <word>stackoverflow</word>  
    <data value="This is internal"/>  
</ns2:container>
Daniel Szalay
  • 4,041
  • 12
  • 57
  • 103
Andy
  • 13,916
  • 1
  • 36
  • 78
  • Thank you for cleaning that up a bit, I had trouble not having the XML interpreted. – Andy Jun 11 '10 at 14:47
  • Further investigation has shown that what I am looking for is the ability to control whether JAXB writes "empty elements" in HTML (paired tags) or XML (single tag) style. In other JAXB providers like JaxMe, this is a property that can be set, but apparently not in JAXB. I just wonder why it is different depending on the destination type. I know they are functionally equivalent, but I need them to be identical to work with our system. Any ideas? – Andy Jun 11 '10 at 19:09

3 Answers3

8

Looks like this might be a "bug" in JAXB. Looking at the source, the calls for marshal() create different writers based on the output/writer type parameter:

public void marshal(Object obj, OutputStream out, NamespaceContext inscopeNamespace) throws JAXBException {
    write(obj, createWriter(out), new StAXPostInitAction(inscopeNamespace,serializer));
}

public void marshal(Object obj, XMLStreamWriter writer) throws JAXBException {
    write(obj, XMLStreamWriterOutput.create(writer,context), new StAXPostInitAction(writer,serializer));
}

The implementations of the writers is different with regards to how they handle "empty elements". The above code is from:

jaxb-ri\runtime\src\com\sun\xml\bind\v2\runtime\MarshallerImpl.java.

The two writers you are creating are:

jaxb-ri\runtime\src\com\sun\xml\bind\v2\runtime\output\UTF8XmlOutput.java

jaxb-ri\runtime\src\com\sun\xml\bind\v2\runtime\output\XMLStreamWriterOutput.java

  • 5
    More accurately this is a bug in the Metro JAXB implementation. Other JAXB implementations such as EclipseLink JAXB (MOXy) do not have this bug. – bdoughan Jul 08 '10 at 13:30
2

The good news is that JAXB is a specification with more than one implementation (just like JPA). If one implementation is not meeting your needs, others are available such as EclipseLink JAXB (MOXy):

bdoughan
  • 147,609
  • 23
  • 300
  • 400
1

I don't know why JAXB is doing this - or even if it is JAXB - if JAXB is outputting XML via a SAXContentHandler for example, then it has no direct control over how close tags are produced.

To get consistent behaviour, you could wrap your OutputStream in a OutputStreamWriter, e.g.

   public void marshal(Marshalable obj, OutputStream os) throws XMLMarshalException {
        try {
            marshaller.marshal(obj, new OutputStreamWriter(os, "UTF-8"));
        } catch (JAXBException jaxbe) {
            throw new XMLMarshalException(jaxbe);
        }
    }

Along the same lines, you might see what happens if you wrap the StringWriter in a PrintWriter. Maybe there is some custom code that detects StringWriter to tries to keep the output short as possible. Sounds unlikely, but I have no other explanation.

mdma
  • 56,943
  • 12
  • 94
  • 128
  • I ended up doing exactly this before checking back here and got consistent output. Thanks. – Andy Jun 14 '10 at 13:53