Deployment and Integration - REST and the JAX-RS Standard - RESTful Java with JAX-RS 2.0 (2013)

RESTful Java with JAX-RS 2.0 (2013)

Part I. REST and the JAX-RS Standard

Chapter 14. Deployment and Integration

Throughout this book, I have focused on teaching you the basics of JAX-RS and REST with simple examples that have very few moving parts. In the real world, though, your JAX-RS services are going to interact with databases and a variety of server-side component models. They will need to be secure and sometimes transactional. Chapter 3 was a very simple example of deploying JAX-RS within a Java EE environment. In this chapter, we’ll look into more deployment details of JAX-RS and how it integrates with Java EE and other component models.

Deployment

JAX-RS applications are deployed within a standalone servlet container, like Apache Tomcat, Jetty, JBossWeb, or the servlet container of your favorite application server, like JBoss, Wildfly, Weblogic, Websphere, or Glassfish. Think of a servlet container as a web server. It understands the HTTP protocol and provides a low-level component model (the servlet API) for receiving HTTP requests.

Servlet-based applications are organized in deployment units called Web ARchives (WAR). A WAR is a JAR-based packaging format that contains the Java classes and libraries used by the deployment as well as static content like images and HTML files that the web server will publish. Here’s what the structure of a WAR file looks like:

<any static content>

WEB-INF/

web.xml

classes/

lib/

Any files outside and above the WEB-INF/ directory of the archive are published and available directly through HTTP. This is where you would put static HTML files and images that you want to expose to the outside world. The WEB-INF/ directory has two subdirectories. Within the classes/directory, you can put any Java classes you want. They must be in a Java package structure. The lib/ directory can contain any application or third-party libraries that will be used by the deployment. The WEB-INF/ directory also contains a web.xml deployment descriptor file. This file defines the configuration of the WAR and how the servlet container should initialize it.

You will need to define a web.xml file for your JAX-RS deployments. How JAX-RS is deployed within a servlet container varies between JAX-RS-aware (like within Java EE application servers or standalone Servlet 3.x containers like Tomcat) and older JAX-RS–unaware servlet containers. Let’s dive into these details.

The Application Class

Before looking at what we have to do to configure a web.xml file, we need to learn about the javax.ws.rs.core.Application class. The Application class is the only portable way of telling JAX-RS which web services (@Path annotated classes) as well as which filters, interceptors,MessageBodyReaders, MessageBodyWriters, and ContextResolvers (providers) you want deployed. I first introduced you to the Application class back in Chapter 3:

package javax.ws.rs.core;

import java.util.Collections;

import java.util.Set;

public abstract class Application {

private static final Set<Object> emptySet =

Collections.emptySet();

public abstract Set<Class<?>> getClasses();

public Set<Object> getSingletons() {

return emptySet;

}

}

The Application class is very simple. All it does is list classes and objects that JAX-RS is supposed to deploy. The getClasses() method returns a list of JAX-RS web service and provider classes. JAX-RS web service classes follow the per-request model mentioned in Chapter 3. Provider classes are instantiated by the JAX-RS container and registered once per application.

The getSingletons() method returns a list of preallocated JAX-RS web services and providers. You, as the application programmer, are responsible for creating these objects. The JAX-RS runtime will iterate through the list of objects and register them internally. When these objects are registered, JAX-RS will also inject values for @Context annotated fields and setter methods.

Let’s look at a simple example of an Application class:

package com.restfully.shop.services;

import javax.ws.rs.core.Application;

public class ShoppingApplication extends Application {

public ShoppingApplication() {}

public Set<Class<?>> getClasses() {

HashSet<Class<?>> set = new HashSet<Class<?>>();

set.add(CustomerResource.class);

set.add(OrderResource.class);

set.add(ProduceResource.class);

return set;

}

public Set<Object> getSingletons() {

JsonWriter json = new JsonWriter();

CreditCardResource service = new CreditCardResource();

HashSet<Object> set = new HashSet();

set.add(json);

set.add(service);

return set;

}

}

Here, we have a ShoppingApplication class that extends the Application class. The getClasses() method allocates a HashSet, populates it with @Path annotated classes, and returns the set. The getSingletons() method allocates a MessageBodyWriter class namedJsonWriter and an @Path annotated class CreditCardResource. It then creates a HashSet and adds these instances to it. This set is returned by the method.

