ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Introducing JBoss Remoting

by John Mazzitelli
02/23/2005

Recently, JBoss introduced a new open source remoting framework called JBoss Remoting. It serves as the core framework for the next generation (v5.0) JBoss Application Server (AS) to provide remote services--things like remoting JMX MBeans, remoting EJBs, and so on. JBoss Remoting can also be used as a standalone framework to build network-aware services independent of the JBoss AS.

Considering the plethora of remoting frameworks that already exist (RMI, EJB, web services, et al.), the first question you might ask is, "Why introduce another remoting framework?" The competitive advantage of JBoss Remoting is that it is very lightweight, agnostic to network transport, and easy to extend. It supports a versatile invocation model that is far beyond today's RMI or web services. There is no need to generate and compile a client-side agent for each service. As a result, JBoss Remoting provides the basis for more complex and heavyweight remoting frameworks. For instance, the next generation JBoss web services stack, JBossWS, is based on JBoss Remoting with a custom SOAP data marshaller. Another example is the JBoss AOP Remoting project, which provides clustering features such as request load balancing and failover for remote calls using annotations. JBoss AOP Remoting also supports asynchronous invocation and propagation of both the transaction and security context over the network.

From the user's point of view, JBoss Remoting enables you to very easily design, implement, and deploy services that can be remotely invoked by clients using several different transport mechanisms transparently. It also provides you an efficient way to access existing services in JBoss AS. In this article, we will show you how to get started with JBoss Remoting.

Server-Side API

One of the first things to consider when building your JBoss Remoting service is what your endpoint reference is. In other words, how do clients identify your service in order to connect to it? JBoss Remoting defines the org.jboss.remoting.InvokerLocator class for this. For all intents and purposes, it is a URL (with some restrictions). Simple yet efficient, the InvokerLocator will not only identify the endpoint name of your specific service, but also define what transport protocol to use when sending the data (as any URL does). The format of the InvokerLocator URL is technically:

transport://host:port/parameter1=value1&parameterN=valueN

This isn't a true URL (aside from providing parameter values, there is no identification data beyond the protocol, hostname, and port number), but that is an easy way to think of it. A server will create its InvokerLocator by passing in the locator URL as a String:

InvokerLocator myLocator = 
    new InvokerLocator("socket://mybox:8084");

Note that by doing this, I am indicating that my service will be hosted on the computer mybox and will be network-aware via a simple TCP/IP socket transport layer listening on port 8084.

The InvokerLocator merely indicates where and how clients can connect to the server. The server must create a Connector and start it in order to actually be live on the network. It does this by creating an org.jboss.remoting.transport.Connector, associating that Connector with our InvokerLocator URL, and then starting the Connector:

Connector connector = new Connector();
connector.setInvokerLocator(myLocator.getLocatorURI());
connector.start();

Once started, the Connector becomes live and ready to accept connections from clients. The Connector object is actually a valid JMX MBean, so it can be deployed in JBossAS using the configuration shown below. Note that while some of this XML performs the same function as the Java code above, it also provides additional functionality by defining the configuration of the invocation handler--see the next section for more details on this.

<mbean code="org.jboss.remoting.transport.Connector"
  xmbean-dd="org/jboss/remoting/transport/Connector.xml"
  name="jboss.remoting:service=Connector,transport=Socket">
  <attribute name="InvokerLocator">
    socket://mybox:8084
  </attribute>
  <attribute name="Configuration">
    <handlers>
      <handler subsystem="SUBSYS">
        com.abc.MyHandler
      </handler> 
    </handlers>
  </attribute>
</mbean>

The question now becomes: what to do with the request when one comes in? That's where invocation handlers come in. When a Connector accepts an incoming request, it hands off that request to one of its invocation handlers. You plug your service-specific code into the JBoss Remoting framework by implementing org.jboss.remoting.ServerInvocationHandler and installing that handler into your Connector. The important method that you need to implement is ServerInvocationHandler.invoke(InvocationRequest). Within the invoke method, you perform the work your service actually needs to perform. The InvocationRequest encapsulates the data sent from the client--the invocation handler obtains the client data via InvocationRequest.getParameter():

/** handler to simply echo incoming
    parameter's toString value */
public class MyHandler
    implements ServerInvocationHandler {
  public Object invoke(InvocationRequest invocation) {
    return "Echo:" + invocation.getParameter();
  }
}

JBoss Remoting makes no assumptions about the thread safety of your handler code; it sends requests to your handler as soon as the requests come in. Therefore, to support concurrent request processing, you must make sure you write your handler with thread safety in mind--synchronizing code as necessary, for example.

