Exposing JPA Entities through RESTful Services

                                     This page describes how to expose JPA entities through a standards-based (JAX-RS/JAXB/JPA) RESTful service. REST is an acronym for Representational State Transfer, which is a design idiom that embraces a stateless client-server architecture in which Web services are viewed as resources which can be identified by their URLs.

Implementing a JPA-Based JAX-RS Service
To implement a JAX-RS service, extend JPASingleKeyResource  or JPACompositeKeyResourcedepending upon the key type. The following example extends JPASingleKeyResource:
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.ws.rs.Path;

import org.eclipse.persistence.jpa.rest.JPASingleKeyResource;

@Stateless
@LocalBean
@Path("/customers")
public class CustomerService extends JPASingleKeyResource<Customer, Long> {

    @PersistenceContext(unitName="CustomerService", type=PersistenceContextType.TRANSACTION)
    EntityManager entityManager;

    public CustomerService() {
        super(Customer.class);
    }

    @Override
    protected EntityManager entityManager() {
        return entityManager;
    }

}

URIs for JPA Entities with Composite Keys

To locate a resource with composite keys, the URI includes the property names from the JPA key class as matrix parameters. The advantage of using matrix parameters is that they may be cached. The same representation is also used if composite keys are represented using an embedded key class.
@Entity
@IdClass(CustomerID.class)
public class Customer implements Serializable {
 
    @Id
    private long id;
 
    @Id
    private String country;
 
}
 
public class CustomerID {
 
    private String country;
    private long id;
 
    public CustomerID() {
        super();
    }
 
    public CustomerID(String country, long id) {
        this.country = country;
        this.id = id;
    }
 
    public String getCountry() {
        return country;
    }
 
    public long getId() {
        return id;
    }
 
}

Using Create, Read, Update, and Delete (CRUD) Operations on RESTful Services
The CRUD operations are described in the following sections:
The examples in these sections use the Jersey client APIs. Jersey is the open source JAX-RS (JSR 311) Reference Implementation for building RESTful Web services. 

POST - Create Operation

The following example shows a post operation called on a service, using the Jersery client APIs. The XML message is converted to the appropriate object type using JAXB.
Client c = Client.create();
WebResource resource = client.resource("http://www.example.com/customer-app/rest/customers");
ClientResponse response = resource.type("application/xml").post(ClientResponse.class, "<customer>...</customer>");
System.out.println(response);
This call will be received by the following:
@POST
@Consumes({"application/xml", "application/json"})
public Response create(@Context UriInfo uriInfo, EntityType entity) {
    entityManager().persist(entity);
    UriBuilder uriBuilder = pkUriBuilder(uriInfo.getAbsolutePathBuilder(), entity);
    return Response.created(uriBuilder.build()).build();
}
Examples of successful responses to this call are:
§  200 OK
§  The URI (as described earlier) for the created entity is returned.
Examples of error responses are:
§   ? (The spec only has this question mark.)

GET - Read Operation

Get is a read-only operation. It is used to query resources. The following is an example of how to invoke a GET call using the Jersey client APIs:
WebResource resource = client.resource("http://www.example.com/customer-app/rest/customers;id=1;country=CA");
ClientResponse response = resource.accept(mimeType).get(ClientResponse.class);
Single key cases that use path parameters and composite key cases that use matrix parameters are handled differently, as shown below:
Single Key - Path Parameter
@GET
@Path("{id}")
@Produces({"application/xml", "application/json"})
public EntityType read(@PathParam("id") KeyType id) {
    return entityManager().find(entityClass, id);
}
Composite Key - Matrix Parameters
@GET
@Produces({"application/xml", "application/json"})
public EntityType read(@Context UriInfo info) {
    return entityManager().find(entityClass, getPrimaryKey(info));
}
Examples of successful responses to this call are:
§  200 OK - If a result is returned
§  204 No Content - If no results are returned
Examples of error responses are:
§   ? (The spec only has this question mark.)

PUT - Update Operation

The put operation updates the underlying resource. When using put the client knows the identity of the resource being updated. The following is an example of how to invoke a PUT call using the Jersey client APIs:
Client c = Client.create();
WebResource resource = client.resource("http://www.example.com/customer-app/rest/customers/1");
ClientResponse response = resource.type("application/xml").put(ClientResponse.class, "<customer>...</customer>");
System.out.println(response);
This call will be received by
@PUT
@Consumes({"application/xml", "application/json"})
public void update(EntityType entity) {
    entityManager().merge(entity);
}
An example of a successful response is:
§  200 OK
An example of an error response is:
§  409 Conflict - Locking related exception

DELETE - Delete Operation

The delete operation is used to remove resources. It is not an error to remove a non-existent resource. Below is an example using the Jersey client APIs:
WebResource resource = client.resource("http://www.example.com/customer-app/rest/customers/1");
ClientResponse response = resource.delete(ClientResponse.class);
Single key cases that use path parameters and composite key cases that use matrix parameters are handled differently, as shown below:
Single Key - Path Parameter
@DELETE
@Path("{id}")
public void delete(@PathParam("id") KeyType id) {
    super.delete(id);
}
Composite Key - Matrix Parameters
@DELETE
public void delete(@Context UriInfo info) {
    super.delete(getPrimaryKey(info));
}
An example of a successful response is:
§  200 OK
An example of an error response is:
§   ? (The spec only has this question mark.)

Matrix Parameters to Instance of ID Class

You can convert matrix parameters to an ID class, as shown below: (From spec: "(Note the code below is currently using query parameters and needs to be updated):")
private KeyType getPrimaryKey(UriInfo info) {
    try {
        KeyType pk = (KeyType) PrivilegedAccessHelper.newInstanceFromClass(keyClass);
        for(Entry<String, List<String>> entry : info.getQueryParameters().entrySet()) {
            Field pkField = PrivilegedAccessHelper.getField(keyClass, entry.getKey(), true);
            Object pkValue = ConversionManager.getDefaultManager().convertObject(entry.getValue().get(0), pkField.getType());
            PrivilegedAccessHelper.setValueInField(pkField, pk, pkValue);
        }
        return pk;
    } catch(Exception e) {
        throw new RuntimeException(e);
    }
}
Obtain the key class using the the JPA metamodel facility:
keyClass = (Class<KeyType>) entityManager().getMetamodel().entity(entityClass).getIdType().getJavaType();

Configuration Files
No extra configuration files are required for RESTful services. However, you are responsible for providing the required JAX-RS, JPA, and JAXB configuration files:
§  JAX-RS - You must create the JAX-RS deployment artifacts appropriate to your deployment platform.
§  JPA - You must create the necessary artifacts for JPA.
§  JAXB - You must create the necessary artifacts for JAXB:
§  jaxb.properties file to specify JAXB implementation
§  eclipselink-oxm.xml as an alternate metadata representation






Post a Comment

 
Top