Can\'t I register a bunch of XmlAdapter
s to Marshaller
|Unmarshaller
so that I wouldn\'t need to specify @XmlJavaTypeAdapter
You can use the package-info.java
That's called "package level".
Example : put a package-info.java in the same package that the class you want to marshall/unmarshall.
Suppose you have a class mypackage.model.A and an adapter CalendarAdapter in mypackage.adapter. Declare a package-info.java file in mypackage.model containing :
@XmlJavaTypeAdapters({
@XmlJavaTypeAdapter(value = CalendarAdapter.class, type = Calendar.class)
})
package mypackage.model;
import java.util.Calendar;
import mypackage.adapter.CalendarAdapter;
All field of type Calendar in the class A will be marshalled or unmarshalled with the CalendarAdapter.
You can find useful informations there : http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html
This is a quite a good question !
The short answer is that no, using setAdapter
on marshaller / unmarshaller does not mean that you don't have to use @XmlJavaTypeAdapter
.
Let me explain this with a hypothetical (yet valid!) scenario.
Consider in a web application, one fetches an event in the form of xml having following schema:
<xs:element name="event" >
<xs:complexType>
<xs:sequence>
<!-- Avoiding other elements for concentrating on our adapter -->
<xs:element name="performedBy" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
Equivalent to this, your model will look like:
@XmlRootElement(name="event")
@XmlType(name="")
public class Event {
@XmlElement(required=true)
protected String performedBy;
}
Now the application is already having a bean called User
which maintains the detailed
information about the user.
public class User {
private String id;
private String firstName;
private String lastName;
..
}
Note that this User
is not known to your JAXB Context.
For simplicity we have User as POJO, but it can be any Valid Java Class.
What application architect want is Event
's performedBy
should be represented as User
to gain full details.
Here is where @XmlJavaTypeAdapter comes into picture
JAXBContext is aware about performedBy
as xs:string
, but it has to be represented as
User
in memory in Java.
Modified model looks like:
@XmlRootElement(name="event")
@XmlType(name="")
public class Event {
@XmlElement(required=true)
@XmlJavaTypeAdapter(UserAdapter.class)
protected User performedBy;
}
UserAdapter.java:
public class UserAdapter extends XmlAdapter<String, User> {
public String marshal(User boundType) throws Exception {
..
}
public User unmarshal(String valueType) throws Exception {
..
}
}
The Adapter's definition says that -
Coming to back to your question -
I find it somewhat redundant.
BTW, someMarshaller.setAdapter(...) seem not to do anything.
Consider that our Adapter requires a class called UserContext in order to marshal / unmarshal sucessfully.
public class UserAdapter extends XmlAdapter<String, User> {
private UserContext userContext;
public String marshal(User boundType) throws Exception {
return boundType.getId();
}
public User unmarshal(String valueType) throws Exception {
return userContext.findUserById(valueType);
}
}
Now the question is how will UserAdapter will fetch an instace of UserContext ?? As a good design one should always supply it while it has got instantiated ..
public class UserAdapter extends XmlAdapter<String, User> {
private UserContext userContext;
public UserAdapter(UserContext userContext) {
this.userContext = userContext;
}
public String marshal(User boundType) throws Exception {
return boundType.getId();
}
public User unmarshal(String valueType) throws Exception {
return userContext.findUserById(valueType);
}
}
But JAXB Runtime can only accept Adapter with No-args constructor .. (Obviously JAXBContext does not know about application specific model)
So thankfully there is an option :D
You can tell your unmarshaller
to use given instance of UserAdapter
rather than instating it by own its own.
public class Test {
public static void main(String... args) {
JAXBContext context = JAXBContext.getInstance(Event.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
UserContext userContext = null; // fetch it from some where
unmarshaller.setAdapter(UserAdapter.class, new UserAdapter(userContext));
Event event = (Event) unmarshaller.unmarshal(..);
}
}
setAdapter
method is available on both Marshaller
& Unmarshaller
Note:
setAdapter
on marshaller / unmarshaller does not mean that you don't have to use @XmlJavaTypeAdapter
.
@XmlRootElement(name="event")
@XmlType(name="")
public class Event {
@XmlElement(required=true)
// @XmlJavaTypeAdapter(UserAdapter.class)
protected User performedBy;
}
If you omit this JAXB runtime has no clue that User is your Bound Type & Value Type is something else. It will try to marshal User
as is & u will end up having wrong xml
(or validation failure if enabled)
While we have taken a scenario where Adapter is required to be with arguments, hence
use setAdapter
method.
Some adavanced usages are also there, where in even if you have default no-arg constructor, yet you provide an instance of the Adapter
May this adapter is configured with data, which marshal / unmarshal operation is using !