Installing your invocation handler is simple. Immediately after starting the connector, you add your handlers via Connector.addInvocationHandler(String, ServerInvocationHandler):

Connector.addInvocationHandler("SUBSYS",
                    new com.abc.MyHandler());

The Connector MBean's handlers can also be configured using the normal JBoss configuration file (see the example above). Note that you may install multiple handlers, which is useful in order to de-multiplex client requests to different subsystems. This article will not discuss subsystems in depth; however, the concept is simple to understand and use--check out the documentation found on the JBoss Remoting product web site for more. Suffice it to say each handler is associated with a subsystem (identified by a simple string). That is, a client can submit requests to a particular subsystem. If you like, you can ignore the subsystem concept entirely: a null subsystem is valid, and some APIs are overloaded such that you do not have to specify a subsystem.

That's basically all that needs to be done on the server side. At this point, you now have a remote service that is live and accepting requests from the network. Your custom code implemented as a ServerInvocationHandler will be executed once a Connector receives a request from a client. The JBoss AOP Remoting framework simplifies this process and supports exposing POJO methods as remoting endpoints via annotations.

Client-Side API

We've just discussed how to get a network-aware service started. Now let's create a client that can submit requests to our service. The first thing is to create our InvokerLocator. Remember, this is the object that identifies the location of our service, as well as describing what transport protocol to use. We do the same thing our server did:

InvokerLocator myLocator = 
    new InvokerLocator("socket://mybox:8084");

Now that we have our service's locator identified, we can now create a org.jboss.remoting.Client object:

Client myClient = new Client(myLocator, "SUBSYS");

This is all you need in order to create a client that can be used to locate and connect to a remote service. As a side note, you can now see how a client can direct its request to a particular subsystem. Again, using subsystems isn't required; you can pass in null for the subsystem name or just not pass that parameter in.

With our client, we can now remotely invoke our service and obtain its results via the Client.invoke

method:

System.out.println(
    myClient.invoke("please echo this message back"));

This sends our request object (in this case, a String) to our remote service and prints out its results.

Callback Basics

So far, we have discussed the basics of how a client can refer to and send a request to remote servers. We also went over how to code up remote servers to accept and process client requests. But note two things:

  1. The client was forced to block and wait for the server to finish processing the request before it could receive the returned response object. Request processing was handled synchronously.
  2. The client had no way to listen for asynchronous events that occurred on the server side.

Callbacks allow a client to perform its own tasks while the remote server, in parallel, processes the client's request. Rather than block and wait for the remote server to return a response, a client may simply ask the server to notify or "call back" the client when the server is done. The client may then continue on with additional work that it has to accomplish. When the server finishes processing the request, the server will call back to the client. The callback will contain the response that was the result of the request processing.

Note that this mechanism is not limited to just handling asynchronous client requests. You may have your server utilize callbacks to implement notifications of asynchronous events. Any client interested in knowing about these events may add itself as a listener to those server events. For example, a server might be monitoring the free memory of the JVM in which it is running; if the memory drops to a certain threshold level, it might emit an asynchronous event to all of its listeners to indicate that "memory is running low." Clients may listen for these events in order to help take corrective action or send an email to an administrator, as examples.

JBoss Remoting allows the client to receive callbacks synchronously (via pull callbacks) or asynchronously (via push callbacks). Pull callbacks are synchronous because they require the client to periodically stop what it's doing and poll the server in order to collect the data that are queued up. Push callbacks are asynchronous because the client will be notified immediately when a request has completed processing and the response is available (or in the case of event processing, when an event was triggered). This is analogous to waiting for a piece of snail mail--you can either walk out to your mail box periodically to see if the letter has arrived, or you can just listen for the mailman to ring your door bell when the letter is dropped off.

Server Callback Processing

Remote servers must be prepared to handle callbacks. Whether push or pull, your server implementation code will be the same. The JBoss Remoting framework will deal with handling the differences between push and pull callbacks. When a server has completed a request or wants to send an event notification, it is responsible for informing all of the callbacks

:
InvocationRequest callbackInvocationRequest = new 
    InvocationRequest(null, mySubsystem,
                      responseObject, // this is your data
                      null, null, null);

Iterator itr = m_listeners.iterator();

while(itr.hasNext()) {
   InvokerCallbackHandler callbackHandler = 
      (InvokerCallbackHandler) itr.next();
   callbackHandler.handleCallback(callbackInvocationRequest);
}

