JEE : Using EJB and Context annotations in a JAX-RS Provider class

A lot of new annotations have been introduced since the JEE6 spec. Before we had EJBs and servlets to cover most of our server-side objects in a JEE application. But in JEE6, CDI and JAX-RS have been added, along with a few other JSRs that are implemented using annotations. This results in a long list of annotations, where – in my opinion – it is not always clear which one to use, nor to understand how they work together.

In this blog, I would like to show you how EJB and CDI work or don’t work together, in combination with a JAX-RS Resource.

JAX-RS is used for writing REST-services. It allows you to create these services, simply by annotating a Java class.

This is our REST service class aka “resource class”. It will create a REST service on the /test url, an it returns a String. Here, for test purposes, we always generate a RuntimeException.


@Path("/test")
public class TestResource {
@GET
public String getCustomer() {
if (1==1) throw new RuntimeException("Whoops, something goes wrong");

return "Customer ABC";
}
}

Because we don’t want to send a stack trace to the client, but a meaningful response in the form of an Error class, we use a provider class and annotate it with @Provider (javax.ws.rs.ext.Provider). This annotation is part of the JAX-RS spec.

Annotating a class with @Provider – according to the API-documentation – “marks an implementation of an extension interface that should be discoverable by JAX-RS runtime during a provider scanning phase.”

Our provider class will catch any exception thrown by the resource class, and send a proper response object to the client instead.

The returned Error class, that will be marshalled to an xml object and returned to the client :

@XmlRootElement

public class Error {
private String desc;
public Error() {
super();
}

public Error(String desc) {
super();
this.desc = desc;
}

public String getDesc() {
return desc;
}

public void setDesc(String desc) {
this.desc = desc;
}

}

The provider class that will catch any exception and return the error class looks like :


@Provider

public class GenericExceptionMapper implements ExceptionMapper<Exception> {

@Context HttpServletRequest httpRequest;

public Response toResponse(Exception ex) {
return Response.status(500).entity(new Error("Error during call with method : "+httpRequest.getMethod())).build();

}

}

As you can, see, we use the @Context annotation to access the current http request. When an exception is thrown by the resource class, it will automatically be handled by the toResponse method of our provider class. It returns a response, containing the Error class, that will be sent to the client in xml format. This is done by the JAX-RS framework.

Now we have an EJB bean that we want to use in order to log this error to a database, before sending the response. So we will add this to the provider class as follows :


@Stateless

@Provider

public class GenericExceptionMapper implements ExceptionMapper<Exception> {

@Context HttpServletRequest httpRequest;

@EJB ErrorEJB errorEJB;

public Response toResponse(Exception ex) {
// log the error to the database
errorEJB.logError(ex));
// return the response
return Response.status(500)
.entity(new Error("Error during call with method : "+httpRequest.getMethod()))
.build();
}

We made the GenericExceptionMapper a stateless bean using @Stateless, in order to be able to inject the ErrorEJB into the provider.

But now, we will get a NullPointerException on the httpRequest.getMethod, as the @Context annotation won’t work  anymore.

This is because, before our update, we only used the @Provider annotation , so this class was a CDI bean. With CDI, the container looks up the injected classes in a “scope”, which will basically be a hashmap that exists for a specific period of time :

  • @RequestScoped : per request
  • @SessionScoped : per HTTP session
  • @ApplicationScoped : per application

Into that environment we can inject the HttpServletRequest using the @Context annotation

By adding the @Statefull annotation, we made an EJB from our CDI bean.

With EJBs, the container looks also into a hashmap, but checks if the bean is of type @Stateful, @Stateless or @Singleton. So it isn’t aware of any http context.

In order to solve this problem, we will inject the our ErrorEJB as CDI bean iso an EJB.

This can be done as follows :

  • replace the @Stateless by @RequestScoped
  • replace @EJB by @Inject
  • adding an empty beans.xml file to the WEB-INF

Which gives us :


@RequestScoped

@Provider

public class GenericExceptionMapper implements ExceptionMapper<Exception> {

@Context HttpServletRequest httpRequest;

@Inject ErrorEJB errorEJB;

public Response toResponse(Exception ex) {

errorEJB.logError(ex));

return Response.status(500)
.entity(new Error("Error during call with method : "+httpRequest.getMethod()))
.build();

}

Now for JEE7, there are even more annotations available, so make sure to check the docs before using them.

Advertisements

3 thoughts on “JEE : Using EJB and Context annotations in a JAX-RS Provider class

  1. Hi Chris, thank you for the above. I tried it on Apache Tomee 1.7 but no EJB injection into the @Provider happens. All the other EJB injections work in the Rest and other EJB’s. Thought you might have some insight to why this is happening.

    1. Hi Rentius,
      I haven’t tried this in JEE7, but I think it should work. I ran my example on a Weblogic 11. But just to be clear, did you use the @Inject annotation for the injection of the EJB ? The @Provider should be a javax.ws.rs.ext.Provider annotation (was not very clear in my blog).

      grtz,
      Chris

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

About Chris Noë