RESTful Java with JAX-RS 2.0 (2013)
Part II. JAX-RS Workbook
Chapter 28. Examples for Chapter 14
In Chapter 14, you learned a bit about how JAX-RS fits in the grander scheme of things like Java EE and Spring. In this chapter, there are two similar examples that define the services illustrated in Chapter 2. The first marries JAX-RS with EJB. The second uses Spring to write our JAX-RS services. Instead of using in-memory maps like the earlier examples in the workbook, both examples use the Java Persistence API (JPA) to map Java objects to a relational database.
Example ex14_1: EJB and JAX-RS
This example shows how you can use JAX-RS with EJB and JPA. It makes use of some of the integration code discussed in Chapter 14.
Project Structure
To implement ex14_1, the Wildfly 8.0 Application Server is used to deploy the example. Wildfly is the community version of the JBoss application server. It is Java EE 7–compliant, so JAX-RS 2.0 is already built in. As a result, our Maven pom.xml file needs to change a little to support this example. First, let’s look at the dependency changes in this build file:
pom.xml
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.0.5.Final</version>
<scope>provided</scope>
</dependency>
Because JAX-RS 2.0 is built in, we do not have to add all the RESTEasy third-party dependencies to our WAR file. The provided scope is used to tell Maven that the JAR dependencies are needed only for compilation and to not include them within the WAR.
Next, we need to include a Wildfly Maven plug-in:
<plugins>
<plugin>
<groupId>org.jboss.as.plugins</groupId>
<artifactId>jboss-as-maven-plugin</artifactId>
<version>7.1.1.Final</version>
<executions>
<execution>
<id>jboss-deploy</id>
<phase>pre-integration-test</phase>
<goals>
<goal>deploy</goal>
</goals>
</execution>
<execution>
<id>jboss-undeploy</id>
<phase>post-integration-test</phase>
<goals>
<goal>undeploy</goal>
</goals>
</execution>
</executions>
</plugin>
The jboss-as-maven-plugin is configured to run after the WAR is built, but before the unit tests are run. It uses the Wildfly remote deployment interface to automatically deploy our WAR to the Wildfly application server. We’ll see later how to start the example.
The EJBs
The EJB code is very similar to ex10_2 from Chapter 24, except the code has been expanded to save created order entries into a relational database instead of an in-memory map. Like all of our previous examples, the JAXB classes that define our XML data format live in thecom.restfully.shop.domain package. A separate parallel Java package, com.restfully.shop.persistence, was created for the example’s JPA classes. These JPA classes are almost a carbon copy of the JAXB ones, except they are using JPA annotations to map to a relational database.
You could use JAXB and JPA annotations together within one class hierarchy, but this isn’t the best idea, as there are a few problems you might encounter. The first has to do with how JPA works. Objects like the OrderEntity have relationships to other classes like LineItemEntity,ProductEntity, and CustomerEntity. In JPA, it is common to lazy-load these objects as their object graphs are traversed. This can save on database access time. The problem where JAX-RS is concerned is that the JAX-RS runtime will usually turn the Java object into an XML document outside the scope of an EJB request. This might cause lazy-load exceptions when JAXB tries to traverse the entire object graph.
You can write your code so that it is careful not to introduce lazy-load exceptions, but there is one other major problem you may encounter. You will often want to support older clients that use older versions of the XML format. This can cause a divergence between your XML schema and your database schema. The best way to avoid this problem is to create two separate class hierarchies. That way, your XML and database mappings can evolve separately from one another. Yes, it’s a little more code for you to write, but it will save you headaches in the long run.
I’m going to skip a lot of the details of this example. You’ve already seen how JAXB classes work and this book isn’t an exercise on learning JPA, so I’ll focus on how JAX-RS interacts with EJB. Let’s take a look at one of the EJBs:
ejb/src/main/java/com/restfully/shop/services/CustomerResource.java
@Path("/customers")
publicinterfaceCustomerResource
{
@POST
@Consumes("application/xml")
ResponsecreateCustomer(Customercustomer,
@ContextUriInfouriInfo);
@GET
@Produces("application/xml")
@Formatted
CustomersgetCustomers(@QueryParam("start")intstart,
@QueryParam("size")@DefaultValue("2")intsize,
@QueryParam("firstName")StringfirstName,
@QueryParam("lastName")StringlastName,
@ContextUriInfouriInfo);
@GET
@Path("{id}")
@Produces("application/xml)
Customer getCustomer(@PathParam("id")intid);
}
For a non-JAX-RS-aware EJB container to work with JAX-RS, you need to define your JAX-RS annotations on the EJB’s business interface. The CustomerResource interface does just this.
Our EJB business logic is defined within the CustomerResourceBean class:
ejb/src/main/java/com/restfully/shop/services/CustomerResourceBean.java
@Stateless
publicclassCustomerResourceBeanimplementsCustomerResource
{
@PersistenceContext
privateEntityManagerem;
Our EJB class is annotated with the @javax.ejb.Stateless annotation to mark it as a stateless session EJB. The CustomerResourceBean class implements the CustomerResource interface.
There is a javax.persistence.EntityManager field named em. The annotation @javax.persistence.PersistenceContext injects an instance of the EntityManager into that field. The EntityManager persists Java objects into a relational database. These are all facilities of EJB and JPA.
publicResponsecreateCustomer(Customercustomer,UriInfouriInfo)
{
CustomerEntityentity=newCustomerEntity();
domain2entity(entity,customer);
em.persist(entity);
em.flush();
System.out.println("Created customer "+entity.getId());
UriBuilderbuilder=uriInfo.getAbsolutePathBuilder();
builder.path(Integer.toString(entity.getId()));
returnResponse.created(builder.build()).build();
}
The createCustomer() method implements the RESTful creation of a Customer in the database. The Customer object is the unmarshalled representation of the XML document posted through HTTP. The code allocates an instance ofcom.restfully.shop.persistence.CustomerEntity and copies the data from Customer to this instance. The EntityManager then persists the CustomerEntity instance into the database. Finally, the method uses UriInfo.getAbsolutePathBuilder() to create a URL that will populate the value of the Location header that is sent back with the HTTP response.
publicCustomergetCustomer(intid)
{
CustomerEntitycustomer=em.getReference(CustomerEntity.class,
id);
returnentity2domain(customer);
}
The getCustomer() method services GET /customers/<id> requests and retrieves CustomerEntity objects from the database using the EntityManager. The entity2domain() method call converts the CustomerEntity instance found in the database into an instance of the JAXB class Customer. This Customer instance is what is returned to the JAX-RS runtime.
publicstaticvoiddomain2entity(CustomerEntityentity,
Customercustomer)
{
entity.setId(customer.getId());
entity.setFirstName(customer.getFirstName());
entity.setLastName(customer.getLastName());
entity.setStreet(customer.getStreet());
entity.setCity(customer.getCity());
entity.setState(customer.getState());
entity.setZip(customer.getZip());
entity.setCountry(customer.getCountry());
}
publicstaticCustomerentity2domain(CustomerEntityentity)
{
Customercust=newCustomer();
cust.setId(entity.getId());
cust.setFirstName(entity.getFirstName());
cust.setLastName(entity.getLastName());
cust.setStreet(entity.getStreet());
cust.setCity(entity.getCity());
cust.setState(entity.getState());
cust.setZip(entity.getZip());
cust.setCountry(entity.getCountry());
returncust;
}
The domain2entity() and entity2domain() methods simply convert to and from the JAXB and JPA class hierarchies.
publicCustomersgetCustomers(intstart,
intsize,
StringfirstName,
StringlastName,
UriInfouriInfo)
{
UriBuilderbuilder=uriInfo.getAbsolutePathBuilder();
builder.queryParam("start","{start}");
builder.queryParam("size","{size}");
ArrayList<Customer>list=newArrayList<Customer>();
ArrayList<Link>links=newArrayList<Link>();
The getCustomers() method is expanded as compared to previous examples in this book. The firstName and lastName query parameters are added. This allows clients to search for customers in the database with a specific first and last name.
Queryquery=null;
if(firstName!=null&&lastName!=null)
{
query=em.createQuery(
"select c from Customer c where c.firstName=:first
and c.lastName=:last");
query.setParameter("first",firstName);
query.setParameter("last",lastName);
}
elseif(lastName!=null)
{
query=em.createQuery(
"select c from Customer c where c.lastName=:last");
query.setParameter("last",lastName);
}
else
{
query=em.createQuery("select c from Customer c");
}
The getCustomers() method builds a JPA query based on the values of firstName and lastName. If these are both set, it searches in the database for all customers with that first and last name. If only lastName is set, it searches only for customers with that last name. Otherwise, it just queries for all customers in the database.
ListcustomerEntities=query.setFirstResult(start)
.setMaxResults(size)
.getResultList();
Next, the code executes the query. You can see that doing paging is a little bit easier with JPA than the in-memory database we used in Chapter 24. The setMaxResults() and query.setFirstResult() methods set the index and size of the dataset you want returned.
for(Objectobj:customerEntities)
{
CustomerEntityentity=(CustomerEntity)obj;
list.add(entity2domain(entity));
}
Next, the code iterates through all the CustomerEntity objects returned by the executed query and creates Customer JAXB object instances.
// next link
// If the size returned is equal then assume there is a next
if(customerEntities.size()==size)
{
intnext=start+size;
URInextUri=builder.clone().build(next,size);
LinknextLink=Link.fromUri(nextUri)
.rel("next")
.type("application/xml").build();
links.add(nextLink);
}
// previous link
if(start>0)
{
intprevious=start-size;
if(previous<0)previous=0;
URIpreviousUri=builder.clone().build(previous,size);
LinkpreviousLink=Link.fromUri(previousUri)
.rel("previous")
.type("application/xml").build();
links.add(previousLink);
}
Customerscustomers=newCustomers();
customers.setCustomers(list);
customers.setLinks(links);
returncustomers;
}
}
Finally, the method calculates whether the next and previous Atom links should be added to the Customers JAXB object returned. This code is very similar to the examples described in Chapter 24.
The other EJB classes defined in the example are pretty much extrapolated from the ex10_2 example and modified to work with JPA. I don’t want to rehash old code, so I won’t get into detail on how these work.
The Remaining Server Code
There’s a few more server-side classes we need to go over.
The ExceptionMappers
The EntityManager.getReference() method is used by various EJBs in this example to locate objects within the database. When this method cannot find an object within the database, it throws a javax.persistence.EntityNotFoundException. If we deployed this code as is, JAX-RS would end up eating this exception and returning a 500, “Internal Server Error,” to our clients if they tried to access an unknown object in the database. The 404, “Not Found,” error response code makes a lot more sense to return in this scenario. To facilitate this, a JAX-RSExceptionMapper is used. Let’s take a look:
ejb/src/main/java/com/restfully/shop/services/EntityNotFoundExceptionMapper.java
@Provider
publicclassEntityNotFoundExceptionMapper
implementsExceptionMapper<EntityNotFoundException>
{
publicResponsetoResponse(EntityNotFoundExceptionexception)
{
returnResponse.status(Response.Status.NOT_FOUND).build();
}
}
This class catches EntityNotFoundExceptions and generates a 404 response.
Changes to Application class
The ShoppingApplication class has been simplified a bit. Because all of our code is implemented as EJBs, there’s no special registration we need to do in our Application class. Here’s what it looks like now:
war/src/main/java/com/restfully/shop/services/ShoppingApplication.java
@ApplicationPath("/services")
publicclassShoppingApplicationextendsApplication
{
}
The Wildfly application server will scan the WAR for any annotated JAX-RS classes and automatically deploy them. In this deployment, all of our JAX-RS services are EJBs and contained in the WEB-INF/classes folder of our WAR.
The Client Code
Let’s take a look at the client code:
ear/src/test/java/com/restfully/shop/test/ShoppingTest.java
protectedvoidpopulateDB()throwsException
{
Responseresponse=
client.target("http://localhost:8080/ex14_1/services/shop")
.request().head();
Linkproducts=response.getLink("products");
response.close();
System.out.println("** Populate Products");
Productproduct=newProduct();
product.setName("iPhone");
product.setCost(199.99);
response=client.target(products).request().post(Entity.xml(product));
Assert.assertEquals(201,response.getStatus());
response.close();
product=newProduct();
product.setName("MacBook Pro");
product.setCost(3299.99);
response=client.target(products).request().post(Entity.xml(product));
Assert.assertEquals(201,response.getStatus());
response.close();
product=newProduct();
product.setName("iPod");
product.setCost(49.99);
response=client.target(products).request().post(Entity.xml(product));
Assert.assertEquals(201,response.getStatus());
response.close();
}
The populateDB() method makes HTTP calls on the ProductResource JAX-RS service to create a few products in the database.
@Test
publicvoidtestCreateOrder()throwsException
{
populateDB();
Responseresponse=client.target
("http://localhost:8080/ex14_1/services/shop").request().head();
Linkcustomers=response.getLink("customers");
Linkproducts=response.getLink("products");
Linkorders=response.getLink("orders");
response.close();
The test starts off by initializing the server’s database by calling populateDB(). Like ex10_2, the client interacts with the StoreResource JAX-RS service to obtain links to all the services in the system.
System.out.println("** Buy an iPhone for Bill Burke");
System.out.println();
System.out.println("** First see if Bill Burke exists as a customer");
Customerscusts=client.target(customers)
.queryParam("firstName","Bill")
.queryParam("lastName","Burke")
.request().get(Customers.class);
Customercustomer=null;
if(custs.getCustomers().size()>0)
{
System.out.println("- Found a Bill Burke in the database, using that");
customer=custs.getCustomers().iterator().next();
}
else
{
System.out.println("- Cound not find a Bill Burke in the database,
creating one.");
customer=newCustomer();
customer.setFirstName("Bill");
customer.setLastName("Burke");
customer.setStreet("222 Dartmouth Street");
customer.setCity("Boston");
customer.setState("MA");
customer.setZip("02115");
customer.setCountry("USA");
response=client.target(customers)
.request()
.post(Entity.xml(customer));
Assert.assertEquals(201,response.getStatus());
URIuri=response.getLocation();
response.close();
customer=client.target(uri).request().get(Customer.class);
}
The client code checks to see if the customer “Bill Burke” already exists. If that customer doesn’t exist, it is created within the customer database.
System.out.println();
System.out.println("Search for iPhone in the Product database");
Productsprods=client.target(products)
.queryParam("name","iPhone")
.request()
.get(Products.class);
Productproduct=null;
if(prods.getProducts().size()>0)
{
System.out.println("- Found iPhone in the database.");
product=prods.getProducts().iterator().next();
}
else
{
thrownewRuntimeException("Failed to find an iPhone in the database!");
}
The customer wants to buy a product called iPhone, so the client searches the product database for it.
System.out.println();
System.out.println("** Create Order for iPhone");
LineItemitem=newLineItem();
item.setProduct(product);
item.setQuantity(1);
Orderorder=newOrder();
order.setTotal(product.getCost());
order.setCustomer(customer);
order.setDate(newDate().toString());
order.getLineItems().add(item);
response=client.target(orders).request().post(Entity.xml(order));
Assert.assertEquals(201,response.getStatus());
response.close();
System.out.println();
System.out.println("** Show all orders.");
Stringxml=client.target(orders).request().get(String.class);
System.out.println(xml);
}
}
Finally, an order is created within the database.
Build and Run the Example Program
Perform the following steps:
1. Download the latest Wildfly 8.0 Application Server from http://www.wildfly.org/download/.
2. Unzip Wildfly 8.0 into any directory you want.
3. Open a command prompt or shell terminal, and then change to the wildfly-8.0.0.Final/bin directory.
4. You must start Wildfly manually before you can run the example. To do this, execute standalone.sh or standalone.bat, depending on whether you are using a Unix- or Windows-based system.
5. Open another command prompt or shell terminal and change to the ex14_1 directory of the workbook example code.
6. Make sure your PATH is set up to include both the JDK and Maven, as described in Chapter 17.
7. Perform the build and run the example by typing maven install.
As described before, the pom.xml file within the project is configured to use a special JBoss plug-in so that it can deploy the WAR file from the example to the application server. After the WAR is deployed, the client test code will be executed. Following the execution of the test, the WAR will be undeployed from JBoss by Maven.
Example ex14_2: Spring and JAX-RS
There isn’t much difference between the code of ex14_1 and ex14_2. The Java classes are basically the same, except all the EJB @Stateless annotations were removed from the JAX-RS resource classes because the example is using Spring instead of EJB for its component model.
Besides the removal of EJB metadata, the differences between the two projects are mainly packaging and configuration. If you look through the ex14_2 directory, you’ll see that we’re back to using embedded Jetty. The web.xml file is a tiny bit different than the EJB example, so let’s take a look at that first:
src/main/webapp/WEB-INF/web.xml
<web-app>
<context-param>
<param-name>spring-beans-file</param-name>
<param-value>META-INF/applicationContext.xml</param-value>
</context-param>
</web-app>
This example follows the Spring integration conventions discussed in Chapter 14. The web.xml file adds a <context-param> to point to the Spring XML file that holds all of the example’s Spring configuration. Let’s look at this Spring XML file:
src/main/resources/applicationContext.xml
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd"
default-autowire="byName">
<beanid="entityManagerFactory"
class="org.springframework.orm.jpa.
LocalContainerEntityManagerFactoryBean">
<propertyname="jpaVendorAdapter">
<beanclass="org.springframework.orm.jpa.vendor
.HibernateJpaVendorAdapter">
<propertyname="showSql"value="false"/>
<propertyname="generateDdl"value="true"/>
<propertyname="databasePlatform"
value="org.hibernate.dialect.HSQLDialect"/>
</bean>
</property>
</bean>
<beanid="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<propertyname="driverClassName"
value="org.hsqldb.jdbcDriver"/>
<propertyname="url"value="jdbc:hsqldb:test/db/myDB"/>
<propertyname="username"value="sa"/>
<propertyname="password"value=""/>
</bean>
<beanid="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"/>
<tx:annotation-driven/>
The first part of the Spring configuration file is the configuration required to get JPA and Spring to work together. While the package structure for the Spring example is simpler than the EJB one, you can see that the configuration is a bit more complex.
<beanclass="org.springframework.orm
.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<beanid="customer"class="com.restfully.shop.services
.CustomerResourceBean"/>
<beanid="product"class="com.restfully.shop.services
.ProductResourceBean"/>
<beanid="order"class="com.restfully.shop.services
.OrderResourceBean"/>
<beanid="store"class="com.restfully.shop.services
.StoreResourceBean"/>
</beans>
The rest of the Spring XML file defines all of the JAX-RS resource beans.
The Spring XML file is loaded and registered with the JAX-RS runtime by the ShoppingApplication class:
src/main/java/com/restfully/shop/services/ShoppingApplication.java
@ApplicationPath("/services")
publicclassShoppingApplicationextendsApplication
{
privateSet<Class<?>>classes=newHashSet<Class<?>>();
publicShoppingApplication()
{
classes.add(EntityNotFoundExceptionMapper.class);
}
publicSet<Class<?>>getClasses()
{
returnclasses;
}
protectedApplicationContextspringContext;
@Context
protectedServletContextservletContext;
publicSet<Object>getSingletons()
{
try
{
InitialContextctx=newInitialContext();
StringxmlFile=
(String)servletContext.getInitParameter("spring-beans-file");
springContext=newClassPathXmlApplicationContext(xmlFile);
}
catch(Exceptionex)
{
ex.printStackTrace();
thrownewRuntimeException(ex);
}
HashSet<Object>set=newHashSet();
set.add(springContext.getBean("customer"));
set.add(springContext.getBean("order"));
set.add(springContext.getBean("product"));
set.add(springContext.getBean("store"));
returnset;
}
}
The getSingletons() method is responsible for initializing Spring and registering any JAX-RS resource beans created by Spring with the JAX-RS runtime. It first looks up the name of the Spring XML configuration file. The filename is stored in a servlet context’s init parameter namedspring-beans-file. The getSingletons() method looks up this init parameter via the injected ServletContext.
After getSingletons() gets the name of the config file, it then initializes a Spring ApplicationContext from it. Finally, it looks up each JAX-RS bean within the project and registers it with the JAX-RS runtime.
Build and Run the Example Program
Perform the following steps:
1. Open a command prompt or shell terminal and change to the ex14_2 directory of the workbook example code.
2. Make sure your PATH is set up to include both the JDK and Maven, as described in Chapter 17.
3. Perform the build and run the example by typing maven install.
All materials on the site are licensed Creative Commons Attribution-Sharealike 3.0 Unported CC BY-SA 3.0 & GNU Free Documentation License (GFDL)
If you are the copyright holder of any material contained on our site and intend to remove it, please contact our site administrator for approval.
© 2016-2026 All site design rights belong to S.Y.A.