Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid unnecessary namespace declarations in a marshalled element/tag?

Tags:

java

xml

jaxb

xjc

I have a XSD which is not self created but received from another party. So I can't change this XSD because I must ensure comptibility with the other party.

Using XJC 2.2 and JAXB 2.2 using simple binding mode I want to create an root element with inside an empty hello element. But when marshalled I get a lot of extra namespace crap. Which to me looks unneeded. (It works though, but it's more data to send ect...)

XSD Rootelement:

<element name="epp">
        <complexType>
            <choice>
                <element name="greeting" type="epp:greetingType" />
                <element name="hello" />
                <element name="command" type="epp:commandType" />
                <element name="response" type="epp:responseType" />
                <element name="extension" type="epp:extAnyType" />
            </choice>
        </complexType>
    </element>

Java code:

Epp epp = new Epp(); 
epp.setHello("");

Marshalled Result:

<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
     <hello xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string"></hello>
</epp>

Preferred Result:

<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<hello />
</epp>

Or:

<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<hello></hello>
</epp>

Is there any way to make this possible, preferably without changing XSD or manually change the XJC compiled classes?

like image 483
Phoenix the II Avatar asked Oct 28 '11 13:10

Phoenix the II


2 Answers

The problem is as follows: the schema doesn't define a type for element hello. As a result, XJC generates a field with type Object. That means that JAXB must detect, during marshalling, what kind of object we're dealing with. I'm not certain about the details, but I suppose it'll check the runtime type and then deals with it accordingly. Since String - which is what you actually put into the hello field - has a direct binding to a schema type (namely xs:string) JAXB is gonna go with that. So far, so good.

But JAXB is trying to generate XML that'll be useful for unmarshalling as well. Since the schema didn't specify a type and the hello field is an Object, trying to unmarshal from XML would leave JAXB guessing as to what it should actually turn the content into. One way to tell it how is to specify the type in the XML element, using the xsi:type attribute. This attribute falls within the xsi-bound namespace, so that prefix must be declared and bound. That's what happens with xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance". But that's not all... The declared xsi:type makes use of a type in the XML Schema namespace, bound to prefix xs which means THAT must be declared as well! Hence the xmlns:xs="http://www.w3.org/2001/XMLSchema".

The result: a mess of namespace declarations just to tell whoever uses the XML that it does, in fact, contain a string. This could be solved by adding string as the type for the hello element in the schema, but that's not an option for you.

Fortunately, you're not totally out of luck. You can customize bindings using an external bindings file. Details can be found here: http://download.oracle.com/docs/cd/E17802_01/webservices/webservices/docs/2.0/tutorial/doc/JAXBUsing4.html

Normally, this bindings file ought to do the trick:

<?xml version="1.0" encoding="UTF-8"?>
<bindings xmlns="http://java.sun.com/xml/ns/jaxb" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
    version="2.1">

    <!-- Bindings for the general schema -->
    <bindings schemaLocation="test.xsd" node="/xs:schema">

        <bindings node="xs:element[@name='epp']">
            <bindings node=".//xs:element[@name='hello']">
                <javaType name="java.lang.String" />
            </bindings>
        </bindings>

    </bindings>

</bindings>

... but when I try xjc with this I'm getting the error the compiler was unable to honor this javaType customization. It does work when I specify some standard schema type (like string or int) on the hello element in the schema, but again didn't work when I actually tried to supply parse and print methods for the conversion, so I'm gonna have to assume this is a bug in xjc that occurs when no type is specified in the schema.

I'm hoping someone else can give advice regarding the bindings issue. Otherwise, the only option I see is sending your schema through some XSLT transform prior to unleashing XJC on it to set every non-typed element to string by default.

like image 98
G_H Avatar answered Nov 02 '22 00:11

G_H


When the schema doesn't specify a type for an element, the default type is xs:anyType, which is the root of the XML Schema type hierarchy (all simple and complex types are subtypes of anyType).

When JAXB encounters an anyType element it will bind it to a property of type Object. The value you put into this property can be

  • null, meaning omit the element
  • an object of a type the JAXBContext knows about, which will be marshalled in the normal way and an xsi:type added to indicate what the original type was, or
  • an org.w3c.dom.Element representing the actual XML to use.

So try this:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();

epp.setHello(doc.createElementNS("urn:ietf:params:xml:ns:epp-1.0", "hello"));
like image 37
Ian Roberts Avatar answered Nov 02 '22 01:11

Ian Roberts