This requires some explanation. Handling a callback will look as if the server is sending an invocation request back to the client (in the case of a push callback, that's exactly what is happening). Thus the need to pass an org.jboss.remoting.InvocationRequest object to the callback handler's handleCallback method.

The responseObject given to the InvocationRequest constructor is any Object that you want to pass to the client callback. This can be the response to the request or, in the case of an event notification, can be the encapsulation of the event data. As a side note: the API to create the callback invocation request will be cleaned up in a future version of JBoss Remoting; there should be no need to pass in so many unused arguments.

The m_listeners object is something your server must create and manage. It contains a list of all listeners that the client has added to your server handler. JBoss Remoting's ServerInvocationHandler interface provides hooks that you use to add and remove listeners to this list (see that interface's addListener and removeListener methods).

Pull Callbacks

To use callbacks (push or pull), the client must first implement a callback handler. A callback handler is simply an implementation of the interface org.jboss.remoting.InvokerCallbackHandler whose job is to process the callback data (for example, the request's response or the event notification).

public class MyCallbackHandler 
                   implements InvokerCallbackHandler {
   public void handleCallback(InvocationRequest invocation) {
      Object response = invocation.getParameter();
      System.out.println("The server returned: " + response);
   }
}

Once the callback handler has been implemented, you must inform the remote server that you want to use pull callbacks by adding this handler as a listener:

    Client client = new Client(myLocator, mySubsystem);
    InvokerCallbackHandler callbackHandler = 
                           new MyCallbackHandler();
    client.addListener(callbackHandler);

Passing the handler as the only argument to Client.addListener() will install the handler on the server as a pull callback handler.

If you plan on using pull callbacks, your client must perform the extra step of polling the server periodically. This, in effect, asks the server to synchronously deliver callback data to the client. Each time the client wants to poll the server, the following call must be made:

    List callbacks = client.getCallbacks();

This will obtain all the callbacks that the server has waiting for the client. At this point, the client processes them as it sees fit.

Push Callbacks

Push callbacks require the client itself to be a JBoss Remoting server (called the callback server). This callback server is the same as any JBoss Remoting server. It simply is accessible to the client. The callback server will accept incoming push callbacks asynchronously and process them for the client. We already discussed how to prepare a JBoss Remoting server, so we won't go over it again here. To let the remote server know about our client's callback server, the client merely sends the callback server's InvokerLocator as a second argument to the addListener method:

InvokerLocator callbackLocator = 
          new InvokerLocator(callbackURL);
    client.addListener(callbackHandler, callbackLocator);

Listening for callbacks is the same as any server processing. The callback server's invoke method will accept incoming invocation requests that will contain the callback data. The callback server merely has to process them as usual.

public class MyCallbackServer 
    implements ServerInvocationHandler {
   public void invoke( InvocationRequest invocation ) {
      Object response = invocation.getParameter();
      System.out.println("The callback data: " + response);
   }
}

The callback server's invoke method will have to determine what to do with the incoming data. If this was the asynchronous response to a client request, the client now has confirmation that its request is complete and it has the return value. In the case of an event notification, the client should react to the event as it sees fit.

It should be noted that, due to firewall or NAT issues, it might not be possible for the remote server to actually communicate with this callback server. In this case, pull callbacks would be a solution, since they would not require the remote server to be able to send data to the remote client.

Related Reading

Better, Faster, Lighter Java
By Justin Gehtland

Some interesting situations may arise when using push callbacks. For example, the above-mentioned firewall issues. Other examples: what happens if a client sends a second request before the first request's callback response arrives? How do you handle a flood of callback events? Some conditions may be handled easily within your server or client implementations; others may require that the JBoss Remoting framework help out. For discussions on these more advanced topics, you are encouraged to participate on the JBoss user forums.

Conclusions

Hopefully, what you take away from all of this is the ease of use that JBoss Remoting provides when developing remote services and the clients that use them. The more complex programming model of RMI has been stripped away, leaving only the bare essentials required to define a service and its client. It is important to note that, while the complexity of the RMI programming model is gone, JBoss Remoting is able to use RMI as a transport protocol. You simply change your invoker location to use the protocol rmi: as opposed to socket:. In the future, additional transport protocols will be made available, and will be just as easily swappable. You can also write your own transport protocols.

Note that JBoss Remoting also provides other features not touched upon in this article, specifically the ability for remote servers to register on a replicated Network Registry and for clients to perform auto-discovery of those remote servers found on the network. The documentation that ships with the current release of JBoss Remoting covers these in some detail and include sample code for you to study.

Resources

Note: The first annual JBoss users' conference will be held at the Omni/CNN Center, March 1-2, 2005, in downtown Atlanta, Georgia. www.jboss.com/company/events/jbossworld2005

John Mazzitelli is a developer for JBoss, a division of Red Hat, currently focusing on the implementation of the JBoss Operations Network management platform.


Return to ONJava.com.