Skip to main content

Exposing Domain Models through the RESTful Service Interface, Part 1

June 4, 2009

{cs.r.title}




Suppose you have a domain model represented by a set of classes annotated @Entity Beans and you would like to expose this model through a RESTful web service. There are several ways of doing this, including the Java way that goes by the name of Jersey API. Good choice, but as soon as you create your first web service you will notice that Jersey uses JAXB (Java Architecture for XML Binding) to serialize Java objects in XML documents, and you will also realize that JAXB and JPA (Java Persistence API) are not easy to combine. This article demonstrates how to combine JAXB and JPA to transfer data between the database and the web service interface without any adapter code in the middle, while preserving the authorization mechanism for sensitive information.

The article is example-driven, guiding you step-by-step through code snippets and highlighting the adaptation required to produce the confluence between JPA and JAXB annotations. The code sample included in the article is a snapshot of the Footprint Service Project, an open source project (LGPL license) that supports the distribution of certificates of participation in conferences. The purpose of using a real project instead of toy examples is to guarantee that you are working with tested software and to give you the opportunity to discuss further questions with the project team. The full source code of the project is available in the Subversion repository, but a pre-compiled EAR file is provided to shorten the steps required to run and test the project. Note that code retrieved from the repository will vary as the project developers make new commits. Refer to the Resources section of this article for instructions on running the project.

The Domain Model of Conference Certificates

The full Footprint model is too large to be covered in this article; instead, I focus in a small subset of the problem, being careful not to miss the important details. For now, let's concentrate on a 1:n relationship between the conference attendees and their certificates of participation, as shown in the Figure 1.

Serialization Mismatch
Figure 1. The relationship between users and their certificates

Using an open-source project for the examples lets you check out and experiment with a large code set rather than just a few simple cases.

Applying JAXB @XML Annotations to the JPA @Entity Beans

The first step is to design how the persistence layer will be accessed from the other layers of your Java EE application. Despite the possibility of using the EntityManager injected in your business layer, I prefer to follow the classical Design Access Object (DAO) pattern in order to have a centralized data access layer. The idea is to have a CRUD (Create, Read, Update, Delete) interface shared by all entities, and to have sub-interfaces extending the CRUD with specialized business methods. To enable using dependency injection, and to allow the container to manage the allocation of the DAO classes, I coded it as an EJB 3.0 Stateless Session Bean, as shown in the steps below.

1. Create a Package Containing the JPA Entities and the package-info.java File

JSR 175 recommends package-info.java for package-level annotations, and in our case it is very important to have a namespace applied to all entities. For example:

@XmlSchema( namespace = "http://footprint.dev.java.net/service/entity") @XmlAccessorType(XmlAccessType.FIELD) package net.java.dev.footprint.service.entity;  import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlSchema;

The choice of the XmlAccessorType should parallel the JPA annotations. If you are annotating the fields of your class as JPA columns, use XmlAccessType.FIELD; otherwise use XmlAccessType.PROPERTY. The default value is XmlAccessType.PUBLIC_MEMBER. This causes problems if you declare a field and create its respective get method. The JAXB will assume the class has two fields with the same name and will throw a an exception: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException:  2 counts of IllegalAnnotationExceptions.

2. Implement the Domain Model

All entities should have a version number and a unique identifier, so we start the domain model implementation with a @MappedSuperclass Entity, abstract and serializable as a XML complex type. Here is the complete Footprint entities superclass code:

import java.io.Serializable; import javax.persistence.*; import javax.xml.bind.annotation.*;  @XmlType @MappedSuperclass public abstract class                 AbstractFootprintEntity implements Serializable {      @Transient     public static final long serialVersionUID = 196919661993L;      @XmlAttribute     @Version     private int version;      @XmlElement     @Id     @GeneratedValue      private long id;      public long getId() { return id; }     public void setId(long id) { this.id = id; }     public int getVersion() { return version; } }

