 |
Using JAXB 2.0's XmlJavaTypeAdapter
Posted by kohsuke on September 30, 2005 at 03:51 PM | Comments (12)
Sometimes when you are binding your own classes to XML, you hit with a situation where your class representation doesn't quite match what you'd like to see in the XML. Some other times, some of your classes hit the limitation in JAXB that the class must have a default constructor. XmlJavaTypeAdapter is a solution for those problems.
Consider the following question Phil asked in the JAXB&JAX-WS forum:
How do I work around this?
-phil
error: java.util.Currency does not have a no-arg default constructor
this problem is related to the following location:
at java.util.Currency
at com.tickets.common.util.Money.getCurrency (Unknown Source)
at com.tickets.common.util.Money (Unknown Source)
The problem happens when you have a code like this:
package com.tickets.common.util;
import java.util.Currency;
class Money {
Currency getCurrency() { ... }
}
Except a few classes that JAXB implementations would recognize out of the box, classes need to have a default no-argument constructor (because otherwise JAXB wouldn't know how to instanciate it.) java.util.Currency doesn't have one, hence the complaint.
To solve this problem, first, you write an 'adapter' class, that extends XmlAdapter. Your code will be converting Currency to something else that JAXB knows how to handle. I think you'd probably want currency to show up in XML as a string, so your adapter will be converting Currency and String. The adapter class would look like this:
public class CurrencyAdapter extends XmlAdapter<String,Currency> {
public Currency unmarshal(String val) throws Exception {
return Currency.getInstance(val);
}
public String marshal(Currency val) throws Exception {
return val.toString();
}
}
You can then put @XmlJavaTypeAdapter on a place where your code references Currency like this:
class Money {
@XmlJavaTypeAdapter(CurrencyAdapter.class)
Currency getCurrency() {...}
}
If you have a lot of properties it becomes tedious to annotate each currency property in this way. If so, you can place this annotation as a package. This you do in package-info.java like this:
@XmlJavaTypeAdapter(value=CurrencyAdapter.class,type=Currency.class)
package com.tickets.common.util;
This tells JAXB that every reference to Currency inside your util package needs to be adapted by the specified adapter.
Finally, if the Currency class is your own class (which is not in this case, but for others who may have a similar situation), you can place this annotation on the class directly:
@XmlJavaTypeAdapter(CurrencyAdapter.class)
class Currency {
...
}
... to indicate that all references to this class be using the adapter. That's the basics of using XmlAdapter. Hope this explanation helps you get going, Phil!
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Re: "classes need to have a default no-argument constructor (because otherwise JAXB wouldn't know how to instanciate it.)"
I've never bought this. Isn't it possible from the XML Schema to determine what elements are required to construct the object? For example, if the Schema has:
<xsd:complexType name="Address" >
<xsd:sequence>
<xsd:element name="city" type="xsd:string"/>
<xsd:element name="state" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
Then the constructor's signature must be:
public Address(city c, state s)
I ask because without support for this I lose the important guarantee that the design of the class makes it impossible to construct an invalid instance of it.
Posted by: johnbchicago on September 30, 2005 at 05:13 PM
-
I talked about that issue earlier in in case you are interested.
Some other times classes aren't directly instanciable, so just supporting a constructor with parameters wouldn't be enough by itself. It's always tricky to decide what should be easy to do and what should be possible to do.
XmlJavaTypeAdapter isn't very easy to use, but at least it enables the case you mentioned, and the factory pattern. So that's why we picked it. I fully agree with you that there are other designs that make some of our use cases much easier.
Posted by: kohsuke on September 30, 2005 at 05:40 PM
-
Hello kohsuke,
No offence but I think it is a serious misstake not to support those fine true immutable classes in a simple way. I teach everyone to create those kind of classes all the time because they are so bug safe. And one should never have to alter the class to make it fit some persistence framework. Again, IMO.
Having a default constructor for a class that can not live without some information is bug prone, not to say plain wrong.
I guess it is too late to fix this though.
Posted by: mgrev on October 02, 2005 at 05:40 AM
-
Wouldn't a private default ctor still preserve your data integrity?
Posted by: plindsay on October 06, 2005 at 03:49 PM
-
mgrev, I'm really sympathetic to your comment. What I personally found is that I write a class like the following, which generally works fine with JAXB and make me happy:
class Point {
@XmlElement
private int x;
@XmlElement
private int y;
public Point(int x,int y) {
this.x = x; this.y = y;
}
private Point() {}
// getters and setters
}
If I feel strongly about putting 'final' on those data fields, I think I can do that, provided that I set meaninless value in the private default constructor.
Posted by: kohsuke on October 06, 2005 at 04:42 PM
-
I seem to have a problem using XmlJavaTypeAdapter when there's a default mapping for the base class. Please take a look here for details.
Is there a way to work around this?
Posted by: dtenev on March 15, 2006 at 06:26 AM
-
I am intrested in adding the @XmlJavaTypeAdapter annotation to the package-info.java...
However @XmlJavaTypeAdapter value=CurrencyAdapter.class,type=Currency.class)
package com.tickets.common.util;
this annotaion gives compile errors ...as
"The annotation @XmlJavaTypeAdapter is disallowed for this location" and "The attribute type is undefined for the annotation type XmlJavaTypeAdapter"
I am using the latest jar of JAXB implementation
Please Help..
Posted by: bhupiss on April 28, 2006 at 07:21 AM
-
How does one deal with templates of interfaces using the type adapter. For example if I have ArrayList, it appears that I need to place a type adapter for ArrayList in order to get the code to not complain about FooInterface not have a no-arg constructor. However, in the adapter, how do I return the list of objects with FooInterfaces if each of a different type?
Posted by: turnej1 on May 04, 2006 at 06:31 AM
-
Hi Kohsuke,
I am facing a problem using the @XmlJavaType Adpater. I would like to expose an EJB as a WebService ; and one of the method I have to expose has this signature :
@WebMethod
public Interface3 test (Interface1 obj1)
As JAX-B need to have a default constructor, I have to indicate to it which implementation classes to use for the two interfaces (parameter and return result). I solve the problem for the return result using the @XmlJavaTypeAdapter and the associated Adapter class (that cast the object in implementation class or in interface depending on the case).
The problem I am facing is for the parameter. The only solution I see is to use another Adapter class for this interface. But the @XmlJavaTypeAdpater can only be added once. I see in the JAX-B specification that the target of this annotation was also parameter
@WebMethod
@XmlJavaTypeAdapter (MyAdapter3.class)
public Interface3 test (@XmlJavaTypeAdapter(MyAdapter1.class) Interface1 obj1)
But puting these two annotations does not work, I get the following error :
error: pack.Interface1 is an interface, and JAXB can't handle interfaces.
Do not any other solution... Any help appreciated !
Thanks in advance,
Prisca.
Posted by: prisca on May 12, 2006 at 01:26 AM
-
Prisca, that sounds like a bug to me, as this should work.
Can you file this as a bug in JAX-WS?
Posted by: super_kohsuke on May 12, 2006 at 07:07 AM
-
This is a good article but I dont see how I can set the annotation @XmlJavaTypeAdapter using the Jaxb Schema. I am trying to create the schema and set these values there and then generate my classes using xjc.bat. But I dont see any option where I can suggest this in the schema. I have even tried generating the schema thru my classes that had @XmlJavaTypeAdapter but that schema didnt had anything related to an adapter class.
Can you help?
Posted by: vikashsonu on May 18, 2006 at 11:55 AM
-
Hi Kohsuke, Good article. could eloborate on situations like, when using a ThirdParty jar, which has say java.sql.Timestamp used with in, (Timestamp does not have default constructor). By Adding a TimestampAdapter (Adapter converts Timestamp to java.util.Date). How would I use it against the Thirdparty jar, where my adapter should kick in. Thanks for the help in advance. Mohan Gunupati
Posted by: mohan72 on October 26, 2007 at 09:13 AM
|