As part of my refactoring of my existing J2EE application using JAX-RS, I wanted to try and reduce the JAXB custom code in place. Previously I was using Jackson and Jersey for JSON serialization using the "Mapped" notation. This involved creating my own JAXBContext object and I was required to create some special mapping processing so that I could map an attribute as etiher an array, non-string or other value.
Since I was going to use the latest version of eclipselink with the refactoring (currently EclipseLink 2.4.1) I decided to check out using MOXy which I had always had my eye on. My goal was to avoid the mapping approach (which was inherently buggy since it required me to remember to add any field to a property file for proper serialization and I could not serialize the same named field differently for different types - ie. field X was always to be type Y). MOXy seemed to like it might be able to address these concerns.
To leverage MOXy in your environment, you need to include it as a provider. In my case I included it in my JAX-RS Application class and set some configuration properties on it. Here is an example of my current application (note I am using Package scanning for JAX-RS resources/providers in Jersey)
public class WebApplication extends PackagesResourceConfig {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(WebApplication.class.getName());
public WebApplication() {
super("org.javad.web.providers","org.javad.preferences.model.resources","org.javad.stamp.model.resources");
}
@Override
public Set<Object> getSingletons() {
MOXyJsonProvider moxyJsonProvider = new MOXyJsonProvider();
moxyJsonProvider.setAttributePrefix("");
moxyJsonProvider.setFormattedOutput(true);
moxyJsonProvider.setIncludeRoot(false);
moxyJsonProvider.setMarshalEmptyCollections(true);
moxyJsonProvider.setValueWrapper("$");
Set<Object> set = new HashSet<Object>();
set.add(moxyJsonProvider);
return set;
}
}
You can read more on the MOXy settings
here.
You then need to reference your Application in your
web.xml
as part of your JAX-RS resource mapping:
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.javad.web.WebApplication</param-value>
</init-param>
There are other ways to configure the MOXy configuration, but this is the one that I choose and seemed to fit well for my environment (since I was using a
PackageResourceConfig
from Jersey for finding my resources).
Since several of my JPA entities included a foreign reference to another JPA entity, I wanted only the ID of the foreign entity to be serialized. Previously I had been using the
@XmlIDRef
to drive this for serialization, but I had difficulty using this with MOXy. Instead, I needed to declare an XML Adapter to do the serialization and deserialization of the field. This is done using the
@XmlJavaTypeAdapter
annotation. For my album entity, the foreign key reference to the stamp collection entity looks like this:
@XmlJavaTypeAdapter(StampCollectionRefAdapter.class)
@XmlElement(name = StampFormConstants.STAMP_COLLECTION_REF )
@ManyToOne(optional=false,fetch=FetchType.EAGER)
@JoinColumn(name="COLLECTION_ID",nullable=false)
private StampCollection collection;
In the
StampCollectionRefAdapter
I transfer the StampCollection entity to an ID and vice versa (when deserializing from a JSON posting). One issue I ran into was that I could not inject the StampCollectionService into this class. This is because these classes are initialized by MOXy (it would appear) prior to the CDI and EntityManagerFactory initialization, so I needed to obtain the EntityManagerFactory which was used in my initialization servlet listener (this sets up a few configurations in my environment). I had tried declaring this class as a
@Stateless
EJB but did not have any luck (likely because it was constructed explicitly). I may look into this further later, but for now the only way I could get access to the service to transfer the ID back to the physical entity was to leveage the entity manager factory from the initialization listener through a static context (note: this would might fail if I tried to perform any transactions in against this, but should work for normal queries)
public class StampCollectionRefAdapter extends XmlAdapter<StampCollectionRefAdapter.StampCollectionRefType, StampCollection> {
public StampCollectionRefAdapter() {
super();
}
@Override
public StampCollection unmarshal(StampCollectionRefType v) throws Exception {
StampCollection collection = null;
EntityManagerFactory emf = SessionInitializer.getEntityManagerFactory();
if( emf != null ) {
EntityManager em = emf.createEntityManager();
if( em != null ) {
collection = em.find(StampCollection.class, v.id);
}
}
return collection;
}
@Override
public StampCollectionRefType marshal(StampCollection v) throws Exception {
StampCollectionRefType t = new StampCollectionRefType();
t.id = v.getId().longValue();
return t;
}
public static class StampCollectionRefType {
@XmlValue
public long id;
}
}
I am having a small issue with arrays of Entities. I have a serializable class which is Genericized and has two fields:
- total - representing the total number of objects (not necessarily the items returned)
- items - the List of entities to be returned
The first oddity was I could not define a
getItems()
method on the abstract class. Instead I had to declare it on the concrete class that implements the Generic type. The reason for this is I want my array of items to be the plural of the actual items contained within it. This requires me to declare the collection similiar to the following:
@XmlRootElement(name="list")
@XmlAccessorType(XmlAccessType.PROPERTY)
public class AlbumModelList extends AbstractModelList<Album> {
@Override
@XmlElement(name="albums")
public List<Album> getItems() {
return items;
}
}
The resulting JSON will thus look as I intended:
{
"total": 5,
"albums": [
{
"id": "2000",
"name": "Test Collection X",
"countryRefs": [],
"stampCollectionRef": 1
},
{
"id": "1",
"name": "Test Collection x2",
"countryRefs": [],
"stampCollectionRef": 1
},
{
"id": "2",
"name": "Test Collection x4",
"countryRefs": [],
"stampCollectionRef": 1
},
{
"id": "3",
"name": "Test Collection x5",
"countryRefs": [],
"stampCollectionRef": 1
},
{
"id": "4",
"name": "Test Collection x6",
"countryRefs": [],
"stampCollectionRef": 1
}
]
}
But if I use a content type of XML (which I am not using very often) the Array is not showing up as am
<albums/>
element that contains
<album/>
nodes but instead looks like the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<list>
<total>5</total>
<albums id="2000">
<name>Test Collection X</name>
<stampCollectionRef>1</stampCollectionRef>
</albums>
<albums id="1">
<name>Test Collection x2</name>
<stampCollectionRef>1</stampCollectionRef>
</albums>
<albums id="2">
<name>Test Collection x4</name>
<stampCollectionRef>1</stampCollectionRef>
</albums>
<albums id="3">
<name>Test Collection x5</name>
<stampCollectionRef>1</stampCollectionRef>
</albums>
<albums id="4">
<name>Test Collection x6</name>
<stampCollectionRef>1</stampCollectionRef>
</albums>
</list>
Not sure what I am missing here, but I need to address this at some point along with modifying my entity IDs from wrapper types to primitives.