Some important notes about this code:

  1. Use XmlType to serialize mapped super classes because you will not use this type as an XML root element. That class will be more useful as a parameter in Generics methods.
  2. Our superclass is Serializable because Detached Entities must implement the Serializable interface. When entities are serialized in XML documents they become detached entities.
  3. Always declare a @Version attribute and serialize this attribute in the XML documents. This is used by the Optimistic Concurrency Control of JPA and it is especially relevant to detached objects exposed through web services. Not only will the entity instance be detached, but the entity manager instance will be different between the stateless service calls. The only chance to detect conflicts between concurrent updates is through the versioning of the entities. Another important point is the absence of the setVersion(int) method. This is because the version field reflects its respective database column, modifiable only by JPA.
  4. Never annotate a field with name id as @XmlAttribute because xml:id is a defined attribute in XML documents and should be unique. Another JPA best practice: all entities should have an ID attribute. As a rule of thumb, never use a name from the XML specification as an entity's field name. A simple trick if XML is not your mother tongue: compile the schema from your annotated classes and check if it passes the W3C XML Schema Validator.
  5. The annotation @Transient is redundant in the field serialVersionUID since class members are neither serialized by JAXB nor persisted by JPA. The annotation serves to remind you that you can suppress any field from both persistence and serialization if needed. We declared the serialVersionUID public to reuse its value in the entity subclasses.
  6. The order of the fields in the serialization will match their order in the class declaration. You can optionally use the annotation @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) to force an alphabetical order, but if you use schemagen or some other tool to compile those classes in an XSD schema, the order will reflect your choice. It is more a matter of style than a technical issue, so I prefer configuring through the fields' order.
3. Create the Entity Beans

Below is the code for two related entities from the Footprint model. First is the entity that represents the user table:

import java.util.*; import javax.persistence.*; import javax.xml.bind.annotation.*;  @XmlRootElement @Entity public class FpUser extends FpAbstractUser {          private static final long serialVersionUID =         AbstractFootprintEntity.serialVersionUID;      @XmlElement     @Column(nullable = false)     private String name;      @XmlElement     @Column(nullable = true)     private String email;      @XmlTransient     @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)     private List<FpCertificate> certificate;      public List<FpCertificate> getCertificate() {         if (certificate == null) {             certificate = new ArrayList<FpCertificate>();         }         return certificate;     }      public void setCertificate(List<FpCertificate> certificate) {         this.certificate = certificate;     }      public String getEmail() { return email; }     public void setEmail(String email) { this.email = email; }     public String getName() { return name; }     public void setName(String name) { this.name = name; }

The conversion of simple JPA attributes in JAXB serializable fields is simple: just add the annotation @XmlElement or @XmlAttribute, depending on the how you want the information to appear in the output XML. The trickiest part is to convert the relationship between entities in JAXB serializable fields, as you can see in the annotation @XmlTransient on the certificate field. Before discussing that, let's check the code from the owner side of the relationship: the FpCertificate class.

import javax.persistence.*; import javax.xml.bind.annotation.*;  @XmlRootElement @Entity public class FpCertificate extends AbstractFootprintEntity {     private static final long serialVersionUID =         AbstractFootprintEntity.serialVersionUID;      @XmlElement     @ManyToOne(fetch = FetchType.EAGER,cascade = CascadeType.ALL)     @JoinColumn(name = "user_id")     private FpAbstractUser user;      @XmlElement     @Column     private String path;      public FpAbstractUser getUser() { return user; }     public void setUser(FpAbstractUser user) {this.user = user; }     public String getPath() { return path; }     public void setPath(String path) {this.path = path; } }
JAXB Annotations on the Relationships Between JPA Entities