Deployment Within a JAX-RS-Aware Container

Java EE stands for Java Enterprise Edition. It is the umbrella specification of JAX-RS and defines a complete enterprise platform that includes services like a servlet container, EJB, transaction manager (JTA), messaging (JMS), connection pooling (JCA), database persistence (JPA), web framework (JSF), and a multitude of other services. Application servers that are certified under Java EE 6 are required to have built-in support for JAX-RS 1.1. Java EE 7 containers are required to have built-in support for JAX-RS 2.0.

For standalone Servlet 3.x containers like Tomcat and Jetty, most JAX-RS implementations can seamlessly integrate JAX-RS just as easily as with Java EE. They do this through the Servlet 3.0 ServletContainerInitializer SPI, which we will not cover here. The only difference between standalone servlet deployments and Java EE is that your WAR deployments will also need to include the libraries of your JAX-RS implementation.

Deploying a JAX-RS application is very easy in a JAX-RS-aware servlet container. You still need at least an empty web.xml file:

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

version="3.0">

</web-app>

If you have at least one Application class implementation annotated with @ApplicationPath, the JAX-RS–aware container will automatically deploy that Application. For example:

package com.restfully.shop.services;

import javax.ws.rs.core.Application;

import javax.ws.rs.ApplicationPath;

@ApplicationPath("/root")

public class ShoppingApplication extends Application {

public ShoppingApplication() {}

public Set<Class<?>> getClasses() {

HashSet<Class<?>> set = new HashSet<Class<?>>();

set.add(CustomerResource.class);

set.add(OrderResource.class);

set.add(ProduceResource.class);

return set;

}

public Set<Object> getSingletons() {

JsonWriter json = new JsonWriter();

CreditCardResource service = new CreditCardResource();

HashSet<Object> set = new HashSet();

set.add(json);

set.add(service);

return set;

}

}

The @ApplicationPath annotation here will set up a base path to whatever the WAR’s context root is, with root appended.

You can fully leverage the servlet class scanning abilities if you have both getClasses() and getSingletons() return an empty set. For example:

package com.restfully.shop.services;

import javax.ws.rs.core.Application;

import javax.ws.rs.ApplicationPath;

@ApplicationPath("/root")

public class ShoppingApplication extends Application {

// complete

}

When scanning, the application server will look within WEB-INF/classes and any JAR file within the WEB-INF/lib directory. It will add any class annotated with @Path or @Provider to the list of things that need to be deployed and registered with the JAX-RS runtime. You can also deploy as many Application classes as you want in one WAR. The scanner will also ignore any Application classes not annotated with @ApplicationPath.

You can also override the @ApplicationPath annotation via a simple servlet mapping within web.xml:

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee

http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

version="3.0">

<servlet-mapping>

<servlet-name>com.rest.ShoppingApplication</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>

</web-app>

The servlet-name is the fully qualified class name of your Application class. With this configuration, you can also omit the @ApplicationPath annotation entirely.

Deployment Within a JAX-RS-Unaware Container

If you are running in 2.x or older Servlet containers, you’ll have to manually configure your web.xml file to load your JAX-RS implementation’s proprietary servlet class. For example:

<?xml version="1.0"?>

<web-app>

<servlet>

<servlet-name>JAXRS</servlet-name>

<servlet-class>

org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher

</servlet-class>

<init-param>

<param-name>

javax.ws.rs.Application

</param-name>

<param-value>

com.restfully.shop.services.ShoppingApplication

</param-value>

</init-param>

</servlet>

<servlet-mapping>

<servlet-name>JAXRS</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>

</web-app>

