Reputation: 161
We have been using jaxb for a long time to unmarshal XML into POJOs that we can use within our system. However, only recently I found an issue with how jaxb is parsing the xml provided data and I would like to override that behavior by implementing a custom XmlAdapter and forcing jaxb to use it.
Let's say we have a complex field definition like the below in our xsd:
<xs:complexType name="ratingType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="scheme" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="subRatings" use="optional">
<xs:simpleType>
<xs:list itemType='xs:string'/>
</xs:simpleType>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
Without making any changes, jaxb creates the following java class attribute:
@XmlAttribute(name = "subRatings")
protected List<String>subRatings;
This has been generally ok, however, due to new client requirements the data we get on this field will change.
Current use case:
<rating scheme="urn:ncs" subRatings="Violence">MA15+</rating>
This turns into subRatings=["Violence"], which is great.
New use case:
<rating scheme="urn:ncs" subRatings="Coarse Language">MA15+</rating>
This, however, turns into subRatings=["Coarse", "Language"], which is not right and causes issues downstream as that is not a recognized subRating. I expect it to be subRatings=["Coarse Language"] instead.
I have tried a few things, but so far the one that gets me closest to what I want is using jaxb bindings like the below:
<jxb:bindings schemaLocation="media.xsd" node="//xs:complexType[@name='ratingType']">
<jxb:bindings node=".//xs:attribute[@name='subRatings']">
<jxb:property>
<jxb:baseType>
<xjc:javaType name="java.util.List<String>" adapter="org.jvnet.jaxb2_commons.xml.bind.annotation.adapters.CommaDelimitedStringAdapter"/>
</jxb:baseType>
</jxb:property>
</jxb:bindings>
</jxb:bindings>
The generated class has the following attribute definition:
@XmlAttribute(name = "subRatings")
@XmlJavaTypeAdapter(CommaDelimitedStringAdapter.class)
protected List<String>subRatings;
All good so far. However, for some reason, jaxb adds this import to the class: import java.util.List<String>;
which causes it to not compile.
Other thing that I tried was removing the specific type in the javaType name definition, like java.util.List
but that causes jaxb to create the field with a raw type like protected List subRatings;
and that's not only ugly but it causes issues downstream as now anyone using this class needs to cast the subrating object making the code smelly. Any ideas how to overcome this? Keep in mind that I cannot modify the generated classes as they are built at compile time.
Upvotes: 2
Views: 472
Reputation: 2848
New solution
See initial answer first before new solution is released
New solution waiting in PR provided in JAXB-RI : https://github.com/eclipse-ee4j/jaxb-ri/pull/1781
It'll fix the generic not handled in parsing the expected xjc:javaType name
tag : expecting java.util.List<java.lang.String>
will now generate the good imports tags and good java type for the property.
Initial answer
Don't know if you found any solution to your problem but maybe this could work :
LinkedList<String>
class to mask generics frompublic class ListString extends LinkedList<String> {
}
public class CommaDelimitedStringAdapter extends
XmlAdapter<String, ListString> {
@Override
public String marshal(ListString value) throws Exception {
if (value == null) {
return null;
} else {
return StringUtils.join(value.iterator(), ", ");
}
}
@Override
public ListString unmarshal(String text) throws Exception {
if (text == null) {
return null;
} else
{
final ListString value = new ListString();
final String[] items = StringUtils.split(text, ',');
for (String item : items) {
final String trimmedItem = item.trim();
if (!StringUtils.isEmpty(trimmedItem)) {
value.add(trimmedItem);
}
}
return value;
}
}
}
xjc:javaType name="fully.qualified.package.ListString"
using your new adapter you createdThis will generate the following attribute :
@XmlAttribute(name = "subRatings")
@XmlJavaTypeAdapter(CommaDelimitedStringAdapter.class)
protected ListString subRatings;
Unmarshalling is OK see here, and you're still able to navigate through the List items without any cast.
The only cons I found is that you have to choose the implementation of List
you're using (I choose LinkedList
since this is the one used in unmarshalling
method of the adapter but it will work with any other type).
Upvotes: 0