If you just try to add the @XmlElement annotation on top of your @Column fields, JAXB will throw a circular reference exception trying to serialize the objects into XML documents. The workaround for that problem is to annotate one side of a relationship with the JAXB @XmlTransient annotation, as we did in the FpUser class above. Notice that we are removing the relationship between the entities when we serialize them into XML documents. That results in two different views on the same domain model: the full domain model mapped by the JPA Entities and a partial view of the domain model mapped by the JAXB types. Though scary at first, that strategy preserves the original domain model and causes no problem in our application. Another important aspect to observe is the graph of objects in memory against the data transfer between the service and its clients. When we think about how to expose the data on the service interface, we soon realize that the complete object graph representing a relationship in the domain model may be just too much to serialize in a service response. Imagine if an instance of FpUser has 200 associated certificates and a service call requests the information about the user name. In the domain model, a user contains a list of certificates, but our request needs only the name of the user. So, the decision of which information our service will expose on which method is beyond the technical aspects of annotation. A good practice in RESTful design is to implement complex use cases with several fast small calls instead of a single slow call that transports large amounts of data. In our example, the FpCertificate entities will be completely serialized, but the FpUser entities will be serialized without information about their certificates. We need to create a second method to receive the ID of the FpUser and then return the list of the user's certificates.

Our exercise in defining the visibility of the domain model through the web services starts with the definition of which links we should mark as transient in the JAXB types. The strategy I used to avoid circular references in the JAXB types is summarized in the table below:

JPA JAXB
Relationship Owner Inverse Side
@OneToOne @XmlElement @XmlTransient
@OneToMany @XmlTransient @XmlElement
@ManyToOne @XmlElement @XmlTransient
@ManyToMany @XmlTransient @XmlTransient

Another way to manage the visibility of the domain model data on the service interface is through the JPA Lazy Loading. The default loading strategy of JPA is Fetch.LAZY, while the JAXB framework expects a graph of objects already loaded in memory. So, if you just serialize an @Entity object with JAXB, any lazy-loaded reference to other entities will appear blank on the output XML, and we want to avoid the use of getters and setters during our magic conversion between entities and XML elements. The workaround for that is to change the field loading to EAGER, modifying the JPA annotation, as shown in the FpCertificate code. It may produce a larger result set and may impact the application performance, so you should use it only when you know in advance that the size of the resultant objects graph is acceptable. Otherwise just leave the Fetch.LAZY and force the client to do a second service call to compose the object graph by parts.

Now that we have our model annotated by JAXB, we can use the entities both for persistence and for serialization without any other modification. In the next section I show the code of a Data Access Layer used by the web service to expose the domain model on the web.

A Generic CRUD Facade for your @Entity Beans

You can use several strategies to integrate the persistence layer with the controller layer, including the injection of the EntityManager in the controller classes or the adoption of the Data Access Object pattern. I chose the DAO option, and the steps below enumerate the implementation of the data access layer as a set of EJB 3.0 Stateless Beans.

1. Define the CRUD Interface

Define a CRUD interface that can handle the common persistence operations with our entities. Notice the count method, useful for pagination.

import java.io.Serializable; import java.util.*; import javax.persistence.*;  public interface     FpEntityFacade<T extends AbstractFootprintEntity> {      T create(T entity) throws Exception;

    T read(Serializable primaryKey) throws Exception;

    T update(T entity) throws Exception;

    void delete(T entity) throws Exception;

    Collection<T> readAll(int start, int size) throws Exception;

    long count() throws Exception;
}
2. Define the CRUD Sub-interface

Define the CRUD sub-interface, one for each Entity. Notice that those interfaces can add business operations to the inherited CRUD ones, as suggested in the comments.