Here, we’ve registered and initialized the RESTEasy JAX-RS implementation with the ShoppingApplication class we created earlier in this chapter. The <servlet-mapping> element specifies the base URI path for the JAX-RS runtime. The /* <url-pattern> specifies that all incoming requests should be routed through our JAX-RS implementation.

Configuration

All the examples in this book so far have been simple and pretty self-contained. Your RESTful web services will probably need to sit in front of a database and interact with other local and remote services. Your services will also need configuration settings that are described outside of code. I don’t want to get into too much detail, but the servlet and Java EE specifications provide annotations and XML configurations that allow you to get access to various Java EE services and configuration information. Let’s look at how JAX-RS can take advantage of these features.

Basic Configuration

Any JAX-RS implementation, whether it sits within a JAX-RS-aware or Java EE container, must support the @Context injection of the javax.servlet.ServletContext and javax.servlet.ServletConfig interfaces. Through these interfaces, you can get access to configuration information expressed in the WAR’s web.xml deployment descriptor. Let’s take this web.xml file, for example:

<?xml version="1.0"?>

<web-app>

<context-param>

<param-name>max-customers-size</param-name>

<param-value>10</param-value>

</context-param>

</web-app>

In this web.xml file, we want to define a default maximum dataset size for a JAX-RS–based customer database that returns a collection of customers through XML. We do this by defining a <context-param> named max-customers-size and set the value to 10. We can get access to this value within our JAX-RS service by injecting a reference to ServletContext with the @Context annotation. For example:

@Path("/customers")

public class CustomerResource {

protected int defaultPageSize = 5;

@Context

public void setServletContext(ServletContext context) {

String size = context.getInitParameter("max-customers-size");

if (size != null) {

defaultPageSize = Integer.parseInt(size);

}

}

@GET

@Produces("application/xml")

public String getCustomerList() {

... use defaultPageSize to create

and return list of XML customers...

}

}

Here, we use the @Context annotation on the setServletContext() method of our CustomerResource class. When an instance of CustomerResource gets instantiated, the setServletContext() method is called with access to a javax.servlet.ServletContext. From this, we can obtain the value of max-customers-size that we defined in our web.xml and save it in the member variable defaultPageSize for later use.

Another way you might want to do this is to use your javax.ws.rs.core.Application class as a factory for your JAX-RS services. You could define or pull in configuration information through this class and use it to construct your JAX-RS service. Let’s first rewrite ourCustomerResource class to illustrate this technique:

@Path("/customers")

public class CustomerResource {

protected int defaultPageSize = 5;

public void setDefaultPageSize(int size) {

defaultPageSize = size;

}

@GET

@Produces("application/xml")

public String getCustomerList() {

... use defaultPageSize to create and return list of XML customers...

}

}

We first remove all references to the ServletContext injection we did in our previous incarnation of the CustomerResource class. We replace it with a setter method, setDefaultPageSize(), which initializes the defaultPageSize member variable. This is a better design for ourCustomerResource class because we’ve abstracted away how it obtains configuration information. This gives the class more flexibility as it evolves over time.

We then inject the ServletContext into our Application class and extract the needed information to initialize our services:

import javax.ws.rs.core.Application;

import javax.naming.InitialContext;

@ApplicationPath("/")

public class ShoppingApplication extends Application {

public ShoppingApplication() {}

public Set<Class<?>> getClasses() {

return Collections.emptySet();

}

@Context

ServletContext servletContext

public Set<Object> getSingletons() {

int pageSize = 0;

try {

InitialContext ctx = new InitialContext();

Integer size =

(Integer)ctx.getInitParameter("max-customers-size");

pageSize = size.getValue();

} catch (Exception ex) {

... handle example ...

}

CustomerResource custService = new CustomerResource();

custService.setDefaultPageSize(pageSize);

HashSet<Object> set = new HashSet();

set.add(custService);

return set;

}

}

EJB Integration

EJBs are Java EE components that help you write business logic more easily. They support integration with security, transactions, and persistence. Further explanation of EJB is beyond the scope of this book. I suggest reading the book that I co-wrote with Andrew Rubinger, Enterprise JavaBeans 3.1 (O’Reilly), if you want more information. Java EE requires that EJB containers support integration with JAX-RS. You are allowed to use JAX-RS annotations on local interfaces or no-interface beans of stateless session or singleton beans. No other integration with other bean types is supported.

If you are using the full-scanning deployment mechanism I mentioned before, you can just implement your services and put the classes of your EJBs directly within the WAR, and JAX-RS will find them automatically. Otherwise, you have to return the bean class of each JAX-RS EJB from your Application.getClasses() method. For example, let’s say we have this EJB bean class:

@Stateless

@Path("/customers")

public class CustomerResourceBean implements CustomerResource {

...

}

If you are manually registering your resources via your Application class, you must register the bean class of the EJB via the Application.getClasses() method. For example:

package com.restfully.shop.services;

import javax.ws.rs.core.Application;

import javax.ws.rs.ApplicationPath;

@ApplicationPath("/root")

public class ShoppingApplication extends Application {

public Set<Class<?>> getClasses() {

HashSet<Class<?>> set = new HashSet<Class<?>>();

set.add(CustomerResourceBean.class);

return set;

}

}

Spring Integration

Spring is an open source framework similar to EJB. Like EJB, it provides a great abstraction for transactions, persistence, and security. Further explanation of Spring is beyond the scope of this book. If you want more information on it, check out Spring: A Developer’s Notebook by Bruce A. Tate and Justin Gehtland (O’Reilly). Most JAX-RS implementations have their own proprietary support for Spring and allow you to write Spring beans that are JAX-RS web services. If portability is not an issue for you, I suggest that you use the integration with Spring provided by your JAX-RS implementation.

There is a simple, portable way to integrate with Spring that we can talk about in this chapter. What you can do is write an Application class that loads your Spring XML files and then registers your Spring beans with JAX-RS through the getSingletons() method. First, let’s define a Spring bean that represents a customer database. It will pretty much look like the CustomerResource bean described in EJB Integration:

@Path("/customers")

public interface CustomerResource {

@GET

@Produces("application/xml")

public String getCustomers();

@GET

@Produces("application/xml")

@Path("{id}")

public String getCustomer(@PathParam("id") int id);

}

In this example, we first create an interface for our CustomerResource that is annotated with JAX-RS annotations:

public class CustomerResourceBean implements CustomerResource {

public String getCustomers() {...}

public String getCustomer(int id) {...}

}

Our Spring bean class, CustomerResourceBean, simply implements the CustomerResource interface. Although you can opt to not define an interface and use JAX-RS annotations directly on the bean class, I highly suggest that you use an interface. Interfaces work better in Spring when you use features like Spring transactions.

Now that we have a bean class, we should declare it within a Spring XML file called spring-beans.xml (or whatever you want to name the file):

<beans xmlns="http://www.springframework.org/schema/beans"

<bean id="custService"

class="com.shopping.restful.services.CustomerResourceBean"/>

</beans>

Place this spring-beans.xml file within your WAR’s WEB-INF/classes directory or within a JAR within the WEB-INF/lib directory. For this example, we’ll put it in the WEB-INF/classes directory. We will find this file through a class loader resource lookup later on when we write ourApplication class.

Next we write our web.xml file:

<web-app>

<context-param>

<param-name>spring-beans-file</param-name>

<param-value>META-INF/applicationContext.xml</param-value>

</context-param>

</web-app>

In our web.xml file, we define a <context-param> that contains the classpath location of our Spring XML file. We use a <context-param> so that we can change this value in the future if needed. We then need to wire everything together in our Application class:

@ApplicationPath("/")

public class ShoppingApplication extends Application

{

protected ApplicationContext springContext;

@Context

protected ServletContext servletContext;

public Set<Object> getSingletons()

{

try

{

InitialContext ctx = new InitialContext();

String xmlFile = (String)servletContext.getInitParameter

("spring-beans-file");

springContext = new ClassPathXmlApplicationContext(xmlFile);

}

catch (Exception ex)

{

throw new RuntimeException(ex);

}

HashSet<Object> set = new HashSet();

set.add(springContext.getBean("customer"));

return set;

}

}

In this Application class, we look up the classpath location of the Spring XML file that we defined in the <context-param> of our web.xml deployment descriptor. We then load this XML file through Spring’s ClassPathXmlApplicationContext. This will also create the beans defined in this file. From the Spring ApplicationContext, we look up the bean instance for our CustomerResource using the ApplicationContext.getBean() method. We then create a HashSet and add the CustomerResource bean to it and return it to be registered with the JAX-RS runtime.

Wrapping Up

In this chapter, you learned how deployment works within Java EE and standalone Servlet 3.x containers as well as in environments that are JAX-RS aware. We also looked at some portable ways to configure your JAX-RS applications. Finally, you saw how you can portably integrate with EJB and Spring. Chapter 28 will allow you to test-drive some of the concepts presented in this chapter. It will walk you through the deployment of a full application that integrates with EJB, Spring, and Java Persistence (JPA).