JBoss.orgCommunity Documentation
JAX-RS (Java API for RESTful Web Services) is a Java EE standard (JSR-311) for implementing REST-based Web services in Java. Errai JAX-RS brings this standard to the browser and simplifies the integration of REST-based services in GWT client applications. Errai can generate proxies based on JAX-RS interfaces which will handle all the underlying communication and serialization logic. All that’s left to do is to invoke a Java method.
Errai’s JAX-RS support consists of the following:
Checkout the Manual Setup Section for instructions on how to manually add JAX-RS to your project. You can also go to the Errai tutorial project or the demo collection for an example of JAX-RS.
Errai’s JAX-RS support consists mostly of features that make the client side easier and more reliable to maintain. You will need to use an existing third-party JAX-RS implementation on the server side. All Java EE 6 application servers include such a module out-of-the-box. If you are developing an application that you intend to deploy on a plain servlet container, you will have to choose a JAX-RS implementation (for example, RestEasy) and configure it properly in your web.xml.
Alternatively, you could keep your REST resource layer in a completely separate web application hosted on the same server (perhaps build an Errai JAX-RS client against an existing REST service you developed previously). In this case, you could factor out the shared JAX-RS interface into a shared library, leaving the implementation in the non-Errai application.
Finally, you can take advantage of the cross-origin resource sharing (CoRS) feature in modern browsers and use Errai JAX-RS to send requests to a third-party server. The third-party server would have to be configured to allow cross-domain requests. In this case, you would write a JAX-RS-Annotated interface describing the remote REST resources, but you would not create an implementation of that interface.
Errai JAX-RS works by leveraging standard Java interfaces that bear JAX-RS annotations. You will also want these interfaces visible to server-side code so that your JAX-RS resource classes can implement them (and inherit the annotations). This keeps the whole setup typesafe, and reduces duplication to the bare minimum. The natural solution, then is to put the JAX-RS interfaces under the client.shared package within your GWT module:
project
src/main/java
com.mycompany.myapp
com.mycompany.myapp.client.local
com.mycompany.myapp.client.shared
com.mycompany.myapp.server
The contents of the server-side files would be as follows:
Example 7.1. CustomerService.java
@Path("customers")
public interface CustomerService {
@GET
@Produces("application/json")
public List<Customer> listAllCustomers();
@POST
@Consumes("application/json")
@Produces("text/plain")
public long createCustomer(Customer customer);
}
The above interface is visible both to server-side code and to client-side code. It is used by client-side code to describe the available operations, their parameter types, and their return types. If you use your IDE’s refactoring tools to modify this interface, both the server-side and client-side code will be updated automatically.
Example 7.2. CustomerServiceImpl.java
public class CustomerServiceImpl implements CustomerService {
@Override
public List<Customer> listAllCustomers() {
// Use a database API to look up all customers in back-end data store
// Return the resulting list
}
@Override
public long createCustomer(Customer customer) {
// Store new Customer instance in back-end data store
}
}
The above class implements the shared interface. Since it performs database and/or filesystem operations to manipulate the persistent data store, it is not GWT translatable, and it’s therefore kept in a package that is not part of the GWT module.
Note that all JAX-RS annotations (@Path, @GET, @Consumes, and so on) can be inherited from the interface. You do not need to repeat these annotations in your resource implementation classes.
This section assumes you have already set up the CustomerService JAX-RS endpoint as described in the previous section.
To create a request on the client, all that needs to be done is to invoke RestClient.create(), thereby providing the JAX-RS interface, a response callback and to invoke the corresponding interface method:
Example 7.3. App.java
...
Button create = new Button("Create", new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
Customer customer = new Customer(firstName, lastName, postalCode);
RestClient.create(CustomerService.class, callback).createCustomer(customer);
}
});
...
For details on the callback mechanism see Handling Responses.
Injectable proxies can be used as an alternative to calling RestClient.create().
@Inject
private Caller<CustomerService> customerService;
To create a request, the callback objects need to be provided to the call method before the corresponding interface method is invoked.
customerService.call(callback).listAllCustomers();
To use caller injection, your application needs to inherit the Errai IOC GWT module. To do this, just add this line to your application’s *.gwt.xml file and make sure it comes after the Errai JAX-RS module (see Getting Started):
<inherits name="org.jboss.errai.ioc.Container"/>
The JAX-RS interfaces need to be visible to the GWT compiler and must therefore reside within the client packages (e.g. client.shared).
An instance of Errai’s RemoteCallback<T> has to be passed to the RestClient.create() call, which will provide access to the JAX-RS resource method’s result. T is the return type of the JAX-RS resource method. In the example below it’s just a Long representing a customer ID, but it can be any serializable type (see Marshalling).
RemoteCallback<Long> callback = new RemoteCallback<Long>() {
public void callback(Long id) {
Window.alert("Customer created with ID: " + id);
}
};
One special use case for the RemoteCallback is for JAX-RS interface methods that return a javax.ws.rs.core.Response type. Errai does not provide a client implementation of the javax.ws.rs.core.Response class. Instead calls to methods returning a javax.ws.rs.core.Response should use a RemoteCallback<com.google.gwt.http.client.Response>. The MarshallingWrapper class can be used to manually deserialize an entity from the body of a response.
RemoteCallback<Response> callback = new RemoteCallback<Response>() {
public void callback(Response response) {
Window.alert("HTTP status code: " + response.getStatusCode());
Window.alert("HTTP response body: " + response.getText());
Window.alert("Demarshalled entity: " + MasrhallingWrapper.fromJSON(response.getText()));
}
};
For handling errors, Errai’s error callback mechanism can be reused and an instance of ErrorCallback can optionally be passed to the RestClient.create() call. In case of an HTTP error, the ResponseException provides access to the Response object. All other Throwables indicate a communication problem.
ErrorCallback errorCallback = new RestErrorCallback() {
public boolean error(Request request, Throwable throwable) {
try {
throw throwable;
}
catch (ResponseException e) {
Response response = e.getResponse();
// process unexpected response
response.getStatusCode();
}
catch (Throwable t) {
// process unexpected error (e.g. a network problem)
}
return false;
}
};
To provide more customized error handling, Errai also defines client side exception handling via the ClientExceptionMapper interface. The client exception mapper allows developers to process a REST Response into a Throwable prior to the error being delivered to the ErrorCallback described above. The exception mapper class must be annotated with javax.ws.rs.ext.Provider as well as implement the ClientExceptionMapper interface.
@Provider
public class MyAppExceptionMapper implements ClientExceptionMapper {
/**
* @see org.jboss.errai.enterprise.client.jaxrs.ClientExceptionMapper#fromResponse(com.google.gwt.http.client.Response)
*/
@Override
public Throwable fromResponse(Response response) {
if (response.getStatusCode() == 404)
return new MyAppNotFoundException();
else
return new MyAppServerError(response.getStatusText());
}
}
It is important to note that the ClientExceptionMapper will only be invoked when the callback passed to the Caller is an instance of RestErrorCallback.
The ClientExceptionMapper will, by default, be invoked for every error response. However, Errai also provides the org.jboss.errai.enterprise.shared.api.annotations.MapsFrom annotation which provides for additional granularity. An exception mapper can be annotated so that it is only invoked for methods on specific REST interfaces.
@Provider
@MapsFrom({ SomeRestInterface.class })
public class SpecificClientExceptionMapper implements ClientExceptionMapper {
/**
* @see org.jboss.errai.enterprise.client.jaxrs.ClientExceptionMapper#fromResponse(com.google.gwt.http.client.Response)
*/
@Override
public Throwable fromResponse(Response response) {
... // Do something specific here
}
}
An instance of Errai’s RequestCallback can optionally be passed to the RestClient.create() call, which will provide access to the underlying HTTP request. The callback is invoked synchronously after the HTTP request has been initiated. This means that you will have access to the request immediately after the call.
RequestCallback requestCallback = new RequestCallback() {
@Override
public void callback(Request request) {
this.request = request;
}
};
RestClient.create(SearchService.class, callback, requestCallback).find(query);
This allows you to cancel a pending request. Alternatively, a RequestHolder can be used instead.
RequestHolder searchRequestHolder = new RequestHolder();
RestClient.create(SearchService.class, callback, searchRequestHolder).find(query);
...
if (searchRequestHolder.isAlive()) {
searchRequestHolder.getRequest().cancel();
}
Client-side remote call interceptors provide the ability to manipulate or bypass the request before it’s being sent. This is useful for implementing crosscutting concerns like caching or security features e.g:
To have a JAX-RS remote call intercepted, either an interface method or the JAX-RS implementation class method has to be annotated with @InterceptedCall. If the type is annotated, all interface methods will be intercepted.
@Path("customers")
public interface CustomerService {
@GET
@Path("/{id}")
@Produces("application/json")
@InterceptedCall(MyCacheInterceptor.class)
public Customer retrieveCustomerById(@PathParam("id") long id);
}
Note that an ordered list of interceptors can be used for specifying an interceptor chain e.g.
@InterceptedCall({MyCacheInterceptor.class, MySecurityInterceptor.class})
public Customer retrieveCustomerById(@PathParam("id") long id);
Implementing an interceptor is easy:
public class MyCacheInterceptor implements RestClientInterceptor {
@Override
public void aroundInvoke(final RestCallContext context) {
RequestBuilder builder = context.getRequestBuilder();
builder.setHeader("headerName", "value");
context.proceed();
}
}
The RestCallContext passed to the aroundInvoke method provides access to the context of the intercepted JAX-RS (REST) remote call. It allows to read and write the parameter values provided at the call site and provides read/write access to the RequestBuilder instance which has the URL, HTTP headers and parameters set.
Calling proceed executes the next interceptor in the chain or the actual remote call if all interceptors have already been executed. If access to the result of the (asynchronous) remote call is needed in the interceptor, one of the overloaded versions of proceed accepting a RemoteCallback has to be used instead.
The result of the remote call can be manipulated by calling RestCallContext.setResult().
Not calling proceed in the interceptor bypasses the actual remote call, passing RestCallContext.getResult() to the RemoteCallBack provided at the call site.
If you cannot (or do not wish to) annotate the JAX-RS interface you may instead define remote call interceptors by annotating the interceptor class itself with @InterceptsRemoteCall. This annotation requires the developer to specify the JAX-RS interface that should be intercepted. The interceptor will then be applied to all methods in that interface. If the interface method is annotated with InterceptedCall, that annotation will win out.
@InterceptsRemoteCall({ MyJaxrsInterface.class, MyOtherJaxrsInterface.class })
public class MyCacheInterceptor implements RestClientInterceptor {
@Override
public void aroundInvoke(final RestCallContext context) {
// Do interceptor logic here
context.proceed();
}
}
This approach sacrifices granularity (you cannot intercept individual methods on the JAX-RS interface). However, it does allow method interception without modification to the JAX-RS interface (which is particularly useful when the developer is not in control of the JAX-RS interface).
It is worth noting that interceptors may be defined as managed beans using the @Dependent, @Singleton, or @ApplicationScoped annotations. If the Errai application is using IOC (i.e. imports the IOC Errai module) and the interceptor is annotated as a managed bean, then the IOC container will be used to get/create the interceptor instance. This allows developers to @Inject dependencies into interceptors. If IOC is not being used, or else the interceptor is not properly annotated, then the interceptor class will simply be instantiated via new.
Errai’s JSON format will be used to serialize/deserialize your custom types. See Marshalling for details.
Alternatively, a Jackson compatible JSON format can be used on the wire. See Configuration for details on how to enable Jackson marshalling.
All paths specified using the @Path annotation on JAX-RS interfaces are by definition relative paths. Therefore, by default, it is assumed that the JAX-RS endpoints can be found at the specified paths relative to the GWT client application’s context path.
To learn more about configuring the path, checkout the JAX-RS Configuration Section.
Errai provides a filter for protecting REST endpoints from Cross Site Request Forgery (CSRF) attacks. The filter org.jboss.errai.bus.server.servlet.CSRFTokenFilter
generates a CSRF token for every HTTP session that is required as an HTTP header for all POST, PUT, and DELETE requests. Requests made with an Errai Caller<T>
will automatically negotiate for a CSRF token, or the same filter can be added to the host page, where it will inject the token onto the page as a JavaScript variable.