@Remote public interface FpUserFacade extends                             FootprintEntityFacade<FpUser> {     // void changeRating(FpEvent event, int newRating); }  @Remote public interface FpCertificateFacade extends                             FpEntityFacade<FpCertificate> { }

 

3. Implement the CRUD Interface

Now we implement our CRUD interface. Here, the only trick is the reflection used in the constructor to catch the class of the entity; this avoids the need to include the type of the entity every time we construct a new facade. Also observe the EntityManager injected in the bean, which characterizes those beans more as Entity Manager Wrapper than DAO, actually.

import java.io.Serializable; import java.util.*; import javax.ejb.Stateless; import javax.persistence.*; import net.java.dev.footprint.service.entity.*;  @Stateless public abstract class         CRUDEntityFacade<T extends AbstractFootprintEntity>                                  implements FpEntityFacade<T> {      private transient final Class<T> entityClass;      @PersistenceContext(name = "footprint")     protected transient EntityManager manager;      @SuppressWarnings("unchecked")     public CRUDEntityFacade() {         entityClass = (Class<T>)             ((java.lang.reflect.ParameterizedType)                  this.getClass().getGenericSuperclass())                      .getActualTypeArguments()[0];     }      public T create(final T entity) throws Exception {         manager.merge(entity);         manager.flush();         return entity;     }      public Collection<T> readAll(int offset, int limit)             throws Exception {         if (offset < 0) { offset = 0; }         if (limit <= 0 || limit > 50) { limit = 50; }          Query query;         query = manager.createQuery("select e from "                 + entityClass.getSimpleName() + " e");         query.setFirstResult(offset);         query.setMaxResults(offset + limit);         return doQuery(query);     }      public T read(final Serializable primaryKey)                                           throws Exception {         return manager.find(entityClass, primaryKey);     }      public void delete(final T entity) throws Exception {         manager.remove(entity);         manager.flush();     }      public T update(final T entity) throws Exception {         return manager.merge(entity);
    }

    public long count() throws Exception {
        Query query =
                 manager.createQuery("select count(e.id) from "
                   + entityClass.getSimpleName() + " e");
        Number countResult = (Number) query.getSingleResult();
        return countResult.longValue();
    }
}

As you notice in the above code, the update method is an important illustration of exposing domain models through the service interface. All objects transfered between the client and the service are JPA Detached Objects, so you can merge the modified data back into the database only if the version of the detached object is the same as the version number stored in the database. That will not work in two cases:

  1. The partial graph of objects: if you try, for example, to merge the serialized FpUser entities back in the database, it will work, but it will delete the relationship between FpUser and FpCertificate, inserting null into the relationship column.
  2. Under concurrency: the serialized entities contain a version number, which will not always represent the latest version of the entity in the database, and the attempt to merge an entity with a different version number will throw a JPA exception.

So you will be forced to load the original entity, copy programatically the new data from the incoming object to the original entity object, and finally call the merge operation of JPA. It is an unavoidable procedure, and the general approach for doing that is to use reflection-based APIs like the commons beanutils.

Implement the CRUD Sub-interfaces

Next, implement the CRUD sub-interfaces. It is important to notice here the safe update method overriding the generic one. The same should be done to the FpCertificate and the other entities. Another important aspect is that we exclude the relationship from this safe copy. It works if your detached entity comes with complete and valid information from outside the service interface, but for security reasons it is better to update the relationship between entities only through specialized business methods. Imagine, for example, if someone starts to manipulate the entities relationship outside the service interface and your facade just inserts it back into the database without filters; it's just too dangerous. Nevertheless, it is important to state that a merge of relationships works out of the box, and eventually it can be useful if the client is another service in the same security zone.

A special remark about the delete operation in the parent-child relationship between FpCertificate and FpUser: deletion of an FpCertificate entity (child) requires no extra code, but deletion of an FpUser entity (parent) requires a bulk operation, because JPA does not propagate the deletion of a parent to its children. You need to delete the foreign key references to an FpUser entity before deleting it; otherwise, the bulk operation will be rolled back. [Special note: if you apply Hibernate, you can use CascadeType.DELETE_ORPHAN; using TopLink, you have the option to configure the relationships as private-owned. I didn't apply such features because they are both proprietary, but I expect such functionality to be included in a next EJB/JPA specification.]

@Stateless public class UserFacade extends CRUDEntityFacade<FpUser>     implements FpUserFacade {      @Override     public FpUser update(FpUser entity) throws Exception {         FpUser attached =             manager.find(FpUser.class, entity.getId());         // this can be done with Commons BeanUtils
        attached.setEmail(entity.getEmail());
        attached.setName(entity.getName());
        return manager.merge(attached);
    }

    @Override
    public void delete(long id) throws IllegalStateException,
          IllegalArgumentException, TransactionRequiredException,
          PersistenceException {
      Query query = manager.
        createNamedQuery(FpCertificate.BULK_DELETE_CERTIFICATES);
      query.setParameter(FpCertificate.USER_ID_PARAM, id);
      query.executeUpdate();
      FpUser user = manager.find(FpUser.class, new Long(id));
      manager.remove(user);
    }
}

public class CertificateFacade
    extends CRUDEntityFacade<FpCertificate>
            implements FpCertificateFacade {
    @Override
    public FpCertificate update(FpCertificate entity)
                                               throws Exception {
        FpCertificate attached =
            manager.find(FpCertificate.class, entity.getId());
        attached.setPath(entity.getPath());
        return manager.merge(attached);
    }
}

Exposing the Domain Model in the RESTful Interface

Our domain model is ready to be exposed on the web. Below you find an example of a Jersey annotated resource that exposes the FpUser entities in their service end points (logging and exception handling were removed for the sake of clarity). Observe how few lines of code are required to traverse the data directly from the database to the service endpoint without any copy (unless for the update method explained above). Another important aspect is the security of the CRUD operations: it is the responsibility on the service implementation to check if the client or the authenticated user is allowed to access the referred entities; here I am just showing the raw code, but in a production environment one should include business validation on the service interface.

import java.util.*; import javax.ejb.EJB; import javax.ws.rs.*; import javax.xml.bind.*; import net.java.dev.footprint.service.entity.*; import static javax.ws.rs.core.MediaType.*;   @Path("/user") public class UserResource {     @EJB     private FpUserFacade userFacade;      @Produces( { APPLICATION_XML, APPLICATION_JSON })     @Consumes( { APPLICATION_XML, APPLICATION_JSON })     @PUT     @Path("/create")     public FpUser create(FpUser newUser) {
        return FpUser user = userFacade.create(newUser);
    }

    @GET
    @Produces( { APPLICATION_XML, APPLICATION_JSON })
    @Path("/read/{id}")
    public FpUser read(@PathParam("id") String id) {
        userFacade.read(new Long(id)));
    }

    @Produces( { APPLICATION_XML, APPLICATION_JSON })
    @Consumes( { APPLICATION_XML, APPLICATION_JSON })
    @PUT
    @Path("/update")
    public FpUser update(FpUser user) {
        return userFacade.update(user);
    }

    @DELETE
    @Path("/delete/{id}")
    public void delete(@PathParam("id") String id) {
        FpUser user = userFacade.read(id);
        userFacade.delete(user);
    }

    @GET
    @Produces( { APPLICATION_XML, APPLICATION_JSON })
    @Path("/editall/{offset}/{limit}")
    public Collection<FpUser> editAll(
                    @PathParam("offset") int offset
                  , @PathParam("limit")  int limit) {
        return userFacade.readAll(offset, limit);
    }
}

Observe the nice Jersey feature of exposing the data in two different formats: XML and JSON. Note that the client should include the HTTP header Accept:application/json to receive the data in JSON format.

Installing and Testing the Sample Service

The code presented in this article is a snapshot of the Footprint Service Project. In order to install and test the sample project, do the following:

  1. Install and configure Java Standard Edition 6
  2. Install and configure Glassfish v2.1
  3. Download the sample EAR file
  4. Open a console and execute the following commands:
    asadmin start-database asadmin start-domain domain1 asadmin deploy --user admin --password adminadmin                 footprint-service-ear-1.0-SNAPSHOT.ear

Once you've done this, you can test the application opening the URLs below in a web browser. To invoke the POST, PUT, and DELETE methods, you can use the CURL tool on UNIX-based systems. If you are using Windows, you can use CURL for Windows. Remember: if you set the HTTP Accept header to application/json, the same code will respond in JSON format.

