Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB: Isn't it possible to use an XmlAdapter without @XmlJavaTypeAdapter?

Tags:

Can't I register a bunch of XmlAdapters to Marshaller|Unmarshaller so that I wouldn't need to specify @XmlJavaTypeAdapter on each filed, whose type isn't natively JAXB-supported?

I find it somewhat redundant.

BTW, someMarshaller.setAdapter(...) seem not to do anything.

like image 209
java.is.for.desktop.indeed Avatar asked Jul 28 '11 10:07

java.is.for.desktop.indeed


1 Answers

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 -

  1. BoundType is User (In Memeory representation)
  2. ValueType is String (The data type JAXB Context is aware of)

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:

  1. 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)

  2. 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 !

like image 96
user1128134 Avatar answered Sep 19 '22 22:09

user1128134