My Wiki!

Javaee tutorial 2: Building The Business Services With JAX-RS

  • Encapsulating business logic in services and integrating with the persistence tier
  • Using CDI for integrating individual services
  • Integration testing using Arquillian
  • Exposing RESTful services via JAX-RS

Business Services And Their Relationships

business logic is implemented by a number of classes, with different responsibilities:

managing media items

allocating tickets

handling information on ticket availability

remote access through a RESTful interface

The services are consumed by various other layers of the application:

the media management and ticket allocation services encapsulate complex functionality, which in turn is exposed externally by RESTful services that wrap them

RESTful services are mainly used by the HTML5 view layer

the ticket availability service is used by the HTML5 and JavaScript based monitor

Tip

Where to draw the line?

A business service is an encapsulated, reusable logical component that groups together a number of well-defined cohesive business operations. Business services perform business operations, and may coordinate infrastructure services such as persistence units, or even other business services as well. The boundaries drawn between them should take into account whether the newly created services represent , potentially reusable components.

As you can see, some of the services are intended to be consumed within the business layer of the application, while others provide an external interface as JAX-RS services. We will start by implementing the former, and we’ll finish up with the latter. During this process, you will discover how CDI, EJB and JAX-RS make it easy to define and wire together our services.

Preparation

Adding Jackson Core

The first step for setting up our service architecture is to add Jackson Core as a dependency in the project. Adding Jackson Core as a provided dependency will enable you to use the Jackson annotations in the project. This is necessary to obtain a certain degree of control over the content of the JSON responses.

pom.xml

<project ...>
    ...
    <dependencies>
        <!-- This is the dependency for Jackson Core, which we use for
            fine tuning the content of the JSON responses -->
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-core-asl</artifactId>
            <version>1.8.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    ...
</project>

Note

Why do you need the Jackson annotations?

JAX-RS does not specify mediatype-agnostic annotations for certain use cases. You will encounter atleast one of them in the project. The object graph contains cyclic/bi-directional relationships among entities like Venue, Section, Show, Performance and TicketPrice. JSON representations for these objects will need tweaking to avoid stack oVerflow errors and the like, at runtime.

JBoss Enterprise Application 6 uses Jackson to perform serialization and dserialization of objects, thus requiring use of Jackson annotations to modify this behavior. @JsonIgnoreProperties from Jackson will be used to suppress serialization and deserialization of one of the fields involved in the cycle.

Enabling CDI

The next step is to enable CDI in the deployment by creating a beans.xml file in the WEB-INF folder of the web application. src/main/webapp/WEB-INF/beans.xml

<beans 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/beans_1_0.xsd">
 </beans>

Note

If you used the Maven archetype

If you used the Maven archetype to create the project, this file will exist already in the project - it is added automatically.

You may wonder why the file is empty! Whilst beans.xml can specify various deployment-time configuration (e.g. activation of interceptors, decorators or alternatives), it can also act as a marker file, telling the container to enable CDI for the deployment (which it doesn’t do, unless beans.xml is present). Tip

Contexts and Dependency Injection (CDI)

As it’s name suggests, CDI is the contexts and dependency injection standard for Java EE. By enabling CDI in your application, deployed classes become managed components and their lifecycle and wiring becomes the responsibility of the Java EE server.

In this way, we can reduce coupling between components, which is a requirement o a well-designed architecture. Now, we can focus on implementing the responsibilities of the components and describing their dependencies in a declarative fashion. The runtime will do the rest for you: instantiating and wiring them together, as well as disposing of them as needed

Adding utility classes

Copy the following classes from the original example to src/main/java/org/jboss/jdf/example/ticketmonster/util:

Base64

ForwardingMap

MultivaluedHashMap

Reflections

Resources

Internal Services/Businesses

ItemManager

JAX-RS Services

The majority of services in the application are JAX-RS web services. They are critical part of the design, as they next service is used for provide communication with the HTML5 view layer. The JAX-RS services range from simple CRUD to processing bookings and media items.

To pass data across the wire we use JSON as the data marshalling format, as it is less verbose and easier to process than XML by the JavaScript client-side framework.

Initializing JAX-RS

To activate JAX-RS we add the class below, which instructs the container to look for JAX-RS annotated classes and install them as endpoints. This class should exist already in your project, as it is generated by the archetype, so make sure that it is there and it contains the code below: src/main/java/org/jboss/jdf/example/ticketmonster/rest/JaxRsActivator.java

@ApplicationPath(“/rest”) public class JaxRsActivator extends Application {

 /* class body intentionally left blank */

}

All the JAX-RS services are mapped relative to the /rest path, as defined by the @ApplicationPath annotation.

A Base Service For Read Operations

Most JAX-RS services must provide both a (filtered) list of entities or individual entity (e.g. events, venues and bookings). Instead of duplicating the implementation into each individual service we create a base service class and wire the helper objects in.

BaseEntityService.java