  • To populate the database with test data:
  • Read all users:
  • Read all certificates:
  • Create a new user. Below you find the content of a sample test.xml file. Notice the ID element is empty, to avoid causing a conflict in the database.

    • curl -v -H "Content-Type: application/xml" -X  PUT --data-binary @test.xml http://localhost:8080/footprint-service/user/create

      <?xml version="1.0" encoding="UTF-8"  standalone="yes"?> <ns2:fpUser  xmlns:ns2="http://footprint.dev.java.net/service/entity"  version="1"><id></id><name>Your  Name</name><email>email@test.com</email><organization  version="1"><id>3</id><logotype>http://your.company/logo.gif</logotype><name>Your  Company</name><website>http://website.com</website></organization></ns2:fpUser> 
  • Update an existing user. You can use the same test.xml file, but remember to include the ID of the entity that you want to update, and also to modify another field so you can see the changes.

    • curl -v -H "Content-Type: application/xml" -X  POST --data-binary @test.xml  http://fgaucho.dyndns.org:8080/footprint-service/user/update
  • Delete a user. Include an existing ID in the URL. Be aware that the only sign of success is the response HTTP code 204.

    • curl -v -H "Accept:application/json" -X  DELETE http://fgaucho.dyndns.org:8080/footprint-service/user/delete/2

The sample project in this article has no pagination, security, or other advanced features, but if you want more complete code, you can check out the Footprint Service from the Subversion repository and then compile it through Maven as shown below. When svn asks for a password, just press ENTER.

svn checkout https://footprint.dev.java.net/svn/footprint/trunk footprint --username guest
cd footprint
mvn clean install

You will find the packed EAR file in the target folder. Questions about the service design and how to install it can be posted directly to the Footprint project's dev mailing list.

Conclusion and Next Steps

JAXB and JPA can be combined to reduce the boilerplate code of Java EE applications and also to optimize the performance of RESTful web services--a flexible solution, which preserves the original domain model while following the JPA, JAXB, and HTTP standards. Preliminary tests proved that this solution surpasses the performance of traditional techniques based on adapters; nevertheless, our job is not yet complete. Exposing the full domain model through a web service interface is not realistic when you consider production environments. In the second part of this series, I will demonstrate how to control the exposure of the domain model in different service paths, allowing fine-grained management of which parts of the entity beans will be exposed to which group of the application users, providing insight into how to produce robust and scalable RESTful web services based on Java EE technologies.

Acknowledgment

Several people contributed ideas included in this article: the Footprint project members, friends, and anonymous tips in diverse mailing lists, especially the Jersey and Glassfish mailing lists. I also would like to register special thanks to Roland Huecking and Rudolf Fluetsch for inspirational discussions about Java EE technologies.

Resources

Felipe Gaucho works as senior software engineer at Netcetera AG in Switzerland. He is a well known Brazilian JUG leader and open-source evangelist.
Related Topics >> Programming   |   Web Design   |   Web Services and XML   |   

Comments

UML to JAXB / JPA model

Very interesting article ! I want to push my open source project called VO-URP that derives from an UML model (class diagram made with the open source MagicDraw community edition) the following products :
  • xml schemas (JAXB 2.1)
  • java model (JPA 1.0 ie eclipseLink 1.1.1)
  • SQL scripts
  • HTML model documentation
  • test code
  • web Model browser with an XML validator and upload document to database
This open source project is available : VO-URP Home

Hyperjaxb3 helps. Using JPA

Hyperjaxb3 helps.
Using JPA and JAXB together seems like a really powerful pattern to me.Your readers may be also be interested in using Hyperjaxb3 to take some of the manual labour out of annotating Jaxb objects as JPA objects. There is information on why you would want to do this on my blog at http://benwilcock.wordpress.com and some great tutorials on the Hyperjaxb3 wiki pages at http://confluence.highsource.org/display/HJ3/Home.