JBoss.orgCommunity Documentation

Chapter 6. Remote Procedure Calls (RPC)

6.1. Creating an RPC Interface
6.2. Making calls
6.2.1. Proxy Injection
6.3. Handling exceptions
6.3.1. Global RPC exception handler
6.4. Client-side Interceptors
6.4.1. Annotating the Remote Interface
6.4.2. Implementing an Interceptor
6.4.3. Annotating the Interceptor (alternative)
6.4.4. Interceptors and IOC
6.5. Session and request objects in RPC endpoints
6.6. Batching remote calls
6.7. Asynchronous handling of RPCs on the server

ErraiBus supports a high-level RPC layer to make typical client-server RPC communication easy on top of the bus. While it is possible to use ErraiBus without ever using this API, you may find it to be a more useful and concise approach for exposing services to the clients.

Please note that this API has changed since version 1.0. RPC services provide a way of creating type-safe mechanisms to make client-to-server calls. Currently, this mechanism only support client-to-server calls, and not vice-versa.

Plugin Tip

Using the Errai Forge Addon Add Errai Features command, Errai RPC can be used if Errai Messaging or Errai CDI have been installed.

Manual Setup

Checkout the Manual Setup Section for instructions on how to manually add Errai Messaging or Errai CDI to a project.

Creating a service is straight forward. It requires the definition of a remote interface, and a service class which implements it. See the following:

@Remote

public interface MyRemoteService {
  public boolean isEveryoneHappy();
}

The @Remote annotation tells Errai that we’d like to use this interface as a remote interface. The remote interface must be part of of the GWT client code. It cannot be part of the server-side code, since the interface will need to be referenced from both the client and server side code. That said, the implementation of a service is relatively simple to the point:

@Service

public class MyRemoteServiceImpl implements MyRemoteService {
  public boolean isEveryoneHappy() {
    // blatently lie and say everyone's happy.
    return true;
  }
}

That’s all there is to it. You use the same @Service annotation as described in Section 2.4. The presence of the remote interface tips Errai off as to what you want to do with the class.


<servlet>
  <servlet-name>ErraiServlet</servlet-name>
  <servlet-class>org.jboss.errai.bus.server.servlet.DefaultBlockingServlet</servlet-class>
  <init-param>
    <param-name>auto-discover-services</param-name>
    <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

Calling a remote service involves use of the MessageBuilder API. Since all messages are asynchronous, the actual code for calling the remote service involves the use of a callback, which we use to receive the response from the remote method. Let’s see how it works:

MessageBuilder.createCall(new RemoteCallback<Boolean>() {

  public void callback(Boolean isHappy) {
    if (isHappy) Window.alert("Everyone is happy!");
  }
 }, MyRemoteService.class).isEveryoneHappy();

In the above example, we declare a remote callback that receives a Boolean, to correspond to the return value of the method on the server. We also reference the remote interface we are calling, and directly call the method. However, don’t be tempted to write code like this :

 boolean bool = MessageBuilder.createCall(..., MyRemoteService.class).isEveryoneHappy();

The above code will never return a valid result. In fact, it will always return null, false, or 0 depending on the type. This is due to the fact that the method is dispatched asynchronously, as in, it does not wait for a server response before returning control. The reason we chose to do this, as opposed to emulate the native GWT-approach, which requires the implementation of remote and async interfaces, was purely a function of a tradeoff for simplicity.

Handling remote exceptions can be done by providing an ErrorCallback on the client:

MessageBuilder.createCall(

  new RemoteCallback<Boolean>() {
    public void callback(Boolean isHappy) {
      if (isHappy) Window.alert("Everyone is happy!");
    }
  },
  new ErrorCallback() {
    public boolean error(Message message, Throwable caught) {
      try {
        throw caught;
      }
      catch (NobodyIsHappyException e) {
        Window.alert("OK, that's sad!");
      }
      catch (Throwable t) {
        GWT.log("An unexpected error has occurred", t);
      }
      return false;
    }
  },
  MyRemoteService.class).isEveryoneHappy();

As remote exceptions need to be serialized to be sent to the client, the @Portable annotation needs to be present on the corresponding exception class (see Marshalling>). Further the exception class needs to be part of the client-side code. For more details on ErrorCallbacks see <<sid-5931306, Handling Errors .

Client-side remote call interceptors provide the ability to manipulate or bypass the remote call before it’s being sent. This is useful for implementing crosscutting concerns like caching, for example when the remote call should be avoided if the data is already cached locally.

Before invoking an endpoint method Errai sets up an RpcContext that provides access to message resources that are otherwise not visible to RPC endpoints.

@Service

public class MyRemoteServiceImpl implements MyRemoteService {
  public boolean isEveryoneHappy() {
    HttpSession session = RpcContext.getHttpSession();
    ServletRequest request = RpcContext.getServletRequest();
    ...
    return true;
  }
}

Some use cases require multiple interactions with the server to complete. Errai’s RPC mechanism allows for batched invocations of remote methods that will be executed using a single server round-trip. This is useful for reducing the number of simultaneous HTTP connections and at the same time allows for reusing and combining fine-grained remote services.

Injecting a BatchCaller instead of a Caller<T> is all it takes to make use of batched remote procedure calls.

@EntryPoint

public class MyBean {
 @Inject
 private BatchCaller batchCaller;
 private void someMethod() {
    // ...
    batchCaller.call(remoteCallback1, RemoteService1.class).method1();
    batchCaller.call(remoteCallback2, RemoteService2.class).method2();
    // Invokes the accumulated remote requests using a single server round-trip.
    batchCaller.sendBatch();
 }
}

The remote methods will get executed only after sendBatch() was called. The method sendBatch accepts an additional RemoteCallback instance as a parameter which will we invoked when all remote calls have completed in success. Consequently, an ErrorCallback can also be provided which will get executed for all remote calls that have completed in failure.

If computing the result of an RPC call takes a significant amount of time (i.e. because a third party service has to be contacted) it might be preferrable to start a separate thread in the RPC service method or leverage an existing thread from a dedicated thread pool that will provide the result at a later point. In this case the RPC endpoint method can return immediately and the thread handling the incoming request doesn’t need to stay active until the result is available.

Errai provides a special return type CallableFuture to indicate to the RPC system that the result of the remote method call will be provided asynchronously (after the remote method call has returned).

Here’s an example returning a result of type String:

@Remote

public interface LongRunningService {
  public CallableFuture<String> someLongRunningTask();
}
@Service

public class LongRunningServiceImpl implements LongRunningService {
  @Override
  public CallableFuture<String> someLongRunningTask() {
    final CallableFuture<String> future = CallableFutureFactory.get().createFuture(String.class);
    final ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.submit(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(5000);
          future.setValue("foobar");
        }
        catch (Throwable t) {
          t.printStackTrace();
        }
      }
    });
    executorService.shutdown();
    return future;
  }
}

Note that the client-side code does not change when using this feature and will work exactly as described in Section 6.2, “Making calls” i.e.:

MessageBuilder.createCall(new RemoteCallback<String>() {

  @Override
  public void callback(String response) {
    assertEquals("foobar", response);
    finishTest();
  }
}, LongRunningService.class).someLongRunningTask();