/**
 * <p>
 *   A number of RESTful services implement GET operations on a particular type of entity. For
 *   observing the DRY principle, the generic operations are implemented in the code>BaseEntityService/code>
 *   class, and the other services can inherit from here.
 * </p>
 *
 * <p>
 *    Subclasses will declare a base path using the JAX-RS {@link Path} annotation, for example:
 * </p>
 *
 * <pre>
 * code>
 * &#064;Path("/widgets")
 * public class WidgetService extends BaseEntityService<Widget> {
 * ...
 * }
 * /code>
 * </pre>
 *
 * <p>
 *   will support the following methods:
 * </p>
 *
 * <pre>
 * code>
 *   GET /widgets
 *   GET /widgets/:id
 *   GET /widgets/count
 * /code>
 * </pre>
 *
 *  <p>
 *     Subclasses may specify various criteria for filtering entities when retrieving a list of them, by supporting
 *     custom query parameters. Pagination is supported by default through the query parameters code>first/code>
 *     and code>maxResults/code>.
 * </p>
 *
 * <p>
 *     The class is abstract because it is not intended to be used directly, but subclassed by actual JAX-RS
 *     endpoints.
 * </p>
 *
 */
public abstract class BaseEntityService<T> {
 
    @Inject
    private EntityManager entityManager;
 
    private Class<T> entityClass;
 
    public BaseEntityService() {}
 
    public BaseEntityService(Class<T> entityClass) {
        this.entityClass = entityClass;
    }
 
    public EntityManager getEntityManager() {
        return entityManager;
    }
 
}

Retrieving Auctions

Creating and deleting Auctions

Testing

Setup Test

Since Arquillian needs to perform JNDI lookups to get references to the components under test, we need to include a jndi.properties file on the test classpath. Create the file src/test/resources/jndi.properties and populate it with the following contents:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces 
java.naming.provider.url=jnp://localhost:1099

Next, we're going to return to pom.xml to add another dependency. Arquillian picks which container it's going to use to deploy the test archive and negotiate test execution using the service provider mechanism, meaning which implementation of the DeployableContainer SPI is on the classpath. We'll control that through the use of Maven profiles. Add the following profiles to pom.xml:

</code>

<profiles>

 <profile>
    <id>jbossas-remote-60</id>
    <dependencies>
       <dependency>
          <groupId>org.jboss.arquillian.container</groupId>
          <artifactId>arquillian-jbossas-remote-60</artifactId>
          <version>${arquillian.version}</version>
       </dependency>
    </dependencies>
 </profile>

</profiles>

</code>

You would setup a similar profile for each Arquillian-supported container in which you want your tests executed.

All that's left is to execute the tests. In Maven, that's easy. Simply run the Maven test goal with the jbossas-remote-60 profile activated:

mvn test -Pjbossas-remote-60

Skip test

 mvn package -DskipTests=true

Running the test

If you have followed the instructions in the introduction and used the Maven archetype to generate the project structure, you should have two profiles already defined in your application.

/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
        ...
        <profile>
            <!-- An optional Arquillian testing profile that executes tests
                in your JBoss AS instance -->
            <!-- This profile will start a new JBoss AS instance, and execute
                the test, shutting it down when done -->
            <!-- Run with: mvn clean test -Parq-jbossas-managed -->
            <id>arq-jbossas-managed</id>
            <dependencies>
                <dependency>
                    <groupId>org.jboss.as</groupId>
                    <artifactId>jboss-as-arquillian-container-managed</artifactId>
                    <scope>test</scope>
                </dependency>
            </dependencies>
        </profile>
 
        <profile>
            <!-- An optional Arquillian testing profile that executes tests
                in a remote JBoss AS instance -->
            <!-- Run with: mvn clean test -Parq-jbossas-remote -->
            <id>arq-jbossas-remote</id>
            <dependencies>
                <dependency>
                    <groupId>org.jboss.as</groupId>
                    <artifactId>jboss-as-arquillian-container-remote</artifactId>
                    <scope>test</scope>
                </dependency>
            </dependencies>
        </profile>
 
    </profiles>
</project>

If you haven’t used the archetype, or the profiles don’t exist, create them.

Each profile defines a different Arquillian container. In both cases the tests execute in an application server instance. In one case (arq-jbossas-managed) the server instance is started and stopped by the test suite, while in the other (arq-jbossas-remote), the test suite expects an already started server instance.

Once these profiles are defined, we can execute the tests in two ways:

from the command-line build

from an IDE

Executing tests from the command line

You can now execute the test suite from the command line by running the Maven build with the appropriate target and profile, as in one of the following examples.

After ensuring that the JBOSS_HOME environment variable is set to a valid JBoss EAP 6.2 installation directory), you can run the following command:

 mvn clean test -Parq-jbossas-managed

Or, after starting a JBoss EAP 6.2 instance, you can run the following command

 mvn clean test -Parq-jbossas-remote

These tests execute as part of the Maven build and can be easily included in an automated build and test harness.

Test troubleshooting

After fixing the bom.version as described above, start a clean system: kill old test, process, eclipse.

Deployment

  mvn package 
  cp target/*.war JBOSS_HOME/standalone/deployments

Navigation