JBoss.orgCommunity Documentation
This section covers the core messaging concepts of the ErraiBus messaging framework.
It's important to understand the concept of how messaging works in ErraiBus. Service endpoints are given string-based names that are referenced by message senders. There is no difference between sending a message to a client-based service, or sending a message to a server-based service. In fact, a service of the same name may co-exist on both the client and the server and both will receive all messages bound for that service name, whether they are sent from the client or from the server.
Services are lightweight in ErraiBus, and can be declared liberally and extensively within your application to provide a message-based infrastructure for your web application. It can be tempting to think of ErraiBus simply as a client-server communication platform, but there is a plethora of possibilities for using ErraiBus purely with the GWT client context, such as a way to advertise and expose components dynamically, to get around the lack of reflection in GWT.
In fact, ErraiBus was originally designed to run completely within the client but quickly evolved into having the capabilities it now has today. So keep that in mind when you run up against problems in the client space that could benefit from runtime federation.
The MessageBuilder is the heart of the messaging API in ErraiBus. It provides a fluent / builder API, that is used for constructing messages. All three major message patterns can be constructed from the
MessageBuilder
.
Components that want to receive messages need to implement the
MessageCallback
interface.
But before we dive into the details, let look at some use cases first.
Sending Messages with the Client BusIn order to send a message from a client you need to create a
Message
and send it through an instance of
MessageBus
. In this simple example we send it to the subject 'HelloWorldService'.
public class HelloWorld implements EntryPoint { // Get an instance of the RequestDispatcher private RequestDispatcher dispatcher = ErraiBus.getDispatcher(); public void onModuleLoad() { Button button = new Button("Send message"); button.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { // Send a message to the 'HelloWorldService'. MessageBuilder.createMessage() .toSubject("HelloWorldService") // (1) .signalling() // (2) .noErrorHandling() // (3) .sendNowWith(dispatcher); // (4) }); [...] } } }
In the above example we build and send a message every time the button is clicked. Here's an explanation of what's going on as annotated above:
We specify the subject we wish to send a message to. In this case, "
HelloWorldService
".
We indicate that we wish to only signal the service, meaning, that we're not sending a qualifying command to the service. For information on this, read the section on Protocols .
We indicate that we do not want to provide an
ErrorCallback
to deal with errors for this message.
We transmit the message by providing an instance to the
RequestDispatcher
An astute observer will note that access to the
RequestDispatcher
differs within client code and server code. Because the client code does not run within a container, access to the
RequestDispatcher
and
MessageBus
is statically accessed using the
ErraiBus.get()
and
ErraiBus.getDispatcher()
methods. The server-side code, conversely, runs inside a dependency container for managing components. It currently uses Guice as the default container, but will more generically support the JSR-330 specification in the future.
Recieving Messages on the Server Bus / Server ServicesEvery message has a sender and at least one receiver. A receiver is as it sounds--it receives the message and does something with it. Implementing a receiver (also referred to as a service) is as simple as implementing our standard MessageCallback interface, which is used pervasively across, both client and server code. Let's begin with server side component that receives messages:
@Service public class HelloWorldService implements MessageCallback { public void callback(Message message) { System.out.println("Hello, World!"); } }
He we declare an extremely simple service. The
@Service
annotation provides a convenient, meta-data based way of having the bus auto-discover and deploy the service.
Sending Messages with the Server BusIn the following example we extend our server side component to reply with a message when the callback method is invoked. It will create a message and address it to the subject '
HelloWorldClient
':
@Service public class HelloWorldService implements MessageCallback { private RequestDispatcher dispatcher; @Inject public HelloWorldService(RequestDispatcher disaptcher) { dispatcher = dispatcher; } public void callback(CommandMessage message) { // Send a message to the 'HelloWorldClient'. MessageBuilder.createMessage() .toSubject("HelloWorldClient") // (1) .signalling() // (2) .with("text", "Hi There") // (3) .noErrorHandling() // (4) .sendNowWith(dispatcher); // (5) }); } }
The above example shows a service which sends a message in response to receiving a message. Here's what's going on:
We specify the subject we wish to send a message to. In this case, "
HelloWorldClient
". We are sending this message to all clients which are listening in on this subject. For information on how to communicate with a single client, see Section 2.6.
We indicate that we wish to only signal the service, meaning that we're not sending a qualifying command to the service. For information on this, read the section on Protocols.
We add a message part called "text" which contains the value "Hi there".
We indicate that we do not want to provide an
ErrorCallback
to deal with errors for this message.
We transmit the message by providing an instance of the
RequestDispatcher
.
Receiving Messages on the Client Bus/ Client ServicesMessages can be received asynchronously and arbitriraily by declaring callback services within the client bus. As ErraiBus maintains an open COMET channel at all times, these messages are delivered in real time to the client as they are sent. This provides built-in push messaging for all client services.
public class HelloWorld implements EntryPoint { private MessageBus bus = ErraiBus.get(); public void onModuleLoad() { [...] /** * Declare a local service to receive messages on the subject * "BroadcastReceiver". */ bus.subscribe("BroadcastReceiver", new MessageCallback() { public void callback(CommandMessage message) { /** * When a message arrives, extract the "text" field and * do something with it */ String messageText = message.get(String.class, "text"); } }); [...] } }
In the above example, we declare a new client service called
"BroadcastReceiver"
which can now accept both local messages and remote messages from the server bus. The service will be available in the client to receive messages as long the client bus is and the service is not explicitly de-registered.
ConversationsConversations are message exchanges which are between a single client and a service. They are a fundmentally important concept in ErraiBus, since by default, a message will be broadcast to all client services listening on a particular channel.
When you create a reply with an incoming message, you ensure that the message you are sending back is received by the same client which sent the incoming message. A simple example:
@Service public class HelloWorldService implements MessageCallback { public void callback(CommandMessage message) { // Send a message to the 'HelloWorldClient' on the client that sent us the // the message. MessageBuilder.createConversation(message) .toSubject("HelloWorldClient") .signalling() .with("text", "Hi There! We're having a reply!") .noErrorHandling().reply(); }); } }
Note that the only difference between the example in the previous section (2.4) and this is the use of the
createConversation()}}method with {{MessageBuilder
.
It is possible to contruct a message and a default response handler as part of the
MessageBuilder
API. It should be noted, that multiple replies will not be possible and will result an exception if attempted. Using this aspect of the API is very useful for doing simple psuedo-synchronous conversive things.
You can do this by specifying a
MessageCallback
using the
repliesTo()
method in the
MessageBuilder
API after specifying the error handling of the message.
MessageBuilder.createMessage() .toSubject("ConversationalService").signalling() .with("SomeField", someValue) .noErrorHandling() .repliesTo(new MessageCallback() { public void callback(Message message) { System.out.println("I received a response"); } })
See the next section on how to build conversational services that can respond to such messages.
It is possible for the sender to infer, to whatever conversational service it is calling, what subject it would like the reply to go to. This is accomplished by utilizing the standard
MessageParts.ReplyTo
message part. Using this methodology for building conversations is generally encouraged.
Consider the following client side code:
MessageBuilder.createMessage() .toSubject("ObjectService").signalling() .with(MessageParts.ReplyTo, "ClientEndpoint") .noErrorHandling().sendNowWith(dispatcher);
And the conversational code on the server (for service ObjectService ):
MessageBuilder.createConversation(message) .subjectProvided().signalling() .with("Records", records) .noErrorHandling().reply();
In the above examples, assuming that the latter example is inside a service called "
ObjectService
" and is referencing the incoming message that was sent in the former example, the message created will automatically reference the
ReplyTo
subject that was provided by the sender, and send the message back to the subject desired by the client on the client that sent the message.
Broadcasting messages to all clients listening on a specific subject is quite simple and involves nothing more than forgoing use of the reply API. For instance:
MessageBuilder.createMessage(). .toSubject("MessageListener") .with("Text", "Hello, from your overlords in the cloud") .noErrorHandling().sendGlobalWith(dispatcher);
If sent from the server, all clients currently connected, who are listening to the subject
"MessageListener"
will receive the message. It's as simple as that.
Communication from one client to another client is not directly possible within the bus federation, by design. This isn't to say that it's not possible. But one client cannot see a service within the federation of another client. We institute this limitation as a matter of basic security. But many software engineers will likely find the prospects of such communication appealing, so this section will provide some basic pointers on how to go about accomplishing it.
Relay ServicesThe essential architectural thing you'll need to do is create a relay service that runs on the server. Since a service advertised on the server is visible to all clients and all clients are visible to the server, you might already see where we're going with this.
By creating a service on the server which accepts messages from clients, you can create a simple protocol on-top of the bus to enable quasi peer-to-peer communication. (We say quasi, because it still needs to be routed through the server)
While you can probably imagine simply creating a broadcast-like service which accepts a message from one client and broadcasts it to the rest of the world, it may be less clear how to go about routing from one particular client to another particular client, so we'll focus on that problem. This is covered in Section 19.14.13, “Message Routing Information”
Every message that is sent between a local and remote (or server and client) buses contain session routing information. This information is used by the bus to determine what outbound queues to use to deliver the message to, so they will reach their intended recipients. It is possible to manually specify this information to indicate to the bus, where you want a specific message to go.
The utility class
org.jboss.errai.bus.server.util.ServerBusUtils
contains a utility method for extracting the String-based SessionID which is used to identify the message queue associated with any particular client. You may use this method to extract the
SessionID
from a message so that you may use it for routing. For example:
... public void callback(Message message) { String sessionId = ServerBusUtils.getSessionId(message); // Record this sessionId somewhere. ... }
The
SessionID
can then be stored in a medium, say a Map, to cross-reference specific users or whatever identifier you wish to allow one client to obtain a reference to the specific
SessionID
of another client. In which case, you can then provide the
SessionID
as a MessagePart to indicate to the bus where you want the message to go.
MessageBuilder.createMessage() .toSubject("ClientMessageListener") .signalling() .with(MessageParts.SessionID, sessionId) .with("Message", "We're relaying a message!") .noErrorHandling().sendNowWith(dispatcher);
By providing the
SessionID
part in the message, the bus will see this and use it for routing the message to the relevant queue.
Now you're routing from client-to-client!
It may be tempting however, to try and include destination
SessionIDs
at the client level, assuming that this will make the infrastructure simpler. But this will not achieve the desired results, as the bus treats
SessionIDs
as transient. Meaning, the
SessionID
information is not ever transmitted from bus-to-bus, and therefore is only directly relevant to the proximate bus.
Asynchronous messaging necessitates the need for asynchronous error handling. Luckily, support for handling errors is built directly into the
MessageBuilder
API, utilizing the
ErrorCallback
interface. In the examples shown in previous exceptions, error-handing has been glossed over with aubiquitous usage of the
noErrorHandling()
method while building messaging. We chose to require the explicit use of such a method to remind developers of the fact that they are responsible for their own error handling, requiring you to explicitly make the decision to forego handling potential errors.
As a general rule, you should always handle your errors . It will lead to faster and quicker identification of problems with your applications if you have error handlers, and generally help you build more robust code.
MessageBuilder.createMessage() .toSubject("HelloWorldService") .signalling() .with("msg", "Hi there!") .errorsHandledBy(new ErrorCallback() { public boolean error(Message message, Throwable throwable) { throwable.printStackTrace(); return true; } }) .sendNowWith(dispatcher);
The addition of error-handling at first may put off developers as it makes code more verbose and less-readable. This is nothing that some good practice can't fix. In fact, you may find cases where the same error-handler can appropriately be shared between multiple different calls.
ErrorCallback error = new ErrorCallback() { public boolean error(Message message, Throwable throwable) { throwable.printStackTrace(); return true; } } MessageBuilder.createMessage() .toSubject("HelloWorldService") .signalling() .with("msg", "Hi there!") .errorsHandledBy(error) .sendNowWith(dispatcher);
A little nicer.
The error handler requires that return a
boolean
value. This is to indicate whether or not Errai should perform the defautl error handling actions it would normally take during a failure. You will almost always want to return
true
here, unless you are trying to expicitly supress some undesirably activity by Errai, such as automatic subject-termination in conversations. But this is almost never the case.
In some applications, it may be necessary or desirable to delay transmission of, or continually stream data to a remote client or group of clients (or from a client to the server). In cases like this, you can utilize the
replyRepeating()
,
replyDelayed()
,
sendRepeating()
and
sendDelayed()
methods in the
MessageBuilder
.
Delayed TasksSending a task with a delay is straight forward. Simply utilize the appropriate method (either
replyDelayed()
or
sendDelayed()
).
MessageBuilder.createConversation(msg) .toSubject("FunSubject") .signalling() .noErrorHandling() .replyDelayed(TimeUnit.SECONDS, 5); // sends the message after 5 seconds.
or
MessageBuilder.createMessage() .toSubject("FunSubject") .signalling() .noErrorHandling() .sendDelayed(requestDispatcher, TimeUnit.SECONDS, 5); // sends the message after 5 seconds.
A repeating task is sent using one of the MessageBuilder's
repeatXXX()
methods. The task will repeat indefinitely until cancelled (see next section).
MessageBuilder.createMessage() .toSubject("FunSubject") .signalling() .withProvided("time", new ResourceProvider<String>() { SimpleDateFormat fmt = new SimpleDateFormat("hh:mm:ss"); public String get() { return fmt.format(new Date(System.currentTimeMillis()); } } .noErrorHandling() .sendRepeatingWith(requestDispatcher, TimeUnit.SECONDS, 1); //sends a message every 1 second
The above example sends a message very 1 second with a message part called
"time"
, containing a formatted time string. Note the use of the
withProvided()
method; a provided message part is calculated at the time of transmission as opposed to when the message is constructed.
Cancelling an Asynchronous TaskA delayed or repeating task can be cancelled by calling the
cancel()
method of the
AsyncTask
instance which is returned when creating a task. Reference to the AsyncTask object can be retained and cancelled by any other thread.
AsyncTask task = MessageBuilder.createConversation(message) .toSubject("TimeChannel").signalling() .withProvided(TimeServerParts.TimeString, new ResourceProvider<String>() { public String get() { return String.valueOf(System.currentTimeMillis()); } }).defaultErrorHandling().replyRepeating(TimeUnit.MILLISECONDS, 100); ... // cancel the task and interrupt it's thread if necessary. task.cancel(true);
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 to 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.
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.
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 correpond 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
@ExposeEntity
annotation needs to be present on the corresponding exception class (see
Section 19.20, “Serialization”
). Further the exception class needs to be part of the client-side code. For more details on
ErrorCallbacks
see
Section 19.15, “Handling Errors”
.
The ErraiBus maintains it's own seperate session management on-top of the regular HTTP session management. While the queue sessions are tied to, and dependant on HTTP sessions for the most part (meaning they die when HTTP sessions die), they provide extra layers of session tracking to make dealing with complex applications built on Errai easier.
One of the things Errai offers is the concept of session and local scopes.
A local scope is scoped to a single browser instance. But not to a single session.
In a browser a local scope would be confined to a tab or a window within a browser. You can store parameters inside a local scope just like with a session by using the
LocalContext
helper class.
public class TestService implements MessageCallback { public void callback(final Message message) { // obtain a reference to the local context by referencing the incoming message. LocalContext injectionContext = LocalContext.get(message); // set an attribute. injectionContext.setAttribute("MyAttribute", "Foo"); } }
A session scope is scoped across all instances of the same session. When a session scope is used, any parameters stored will be accessible and visible by all browser instances and tabs.
The SessionContext helper class is used for accessing the session scope.
public class TestService implements MessageCallback { public void callback(final Message message) { // obtain a reference to the session context by referencing the incoming message. SessionContext injectionContext = SessionContext.get(message); // set an attribute. injectionContext.setAttribute("MyAttribute", "Foo"); } }
The lifescyle of a session is bound by the underlying HTTP session. It is also bound by activity thresholds. Clients are required to send heartbeat messages every once in a while to maintain their sessions with the server. If a heartbeat message is not received after a certain period of time, the session is terminated and any resources are deallocated.
Serialization on the ErraiBus supports serialization within the same scope and limitations as the default GWT RPC serialization rules. In order to expose your domain objects to the bus so they can be exported across the bus, you must annotate them with the
org.jboss.errai.bus.server.annotations.ExposeEntity
annotation. The presence of this annotation will cause Errai's GWT compiler extensions to generate marshall/demarshall stubs for the annotated objects at compile-time.
For example:
@ExposeEntity public class User implements java.io.Serializable { private int userId; public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } [...] }
All exposed entities must follow Java Bean convensions, and must be in the classpath both at compile-time and at runtime. Compile-time access to the entities is required since the creation of the marshalling/demarshalling proxies involves code generation.
It may not be possible to annotate certain types you wish to expose to the bus for serialization if the entities are located in a third-party library that you do not maintain. As such, you can explicitly indicate in the configuration that you would like to have this entities made available by declaring them in the
ErraiApp.properties
of any module.
errai.bus.serializableTypes=org.foo.Foo \ org.bar.Bar \ org.foobie.Foobie
By default, ErraiBus uses Google Guice to wire components. However, we plan on standardizing on
JSR-330 Dependency Injection
specification in the near future. When deploying services on the server-side, it is currently possible to obtain references to the
MessageBus
,
RequestDispatcher
, the
ErraiServiceConfigurator
, and
ErraiService
by declaring them as injection dependencies in Service classes, extension components, and session providers.
This section contains information on configuring the server-side bus.
Depending on what application server you are deploying on, you must provide an appropriate servlet implementation if you wish to use true, asynchronous I/O. See _section 6.5 _ for information on the available servlet implementations.
Here's a sample web.xml file:
<web-app 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/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>ErraiServlet</servlet-name> <servlet-class>org.jboss.errai.bus.server.servlet.DefaultBlockingServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ErraiServlet</servlet-name> <url-pattern>*.erraiBus</url-pattern> </servlet-mapping> <context-param> <param-name>errai.properties</param-name> <param-value>/WEB-INF/errai.properties</param-value> </context-param> <context-param> <param-name>login.config</param-name> <param-value>/WEB-INF/login.config</param-value> </context-param> <context-param> <param-name>users.properties</param-name> <param-value>/WEB-INF/users.properties</param-value> </context-param> </web-app>
he ErraiService.properties file contains basic configuration for the bus itself.
Example Configuration:
## ## Request dispatcher implementation (default is SimpleDispatcher) ## #errai.dispatcher_implementation=org.jboss.errai.bus.server.SimpleDispatcher errai.dispatcher_implementation=org.jboss.errai.bus.server.AsyncDispatcher # ## Worker pool size. This is the number of threads the asynchronous worker pool should provide for processing ## incoming messages. This option is only valid when using the AsyncDispatcher implementation. ## errai.async.thread_pool_size=5 ## ## Worker timeout (in seconds). This defines the time that a single asychronous process may run, before the worker pool ## terminates it and reclaims the thread. This option is only valid when using the AsyncDispatcher implementation. ## errai.async.worker.timeout=5 ## ## Specify the Authentication/Authorization Adapter to use ## #errai.authentication_adapter=org.jboss.errai.persistence.server.security.HibernateAuthenticationAdapter #errai.authentication_adapter=org.jboss.errai.bus.server.security.auth.JAASAdapter ## ## This property indicates whether or not authentication is required for all communication with the bus. Set this ## to 'true' if all access to your application should be secure. ## #errai.require_authentication_for_all=true
The
errai.dispatcher_implementation
defines, as it's name quite succinctly implies, the dispatcher implementation to be used by the bus. There are two implementations which come with Errai out of the box: the
SimpleDispatcher
and the
AsyncDispatcher
. See section on Dispatchers for more information about the differences between the two.
Specifies the total number of worker threads in the worker pool for handling and delivering messages. Adjusting this value does not have any effect if you are using the SimpleDispatcher.
Specifies the total amount of a time (in seconds) a service has to finish processing an incoming message before the pool interrupts the thread and returns an error. Adjusting this value does not have an effect if you are using the SimpleDispatcher.
Specifies the authentication modelAdapter the bus should use for determining whether calls should be serviced based on authentication and security principles.
Indicates whether or not the bus should always require the use of authentication for all requests inbound for the bus. If this is turned on, an authentication model adapter must be defined, and any user must be authenticated before the bus will deliver any messages from the client to any service.
A boolean indicating whether or not the Errai bootstrapper should automatically scan for services.
The ErraiApp.properties acts as a marker file. When it is detected inside a JAR or at the top of any classpath, the subdirectories are scanned for deployable components. As such, all Errai application modules in a project should contain an ErraiApp.properties at the root of all classpaths that you wish to be scanned.
The file can also include explicitly declared serializable types (such as those from third-party code) that cannot be annotated for serialization. (See the section on serialization for more details)
errai.bus.serializableTypesDefines a list of serializable types to expose to the bus.
errai.bus.serializableTypes=org.foo.Foo \ org.bar.Bar \ org.foobie.Foobie
Dispatchers encapsulate the strategy for taking messages that need to be delivered somewhere and seeing that they are delivered to where they need to go. There are two primary implementations that are provided with Errai, depending on your needs.
SimpleDispatcher is basic implementation that provides no asychronous delivery mechanism. Rather, when you configure the Errai to use this implementation, messages are delivered to their endpoints synchronously. The incoming HTTP thread will be held open until the messages are delivered.
While this sounds like it has almost no advantages, especially in terms of scalablity. Using the SimpleDispatcher can be far preferable when you're developing your application, as any errors and stack traces will be far more easily traced and some cloud services may not permit the use of threads in any case.
The AsyncDispatcher provides full asynchronous delivery of messages. When this dispatcher is used, HTTP threads will have control immediately returned upon dispatch of the message. This dispatcher provides far more efficient use of resources in high-load applications, and will significantly decrease memory and thread usage overall.
Errai has several different implementations for HTTP traffic to and from the bus. We provide a universally-compatible blocking implementation that provides fully synchronous communication to/from the server-side bus. Where this introduces scalability problems, we have implemented many webserver-specific implementations that take advantage of the various proprietary APIs to provide true asynchrony.
These inlcuded implementations are packaged at:
org.jboss.errai.bus.server.servlet
This is a universal, completely servlet spec (2.0) compliant, Servlet implementation. It provides purely synchronous request handling and should work in virtually any servlet container, unless there are restrictions on putting threads into sleep states.
The Tomcat AIO implementation of our servlet allows Errai to take advantage of Tomcat's event-based AIO APIs to improve scalability and reduce thread usage. The use of this implementation is dependant on the Tomcat container being configured to support AIO using either it's NIO or APR connectors. This servlet will NOT work with the regular HTTP and AJP connectors.
The Jetty implementation leverages Jetty's continuations support, which allows for threadless pausing of port connections. This servlet implementation should work without any special configuration of Jetty.
The JBoss Comet support utilizes the JBoss Web AIO APIs (AS 5.0 and AS 6.0) to improve scalability and reduce thread usage. The HTTP, NIO, and AJP connectors are not supported. Use of this implementation requires use of the APR (Apache Portable Runtime).
Support for the comet API in the Grizzy HTTP server (used in Glassfish).
Errai includes a bus monitoring application, which allows you to monitor all of the message exchange activity on the bus in order to help track down any potential problems It allows you to inspect individual messages to examine their state and structure.
To utilize the bus monitor, you'll need to include the _errai-tools _ package as part of your application's dependencies. When you run your application in development mode, you will simply need to add the following JVM options to your run configuration in order to launch the monitor:
-Derrai.tools.bus_monitor_attach=true
The monitor provides you a real-time perspective on what's going on inside the bus. The left side of the main screen lists the services that are currently available, and the right side is the service-explorer, which will show details about the service.
To see what's going on with a specific service, simply double-click on the service or highlight the service, then click "Monitor Service...". This will bring up the service activity monitor.
The service activity monitor will display a list of all the messages that were transmitted on the bus since the monitor became active. You do not need to actually have each specific monitor window open in order to actively monitor the bus activity. All activity on the bus is recorded.
The monitor allows you select individual messages, an view their individual parts. Clicking on a message part will bring up the object inspector, which will allow you to explore the state of any objects contained within the message, not unlike the object inspectors provided by debuggers in your favorite IDE. This can be a powerful tool for looking under the covers of your application.
ErraiBus forms the backbone of the Errai framework's approach to application design. Most importantly, it provides a straight-forward approach to a complex problem space. Providing common APIs across the client and server, developers will have no trouble working with complex messaging scenarios from building instant messaging clients, stock tickers, to monitoring instruments. There's no more messing with RPC APIs, or unweildy AJAX or COMET frameworks. We've built it all in to one, consice messaging framework. It's single-paradigm, and it's fun to work with.
This section covers the core messaging concepts of the ErraiBus messaging framework.
It's important to understand the concept of how messaging works in ErraiBus. Service endpoints are given string-based names that are referenced by message senders. There is no difference between sending a message to a client-based service, or sending a message to a server-based service. In fact, a service of the same name may co-exist on both the client and the server and both will receive all messages bound for that service name, whether they are sent from the client or from the server.
Services are lightweight in ErraiBus, and can be declared liberally and extensively within your application to provide a message-based infrastructure for your web application. It can be tempting to think of ErraiBus simply as a client-server communication platform, but there is a plethora of possibilities for using ErraiBus purely with the GWT client context, such as a way to advertise and expose components dynamically, to get around the lack of reflection in GWT.
In fact, ErraiBus was originally designed to run completely within the client but quickly evolved into having the capabilities it now has today. So keep that in mind when you run up against problems in the client space that could benefit from runtime federation.
The MessageBuilder is the heart of the messaging API in ErraiBus. It provides a fluent / builder API, that is used for constructing messages. All three major message patterns can be constructed from the
MessageBuilder
.
Components that want to receive messages need to implement the
MessageCallback
interface.
But before we dive into the details, let look at some use cases first.
Sending Messages with the Client BusIn order to send a message from a client you need to create a
Message
and send it through an instance of
MessageBus
. In this simple example we send it to the subject 'HelloWorldService'.
public class HelloWorld implements EntryPoint { // Get an instance of the RequestDispatcher private RequestDispatcher dispatcher = ErraiBus.getDispatcher(); public void onModuleLoad() { Button button = new Button("Send message"); button.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { // Send a message to the 'HelloWorldService'. MessageBuilder.createMessage() .toSubject("HelloWorldService") // (1) .signalling() // (2) .noErrorHandling() // (3) .sendNowWith(dispatcher); // (4) }); [...] } } }
In the above example we build and send a message every time the button is clicked. Here's an explanation of what's going on as annotated above:
We specify the subject we wish to send a message to. In this case, "
HelloWorldService
".
We indicate that we wish to only signal the service, meaning, that we're not sending a qualifying command to the service. For information on this, read the section on Protocols .
We indicate that we do not want to provide an
ErrorCallback
to deal with errors for this message.
We transmit the message by providing an instance to the
RequestDispatcher
An astute observer will note that access to the
RequestDispatcher
differs within client code and server code. Because the client code does not run within a container, access to the
RequestDispatcher
and
MessageBus
is statically accessed using the
ErraiBus.get()
and
ErraiBus.getDispatcher()
methods. The server-side code, conversely, runs inside a dependency container for managing components. It currently uses Guice as the default container, but will more generically support the JSR-330 specification in the future.
Recieving Messages on the Server Bus / Server ServicesEvery message has a sender and at least one receiver. A receiver is as it sounds--it receives the message and does something with it. Implementing a receiver (also referred to as a service) is as simple as implementing our standard MessageCallback interface, which is used pervasively across, both client and server code. Let's begin with server side component that receives messages:
@Service public class HelloWorldService implements MessageCallback { public void callback(Message message) { System.out.println("Hello, World!"); } }
He we declare an extremely simple service. The
@Service
annotation provides a convenient, meta-data based way of having the bus auto-discover and deploy the service.
Sending Messages with the Server BusIn the following example we extend our server side component to reply with a message when the callback method is invoked. It will create a message and address it to the subject '
HelloWorldClient
':
@Service public class HelloWorldService implements MessageCallback { private RequestDispatcher dispatcher; @Inject public HelloWorldService(RequestDispatcher disaptcher) { dispatcher = dispatcher; } public void callback(CommandMessage message) { // Send a message to the 'HelloWorldClient'. MessageBuilder.createMessage() .toSubject("HelloWorldClient") // (1) .signalling() // (2) .with("text", "Hi There") // (3) .noErrorHandling() // (4) .sendNowWith(dispatcher); // (5) }); } }
The above example shows a service which sends a message in response to receiving a message. Here's what's going on:
We specify the subject we wish to send a message to. In this case, "
HelloWorldClient
". We are sending this message to all clients which are listening in on this subject. For information on how to communicate with a single client, see Section 2.6.
We indicate that we wish to only signal the service, meaning that we're not sending a qualifying command to the service. For information on this, read the section on Protocols.
We add a message part called "text" which contains the value "Hi there".
We indicate that we do not want to provide an
ErrorCallback
to deal with errors for this message.
We transmit the message by providing an instance of the
RequestDispatcher
.
Receiving Messages on the Client Bus/ Client ServicesMessages can be received asynchronously and arbitriraily by declaring callback services within the client bus. As ErraiBus maintains an open COMET channel at all times, these messages are delivered in real time to the client as they are sent. This provides built-in push messaging for all client services.
public class HelloWorld implements EntryPoint { private MessageBus bus = ErraiBus.get(); public void onModuleLoad() { [...] /** * Declare a local service to receive messages on the subject * "BroadcastReceiver". */ bus.subscribe("BroadcastReceiver", new MessageCallback() { public void callback(CommandMessage message) { /** * When a message arrives, extract the "text" field and * do something with it */ String messageText = message.get(String.class, "text"); } }); [...] } }
In the above example, we declare a new client service called
"BroadcastReceiver"
which can now accept both local messages and remote messages from the server bus. The service will be available in the client to receive messages as long the client bus is and the service is not explicitly de-registered.
ConversationsConversations are message exchanges which are between a single client and a service. They are a fundmentally important concept in ErraiBus, since by default, a message will be broadcast to all client services listening on a particular channel.
When you create a reply with an incoming message, you ensure that the message you are sending back is received by the same client which sent the incoming message. A simple example:
@Service public class HelloWorldService implements MessageCallback { public void callback(CommandMessage message) { // Send a message to the 'HelloWorldClient' on the client that sent us the // the message. MessageBuilder.createConversation(message) .toSubject("HelloWorldClient") .signalling() .with("text", "Hi There! We're having a reply!") .noErrorHandling().reply(); }); } }
Note that the only difference between the example in the previous section (2.4) and this is the use of the
createConversation()}}method with {{MessageBuilder
.
It is possible to contruct a message and a default response handler as part of the
MessageBuilder
API. It should be noted, that multiple replies will not be possible and will result an exception if attempted. Using this aspect of the API is very useful for doing simple psuedo-synchronous conversive things.
You can do this by specifying a
MessageCallback
using the
repliesTo()
method in the
MessageBuilder
API after specifying the error handling of the message.
MessageBuilder.createMessage() .toSubject("ConversationalService").signalling() .with("SomeField", someValue) .noErrorHandling() .repliesTo(new MessageCallback() { public void callback(Message message) { System.out.println("I received a response"); } })
See the next section on how to build conversational services that can respond to such messages.
It is possible for the sender to infer, to whatever conversational service it is calling, what subject it would like the reply to go to. This is accomplished by utilizing the standard
MessageParts.ReplyTo
message part. Using this methodology for building conversations is generally encouraged.
Consider the following client side code:
MessageBuilder.createMessage() .toSubject("ObjectService").signalling() .with(MessageParts.ReplyTo, "ClientEndpoint") .noErrorHandling().sendNowWith(dispatcher);
And the conversational code on the server (for service ObjectService ):
MessageBuilder.createConversation(message) .subjectProvided().signalling() .with("Records", records) .noErrorHandling().reply();
In the above examples, assuming that the latter example is inside a service called "
ObjectService
" and is referencing the incoming message that was sent in the former example, the message created will automatically reference the
ReplyTo
subject that was provided by the sender, and send the message back to the subject desired by the client on the client that sent the message.
Broadcasting messages to all clients listening on a specific subject is quite simple and involves nothing more than forgoing use of the reply API. For instance:
MessageBuilder.createMessage(). .toSubject("MessageListener") .with("Text", "Hello, from your overlords in the cloud") .noErrorHandling().sendGlobalWith(dispatcher);
If sent from the server, all clients currently connected, who are listening to the subject
"MessageListener"
will receive the message. It's as simple as that.
Communication from one client to another client is not directly possible within the bus federation, by design. This isn't to say that it's not possible. But one client cannot see a service within the federation of another client. We institute this limitation as a matter of basic security. But many software engineers will likely find the prospects of such communication appealing, so this section will provide some basic pointers on how to go about accomplishing it.
Relay ServicesThe essential architectural thing you'll need to do is create a relay service that runs on the server. Since a service advertised on the server is visible to all clients and all clients are visible to the server, you might already see where we're going with this.
By creating a service on the server which accepts messages from clients, you can create a simple protocol on-top of the bus to enable quasi peer-to-peer communication. (We say quasi, because it still needs to be routed through the server)
While you can probably imagine simply creating a broadcast-like service which accepts a message from one client and broadcasts it to the rest of the world, it may be less clear how to go about routing from one particular client to another particular client, so we'll focus on that problem. This is covered in Section 19.14.13, “Message Routing Information”
Every message that is sent between a local and remote (or server and client) buses contain session routing information. This information is used by the bus to determine what outbound queues to use to deliver the message to, so they will reach their intended recipients. It is possible to manually specify this information to indicate to the bus, where you want a specific message to go.
The utility class
org.jboss.errai.bus.server.util.ServerBusUtils
contains a utility method for extracting the String-based SessionID which is used to identify the message queue associated with any particular client. You may use this method to extract the
SessionID
from a message so that you may use it for routing. For example:
... public void callback(Message message) { String sessionId = ServerBusUtils.getSessionId(message); // Record this sessionId somewhere. ... }
The
SessionID
can then be stored in a medium, say a Map, to cross-reference specific users or whatever identifier you wish to allow one client to obtain a reference to the specific
SessionID
of another client. In which case, you can then provide the
SessionID
as a MessagePart to indicate to the bus where you want the message to go.
MessageBuilder.createMessage() .toSubject("ClientMessageListener") .signalling() .with(MessageParts.SessionID, sessionId) .with("Message", "We're relaying a message!") .noErrorHandling().sendNowWith(dispatcher);
By providing the
SessionID
part in the message, the bus will see this and use it for routing the message to the relevant queue.
Now you're routing from client-to-client!
It may be tempting however, to try and include destination
SessionIDs
at the client level, assuming that this will make the infrastructure simpler. But this will not achieve the desired results, as the bus treats
SessionIDs
as transient. Meaning, the
SessionID
information is not ever transmitted from bus-to-bus, and therefore is only directly relevant to the proximate bus.
It's important to understand the concept of how messaging works in ErraiBus. Service endpoints are given string-based names that are referenced by message senders. There is no difference between sending a message to a client-based service, or sending a message to a server-based service. In fact, a service of the same name may co-exist on both the client and the server and both will receive all messages bound for that service name, whether they are sent from the client or from the server.
Services are lightweight in ErraiBus, and can be declared liberally and extensively within your application to provide a message-based infrastructure for your web application. It can be tempting to think of ErraiBus simply as a client-server communication platform, but there is a plethora of possibilities for using ErraiBus purely with the GWT client context, such as a way to advertise and expose components dynamically, to get around the lack of reflection in GWT.
In fact, ErraiBus was originally designed to run completely within the client but quickly evolved into having the capabilities it now has today. So keep that in mind when you run up against problems in the client space that could benefit from runtime federation.
The MessageBuilder is the heart of the messaging API in ErraiBus. It provides a fluent / builder API, that is used for constructing messages. All three major message patterns can be constructed from the
MessageBuilder
.
Components that want to receive messages need to implement the
MessageCallback
interface.
But before we dive into the details, let look at some use cases first.
Sending Messages with the Client BusIn order to send a message from a client you need to create a
Message
and send it through an instance of
MessageBus
. In this simple example we send it to the subject 'HelloWorldService'.
public class HelloWorld implements EntryPoint { // Get an instance of the RequestDispatcher private RequestDispatcher dispatcher = ErraiBus.getDispatcher(); public void onModuleLoad() { Button button = new Button("Send message"); button.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { // Send a message to the 'HelloWorldService'. MessageBuilder.createMessage() .toSubject("HelloWorldService") // (1) .signalling() // (2) .noErrorHandling() // (3) .sendNowWith(dispatcher); // (4) }); [...] } } }
In the above example we build and send a message every time the button is clicked. Here's an explanation of what's going on as annotated above:
We specify the subject we wish to send a message to. In this case, "
HelloWorldService
".
We indicate that we wish to only signal the service, meaning, that we're not sending a qualifying command to the service. For information on this, read the section on Protocols .
We indicate that we do not want to provide an
ErrorCallback
to deal with errors for this message.
We transmit the message by providing an instance to the
RequestDispatcher
An astute observer will note that access to the
RequestDispatcher
differs within client code and server code. Because the client code does not run within a container, access to the
RequestDispatcher
and
MessageBus
is statically accessed using the
ErraiBus.get()
and
ErraiBus.getDispatcher()
methods. The server-side code, conversely, runs inside a dependency container for managing components. It currently uses Guice as the default container, but will more generically support the JSR-330 specification in the future.
Recieving Messages on the Server Bus / Server ServicesEvery message has a sender and at least one receiver. A receiver is as it sounds--it receives the message and does something with it. Implementing a receiver (also referred to as a service) is as simple as implementing our standard MessageCallback interface, which is used pervasively across, both client and server code. Let's begin with server side component that receives messages:
@Service public class HelloWorldService implements MessageCallback { public void callback(Message message) { System.out.println("Hello, World!"); } }
He we declare an extremely simple service. The
@Service
annotation provides a convenient, meta-data based way of having the bus auto-discover and deploy the service.
Sending Messages with the Server BusIn the following example we extend our server side component to reply with a message when the callback method is invoked. It will create a message and address it to the subject '
HelloWorldClient
':
@Service public class HelloWorldService implements MessageCallback { private RequestDispatcher dispatcher; @Inject public HelloWorldService(RequestDispatcher disaptcher) { dispatcher = dispatcher; } public void callback(CommandMessage message) { // Send a message to the 'HelloWorldClient'. MessageBuilder.createMessage() .toSubject("HelloWorldClient") // (1) .signalling() // (2) .with("text", "Hi There") // (3) .noErrorHandling() // (4) .sendNowWith(dispatcher); // (5) }); } }
The above example shows a service which sends a message in response to receiving a message. Here's what's going on:
We specify the subject we wish to send a message to. In this case, "
HelloWorldClient
". We are sending this message to all clients which are listening in on this subject. For information on how to communicate with a single client, see Section 2.6.
We indicate that we wish to only signal the service, meaning that we're not sending a qualifying command to the service. For information on this, read the section on Protocols.
We add a message part called "text" which contains the value "Hi there".
We indicate that we do not want to provide an
ErrorCallback
to deal with errors for this message.
We transmit the message by providing an instance of the
RequestDispatcher
.
Receiving Messages on the Client Bus/ Client ServicesMessages can be received asynchronously and arbitriraily by declaring callback services within the client bus. As ErraiBus maintains an open COMET channel at all times, these messages are delivered in real time to the client as they are sent. This provides built-in push messaging for all client services.
public class HelloWorld implements EntryPoint { private MessageBus bus = ErraiBus.get(); public void onModuleLoad() { [...] /** * Declare a local service to receive messages on the subject * "BroadcastReceiver". */ bus.subscribe("BroadcastReceiver", new MessageCallback() { public void callback(CommandMessage message) { /** * When a message arrives, extract the "text" field and * do something with it */ String messageText = message.get(String.class, "text"); } }); [...] } }
In the above example, we declare a new client service called
"BroadcastReceiver"
which can now accept both local messages and remote messages from the server bus. The service will be available in the client to receive messages as long the client bus is and the service is not explicitly de-registered.
ConversationsConversations are message exchanges which are between a single client and a service. They are a fundmentally important concept in ErraiBus, since by default, a message will be broadcast to all client services listening on a particular channel.
When you create a reply with an incoming message, you ensure that the message you are sending back is received by the same client which sent the incoming message. A simple example:
@Service public class HelloWorldService implements MessageCallback { public void callback(CommandMessage message) { // Send a message to the 'HelloWorldClient' on the client that sent us the // the message. MessageBuilder.createConversation(message) .toSubject("HelloWorldClient") .signalling() .with("text", "Hi There! We're having a reply!") .noErrorHandling().reply(); }); } }
Note that the only difference between the example in the previous section (2.4) and this is the use of the
createConversation()}}method with {{MessageBuilder
.
It is possible to contruct a message and a default response handler as part of the
MessageBuilder
API. It should be noted, that multiple replies will not be possible and will result an exception if attempted. Using this aspect of the API is very useful for doing simple psuedo-synchronous conversive things.
You can do this by specifying a
MessageCallback
using the
repliesTo()
method in the
MessageBuilder
API after specifying the error handling of the message.
MessageBuilder.createMessage() .toSubject("ConversationalService").signalling() .with("SomeField", someValue) .noErrorHandling() .repliesTo(new MessageCallback() { public void callback(Message message) { System.out.println("I received a response"); } })
See the next section on how to build conversational services that can respond to such messages.
It is possible for the sender to infer, to whatever conversational service it is calling, what subject it would like the reply to go to. This is accomplished by utilizing the standard
MessageParts.ReplyTo
message part. Using this methodology for building conversations is generally encouraged.
Consider the following client side code:
MessageBuilder.createMessage() .toSubject("ObjectService").signalling() .with(MessageParts.ReplyTo, "ClientEndpoint") .noErrorHandling().sendNowWith(dispatcher);
And the conversational code on the server (for service ObjectService ):
MessageBuilder.createConversation(message) .subjectProvided().signalling() .with("Records", records) .noErrorHandling().reply();
In the above examples, assuming that the latter example is inside a service called "
ObjectService
" and is referencing the incoming message that was sent in the former example, the message created will automatically reference the
ReplyTo
subject that was provided by the sender, and send the message back to the subject desired by the client on the client that sent the message.
Broadcasting messages to all clients listening on a specific subject is quite simple and involves nothing more than forgoing use of the reply API. For instance:
MessageBuilder.createMessage(). .toSubject("MessageListener") .with("Text", "Hello, from your overlords in the cloud") .noErrorHandling().sendGlobalWith(dispatcher);
If sent from the server, all clients currently connected, who are listening to the subject
"MessageListener"
will receive the message. It's as simple as that.
Communication from one client to another client is not directly possible within the bus federation, by design. This isn't to say that it's not possible. But one client cannot see a service within the federation of another client. We institute this limitation as a matter of basic security. But many software engineers will likely find the prospects of such communication appealing, so this section will provide some basic pointers on how to go about accomplishing it.
Relay ServicesThe essential architectural thing you'll need to do is create a relay service that runs on the server. Since a service advertised on the server is visible to all clients and all clients are visible to the server, you might already see where we're going with this.
By creating a service on the server which accepts messages from clients, you can create a simple protocol on-top of the bus to enable quasi peer-to-peer communication. (We say quasi, because it still needs to be routed through the server)
While you can probably imagine simply creating a broadcast-like service which accepts a message from one client and broadcasts it to the rest of the world, it may be less clear how to go about routing from one particular client to another particular client, so we'll focus on that problem. This is covered in Section 19.14.13, “Message Routing Information”
Communication from one client to another client is not directly possible within the bus federation, by design. This isn't to say that it's not possible. But one client cannot see a service within the federation of another client. We institute this limitation as a matter of basic security. But many software engineers will likely find the prospects of such communication appealing, so this section will provide some basic pointers on how to go about accomplishing it.
Relay ServicesThe essential architectural thing you'll need to do is create a relay service that runs on the server. Since a service advertised on the server is visible to all clients and all clients are visible to the server, you might already see where we're going with this.
By creating a service on the server which accepts messages from clients, you can create a simple protocol on-top of the bus to enable quasi peer-to-peer communication. (We say quasi, because it still needs to be routed through the server)
While you can probably imagine simply creating a broadcast-like service which accepts a message from one client and broadcasts it to the rest of the world, it may be less clear how to go about routing from one particular client to another particular client, so we'll focus on that problem. This is covered in Section 19.14.13, “Message Routing Information”
Every message that is sent between a local and remote (or server and client) buses contain session routing information. This information is used by the bus to determine what outbound queues to use to deliver the message to, so they will reach their intended recipients. It is possible to manually specify this information to indicate to the bus, where you want a specific message to go.
The utility class
org.jboss.errai.bus.server.util.ServerBusUtils
contains a utility method for extracting the String-based SessionID which is used to identify the message queue associated with any particular client. You may use this method to extract the
SessionID
from a message so that you may use it for routing. For example:
... public void callback(Message message) { String sessionId = ServerBusUtils.getSessionId(message); // Record this sessionId somewhere. ... }
The
SessionID
can then be stored in a medium, say a Map, to cross-reference specific users or whatever identifier you wish to allow one client to obtain a reference to the specific
SessionID
of another client. In which case, you can then provide the
SessionID
as a MessagePart to indicate to the bus where you want the message to go.
MessageBuilder.createMessage() .toSubject("ClientMessageListener") .signalling() .with(MessageParts.SessionID, sessionId) .with("Message", "We're relaying a message!") .noErrorHandling().sendNowWith(dispatcher);
By providing the
SessionID
part in the message, the bus will see this and use it for routing the message to the relevant queue.
Now you're routing from client-to-client!
It may be tempting however, to try and include destination
SessionIDs
at the client level, assuming that this will make the infrastructure simpler. But this will not achieve the desired results, as the bus treats
SessionIDs
as transient. Meaning, the
SessionID
information is not ever transmitted from bus-to-bus, and therefore is only directly relevant to the proximate bus.
Asynchronous messaging necessitates the need for asynchronous error handling. Luckily, support for handling errors is built directly into the
MessageBuilder
API, utilizing the
ErrorCallback
interface. In the examples shown in previous exceptions, error-handing has been glossed over with aubiquitous usage of the
noErrorHandling()
method while building messaging. We chose to require the explicit use of such a method to remind developers of the fact that they are responsible for their own error handling, requiring you to explicitly make the decision to forego handling potential errors.
As a general rule, you should always handle your errors . It will lead to faster and quicker identification of problems with your applications if you have error handlers, and generally help you build more robust code.
MessageBuilder.createMessage() .toSubject("HelloWorldService") .signalling() .with("msg", "Hi there!") .errorsHandledBy(new ErrorCallback() { public boolean error(Message message, Throwable throwable) { throwable.printStackTrace(); return true; } }) .sendNowWith(dispatcher);
The addition of error-handling at first may put off developers as it makes code more verbose and less-readable. This is nothing that some good practice can't fix. In fact, you may find cases where the same error-handler can appropriately be shared between multiple different calls.
ErrorCallback error = new ErrorCallback() { public boolean error(Message message, Throwable throwable) { throwable.printStackTrace(); return true; } } MessageBuilder.createMessage() .toSubject("HelloWorldService") .signalling() .with("msg", "Hi there!") .errorsHandledBy(error) .sendNowWith(dispatcher);
A little nicer.
The error handler requires that return a
boolean
value. This is to indicate whether or not Errai should perform the defautl error handling actions it would normally take during a failure. You will almost always want to return
true
here, unless you are trying to expicitly supress some undesirably activity by Errai, such as automatic subject-termination in conversations. But this is almost never the case.
In some applications, it may be necessary or desirable to delay transmission of, or continually stream data to a remote client or group of clients (or from a client to the server). In cases like this, you can utilize the
replyRepeating()
,
replyDelayed()
,
sendRepeating()
and
sendDelayed()
methods in the
MessageBuilder
.
Delayed TasksSending a task with a delay is straight forward. Simply utilize the appropriate method (either
replyDelayed()
or
sendDelayed()
).
MessageBuilder.createConversation(msg) .toSubject("FunSubject") .signalling() .noErrorHandling() .replyDelayed(TimeUnit.SECONDS, 5); // sends the message after 5 seconds.
or
MessageBuilder.createMessage() .toSubject("FunSubject") .signalling() .noErrorHandling() .sendDelayed(requestDispatcher, TimeUnit.SECONDS, 5); // sends the message after 5 seconds.
A repeating task is sent using one of the MessageBuilder's
repeatXXX()
methods. The task will repeat indefinitely until cancelled (see next section).
MessageBuilder.createMessage() .toSubject("FunSubject") .signalling() .withProvided("time", new ResourceProvider<String>() { SimpleDateFormat fmt = new SimpleDateFormat("hh:mm:ss"); public String get() { return fmt.format(new Date(System.currentTimeMillis()); } } .noErrorHandling() .sendRepeatingWith(requestDispatcher, TimeUnit.SECONDS, 1); //sends a message every 1 second
The above example sends a message very 1 second with a message part called
"time"
, containing a formatted time string. Note the use of the
withProvided()
method; a provided message part is calculated at the time of transmission as opposed to when the message is constructed.
Cancelling an Asynchronous TaskA delayed or repeating task can be cancelled by calling the
cancel()
method of the
AsyncTask
instance which is returned when creating a task. Reference to the AsyncTask object can be retained and cancelled by any other thread.
AsyncTask task = MessageBuilder.createConversation(message) .toSubject("TimeChannel").signalling() .withProvided(TimeServerParts.TimeString, new ResourceProvider<String>() { public String get() { return String.valueOf(System.currentTimeMillis()); } }).defaultErrorHandling().replyRepeating(TimeUnit.MILLISECONDS, 100); ... // cancel the task and interrupt it's thread if necessary. task.cancel(true);
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 to 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.
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.
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 correpond 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
@ExposeEntity
annotation needs to be present on the corresponding exception class (see
Section 19.20, “Serialization”
). Further the exception class needs to be part of the client-side code. For more details on
ErrorCallbacks
see
Section 19.15, “Handling Errors”
.
The ErraiBus maintains it's own seperate session management on-top of the regular HTTP session management. While the queue sessions are tied to, and dependant on HTTP sessions for the most part (meaning they die when HTTP sessions die), they provide extra layers of session tracking to make dealing with complex applications built on Errai easier.
One of the things Errai offers is the concept of session and local scopes.
A local scope is scoped to a single browser instance. But not to a single session.
In a browser a local scope would be confined to a tab or a window within a browser. You can store parameters inside a local scope just like with a session by using the
LocalContext
helper class.
public class TestService implements MessageCallback { public void callback(final Message message) { // obtain a reference to the local context by referencing the incoming message. LocalContext injectionContext = LocalContext.get(message); // set an attribute. injectionContext.setAttribute("MyAttribute", "Foo"); } }
A session scope is scoped across all instances of the same session. When a session scope is used, any parameters stored will be accessible and visible by all browser instances and tabs.
The SessionContext helper class is used for accessing the session scope.
public class TestService implements MessageCallback { public void callback(final Message message) { // obtain a reference to the session context by referencing the incoming message. SessionContext injectionContext = SessionContext.get(message); // set an attribute. injectionContext.setAttribute("MyAttribute", "Foo"); } }
The lifescyle of a session is bound by the underlying HTTP session. It is also bound by activity thresholds. Clients are required to send heartbeat messages every once in a while to maintain their sessions with the server. If a heartbeat message is not received after a certain period of time, the session is terminated and any resources are deallocated.
The lifescyle of a session is bound by the underlying HTTP session. It is also bound by activity thresholds. Clients are required to send heartbeat messages every once in a while to maintain their sessions with the server. If a heartbeat message is not received after a certain period of time, the session is terminated and any resources are deallocated.
One of the things Errai offers is the concept of session and local scopes.
A local scope is scoped to a single browser instance. But not to a single session.
In a browser a local scope would be confined to a tab or a window within a browser. You can store parameters inside a local scope just like with a session by using the
LocalContext
helper class.
public class TestService implements MessageCallback { public void callback(final Message message) { // obtain a reference to the local context by referencing the incoming message. LocalContext injectionContext = LocalContext.get(message); // set an attribute. injectionContext.setAttribute("MyAttribute", "Foo"); } }
A session scope is scoped across all instances of the same session. When a session scope is used, any parameters stored will be accessible and visible by all browser instances and tabs.
The SessionContext helper class is used for accessing the session scope.
public class TestService implements MessageCallback { public void callback(final Message message) { // obtain a reference to the session context by referencing the incoming message. SessionContext injectionContext = SessionContext.get(message); // set an attribute. injectionContext.setAttribute("MyAttribute", "Foo"); } }
A local scope is scoped to a single browser instance. But not to a single session.
In a browser a local scope would be confined to a tab or a window within a browser. You can store parameters inside a local scope just like with a session by using the
LocalContext
helper class.
public class TestService implements MessageCallback { public void callback(final Message message) { // obtain a reference to the local context by referencing the incoming message. LocalContext injectionContext = LocalContext.get(message); // set an attribute. injectionContext.setAttribute("MyAttribute", "Foo"); } }
A session scope is scoped across all instances of the same session. When a session scope is used, any parameters stored will be accessible and visible by all browser instances and tabs.
The SessionContext helper class is used for accessing the session scope.
public class TestService implements MessageCallback { public void callback(final Message message) { // obtain a reference to the session context by referencing the incoming message. SessionContext injectionContext = SessionContext.get(message); // set an attribute. injectionContext.setAttribute("MyAttribute", "Foo"); } }
Serialization on the ErraiBus supports serialization within the same scope and limitations as the default GWT RPC serialization rules. In order to expose your domain objects to the bus so they can be exported across the bus, you must annotate them with the
org.jboss.errai.bus.server.annotations.ExposeEntity
annotation. The presence of this annotation will cause Errai's GWT compiler extensions to generate marshall/demarshall stubs for the annotated objects at compile-time.
For example:
@ExposeEntity public class User implements java.io.Serializable { private int userId; public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } [...] }
All exposed entities must follow Java Bean convensions, and must be in the classpath both at compile-time and at runtime. Compile-time access to the entities is required since the creation of the marshalling/demarshalling proxies involves code generation.
It may not be possible to annotate certain types you wish to expose to the bus for serialization if the entities are located in a third-party library that you do not maintain. As such, you can explicitly indicate in the configuration that you would like to have this entities made available by declaring them in the
ErraiApp.properties
of any module.
errai.bus.serializableTypes=org.foo.Foo \ org.bar.Bar \ org.foobie.Foobie
It may not be possible to annotate certain types you wish to expose to the bus for serialization if the entities are located in a third-party library that you do not maintain. As such, you can explicitly indicate in the configuration that you would like to have this entities made available by declaring them in the
ErraiApp.properties
of any module.
errai.bus.serializableTypes=org.foo.Foo \ org.bar.Bar \ org.foobie.Foobie
By default, ErraiBus uses Google Guice to wire components. However, we plan on standardizing on
JSR-330 Dependency Injection
specification in the near future. When deploying services on the server-side, it is currently possible to obtain references to the
MessageBus
,
RequestDispatcher
, the
ErraiServiceConfigurator
, and
ErraiService
by declaring them as injection dependencies in Service classes, extension components, and session providers.
This section contains information on configuring the server-side bus.
Depending on what application server you are deploying on, you must provide an appropriate servlet implementation if you wish to use true, asynchronous I/O. See _section 6.5 _ for information on the available servlet implementations.
Here's a sample web.xml file:
<web-app 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/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>ErraiServlet</servlet-name> <servlet-class>org.jboss.errai.bus.server.servlet.DefaultBlockingServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ErraiServlet</servlet-name> <url-pattern>*.erraiBus</url-pattern> </servlet-mapping> <context-param> <param-name>errai.properties</param-name> <param-value>/WEB-INF/errai.properties</param-value> </context-param> <context-param> <param-name>login.config</param-name> <param-value>/WEB-INF/login.config</param-value> </context-param> <context-param> <param-name>users.properties</param-name> <param-value>/WEB-INF/users.properties</param-value> </context-param> </web-app>
he ErraiService.properties file contains basic configuration for the bus itself.
Example Configuration:
## ## Request dispatcher implementation (default is SimpleDispatcher) ## #errai.dispatcher_implementation=org.jboss.errai.bus.server.SimpleDispatcher errai.dispatcher_implementation=org.jboss.errai.bus.server.AsyncDispatcher # ## Worker pool size. This is the number of threads the asynchronous worker pool should provide for processing ## incoming messages. This option is only valid when using the AsyncDispatcher implementation. ## errai.async.thread_pool_size=5 ## ## Worker timeout (in seconds). This defines the time that a single asychronous process may run, before the worker pool ## terminates it and reclaims the thread. This option is only valid when using the AsyncDispatcher implementation. ## errai.async.worker.timeout=5 ## ## Specify the Authentication/Authorization Adapter to use ## #errai.authentication_adapter=org.jboss.errai.persistence.server.security.HibernateAuthenticationAdapter #errai.authentication_adapter=org.jboss.errai.bus.server.security.auth.JAASAdapter ## ## This property indicates whether or not authentication is required for all communication with the bus. Set this ## to 'true' if all access to your application should be secure. ## #errai.require_authentication_for_all=true
The
errai.dispatcher_implementation
defines, as it's name quite succinctly implies, the dispatcher implementation to be used by the bus. There are two implementations which come with Errai out of the box: the
SimpleDispatcher
and the
AsyncDispatcher
. See section on Dispatchers for more information about the differences between the two.
Specifies the total number of worker threads in the worker pool for handling and delivering messages. Adjusting this value does not have any effect if you are using the SimpleDispatcher.
Specifies the total amount of a time (in seconds) a service has to finish processing an incoming message before the pool interrupts the thread and returns an error. Adjusting this value does not have an effect if you are using the SimpleDispatcher.
Specifies the authentication modelAdapter the bus should use for determining whether calls should be serviced based on authentication and security principles.
Indicates whether or not the bus should always require the use of authentication for all requests inbound for the bus. If this is turned on, an authentication model adapter must be defined, and any user must be authenticated before the bus will deliver any messages from the client to any service.
A boolean indicating whether or not the Errai bootstrapper should automatically scan for services.
The ErraiApp.properties acts as a marker file. When it is detected inside a JAR or at the top of any classpath, the subdirectories are scanned for deployable components. As such, all Errai application modules in a project should contain an ErraiApp.properties at the root of all classpaths that you wish to be scanned.
The file can also include explicitly declared serializable types (such as those from third-party code) that cannot be annotated for serialization. (See the section on serialization for more details)
errai.bus.serializableTypesDefines a list of serializable types to expose to the bus.
errai.bus.serializableTypes=org.foo.Foo \ org.bar.Bar \ org.foobie.Foobie
The ErraiApp.properties acts as a marker file. When it is detected inside a JAR or at the top of any classpath, the subdirectories are scanned for deployable components. As such, all Errai application modules in a project should contain an ErraiApp.properties at the root of all classpaths that you wish to be scanned.
The file can also include explicitly declared serializable types (such as those from third-party code) that cannot be annotated for serialization. (See the section on serialization for more details)
errai.bus.serializableTypesDefines a list of serializable types to expose to the bus.
errai.bus.serializableTypes=org.foo.Foo \ org.bar.Bar \ org.foobie.Foobie
he ErraiService.properties file contains basic configuration for the bus itself.
Example Configuration:
## ## Request dispatcher implementation (default is SimpleDispatcher) ## #errai.dispatcher_implementation=org.jboss.errai.bus.server.SimpleDispatcher errai.dispatcher_implementation=org.jboss.errai.bus.server.AsyncDispatcher # ## Worker pool size. This is the number of threads the asynchronous worker pool should provide for processing ## incoming messages. This option is only valid when using the AsyncDispatcher implementation. ## errai.async.thread_pool_size=5 ## ## Worker timeout (in seconds). This defines the time that a single asychronous process may run, before the worker pool ## terminates it and reclaims the thread. This option is only valid when using the AsyncDispatcher implementation. ## errai.async.worker.timeout=5 ## ## Specify the Authentication/Authorization Adapter to use ## #errai.authentication_adapter=org.jboss.errai.persistence.server.security.HibernateAuthenticationAdapter #errai.authentication_adapter=org.jboss.errai.bus.server.security.auth.JAASAdapter ## ## This property indicates whether or not authentication is required for all communication with the bus. Set this ## to 'true' if all access to your application should be secure. ## #errai.require_authentication_for_all=true
The
errai.dispatcher_implementation
defines, as it's name quite succinctly implies, the dispatcher implementation to be used by the bus. There are two implementations which come with Errai out of the box: the
SimpleDispatcher
and the
AsyncDispatcher
. See section on Dispatchers for more information about the differences between the two.
Specifies the total number of worker threads in the worker pool for handling and delivering messages. Adjusting this value does not have any effect if you are using the SimpleDispatcher.
Specifies the total amount of a time (in seconds) a service has to finish processing an incoming message before the pool interrupts the thread and returns an error. Adjusting this value does not have an effect if you are using the SimpleDispatcher.
Specifies the authentication modelAdapter the bus should use for determining whether calls should be serviced based on authentication and security principles.
Indicates whether or not the bus should always require the use of authentication for all requests inbound for the bus. If this is turned on, an authentication model adapter must be defined, and any user must be authenticated before the bus will deliver any messages from the client to any service.
A boolean indicating whether or not the Errai bootstrapper should automatically scan for services.
A boolean indicating whether or not the Errai bootstrapper should automatically scan for extensions.
Specifies the total number of worker threads in the worker pool for handling and delivering messages. Adjusting this value does not have any effect if you are using the SimpleDispatcher.
Specifies the total amount of a time (in seconds) a service has to finish processing an incoming message before the pool interrupts the thread and returns an error. Adjusting this value does not have an effect if you are using the SimpleDispatcher.
Specifies the authentication modelAdapter the bus should use for determining whether calls should be serviced based on authentication and security principles.
A boolean indicating whether or not the Errai bootstrapper should automatically scan for services.
A boolean indicating whether or not the Errai bootstrapper should automatically scan for extensions.
The
errai.dispatcher_implementation
defines, as it's name quite succinctly implies, the dispatcher implementation to be used by the bus. There are two implementations which come with Errai out of the box: the
SimpleDispatcher
and the
AsyncDispatcher
. See section on Dispatchers for more information about the differences between the two.
Indicates whether or not the bus should always require the use of authentication for all requests inbound for the bus. If this is turned on, an authentication model adapter must be defined, and any user must be authenticated before the bus will deliver any messages from the client to any service.
Depending on what application server you are deploying on, you must provide an appropriate servlet implementation if you wish to use true, asynchronous I/O. See _section 6.5 _ for information on the available servlet implementations.
Here's a sample web.xml file:
<web-app 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/web-app_2_5.xsd" version="2.5"> <servlet> <servlet-name>ErraiServlet</servlet-name> <servlet-class>org.jboss.errai.bus.server.servlet.DefaultBlockingServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>ErraiServlet</servlet-name> <url-pattern>*.erraiBus</url-pattern> </servlet-mapping> <context-param> <param-name>errai.properties</param-name> <param-value>/WEB-INF/errai.properties</param-value> </context-param> <context-param> <param-name>login.config</param-name> <param-value>/WEB-INF/login.config</param-value> </context-param> <context-param> <param-name>users.properties</param-name> <param-value>/WEB-INF/users.properties</param-value> </context-param> </web-app>
Dispatchers encapsulate the strategy for taking messages that need to be delivered somewhere and seeing that they are delivered to where they need to go. There are two primary implementations that are provided with Errai, depending on your needs.
SimpleDispatcher is basic implementation that provides no asychronous delivery mechanism. Rather, when you configure the Errai to use this implementation, messages are delivered to their endpoints synchronously. The incoming HTTP thread will be held open until the messages are delivered.
While this sounds like it has almost no advantages, especially in terms of scalablity. Using the SimpleDispatcher can be far preferable when you're developing your application, as any errors and stack traces will be far more easily traced and some cloud services may not permit the use of threads in any case.
The AsyncDispatcher provides full asynchronous delivery of messages. When this dispatcher is used, HTTP threads will have control immediately returned upon dispatch of the message. This dispatcher provides far more efficient use of resources in high-load applications, and will significantly decrease memory and thread usage overall.
The AsyncDispatcher provides full asynchronous delivery of messages. When this dispatcher is used, HTTP threads will have control immediately returned upon dispatch of the message. This dispatcher provides far more efficient use of resources in high-load applications, and will significantly decrease memory and thread usage overall.
SimpleDispatcher is basic implementation that provides no asychronous delivery mechanism. Rather, when you configure the Errai to use this implementation, messages are delivered to their endpoints synchronously. The incoming HTTP thread will be held open until the messages are delivered.
While this sounds like it has almost no advantages, especially in terms of scalablity. Using the SimpleDispatcher can be far preferable when you're developing your application, as any errors and stack traces will be far more easily traced and some cloud services may not permit the use of threads in any case.
Errai has several different implementations for HTTP traffic to and from the bus. We provide a universally-compatible blocking implementation that provides fully synchronous communication to/from the server-side bus. Where this introduces scalability problems, we have implemented many webserver-specific implementations that take advantage of the various proprietary APIs to provide true asynchrony.
These inlcuded implementations are packaged at:
org.jboss.errai.bus.server.servlet
This is a universal, completely servlet spec (2.0) compliant, Servlet implementation. It provides purely synchronous request handling and should work in virtually any servlet container, unless there are restrictions on putting threads into sleep states.
The Tomcat AIO implementation of our servlet allows Errai to take advantage of Tomcat's event-based AIO APIs to improve scalability and reduce thread usage. The use of this implementation is dependant on the Tomcat container being configured to support AIO using either it's NIO or APR connectors. This servlet will NOT work with the regular HTTP and AJP connectors.
The Jetty implementation leverages Jetty's continuations support, which allows for threadless pausing of port connections. This servlet implementation should work without any special configuration of Jetty.
The JBoss Comet support utilizes the JBoss Web AIO APIs (AS 5.0 and AS 6.0) to improve scalability and reduce thread usage. The HTTP, NIO, and AJP connectors are not supported. Use of this implementation requires use of the APR (Apache Portable Runtime).
Support for the comet API in the Grizzy HTTP server (used in Glassfish).
This is a universal, completely servlet spec (2.0) compliant, Servlet implementation. It provides purely synchronous request handling and should work in virtually any servlet container, unless there are restrictions on putting threads into sleep states.
Support for the comet API in the Grizzy HTTP server (used in Glassfish).
The JBoss Comet support utilizes the JBoss Web AIO APIs (AS 5.0 and AS 6.0) to improve scalability and reduce thread usage. The HTTP, NIO, and AJP connectors are not supported. Use of this implementation requires use of the APR (Apache Portable Runtime).
The Jetty implementation leverages Jetty's continuations support, which allows for threadless pausing of port connections. This servlet implementation should work without any special configuration of Jetty.
The Tomcat AIO implementation of our servlet allows Errai to take advantage of Tomcat's event-based AIO APIs to improve scalability and reduce thread usage. The use of this implementation is dependant on the Tomcat container being configured to support AIO using either it's NIO or APR connectors. This servlet will NOT work with the regular HTTP and AJP connectors.
Errai includes a bus monitoring application, which allows you to monitor all of the message exchange activity on the bus in order to help track down any potential problems It allows you to inspect individual messages to examine their state and structure.
To utilize the bus monitor, you'll need to include the _errai-tools _ package as part of your application's dependencies. When you run your application in development mode, you will simply need to add the following JVM options to your run configuration in order to launch the monitor:
-Derrai.tools.bus_monitor_attach=true
The monitor provides you a real-time perspective on what's going on inside the bus. The left side of the main screen lists the services that are currently available, and the right side is the service-explorer, which will show details about the service.
To see what's going on with a specific service, simply double-click on the service or highlight the service, then click "Monitor Service...". This will bring up the service activity monitor.
The service activity monitor will display a list of all the messages that were transmitted on the bus since the monitor became active. You do not need to actually have each specific monitor window open in order to actively monitor the bus activity. All activity on the bus is recorded.
The monitor allows you select individual messages, an view their individual parts. Clicking on a message part will bring up the object inspector, which will allow you to explore the state of any objects contained within the message, not unlike the object inspectors provided by debuggers in your favorite IDE. This can be a powerful tool for looking under the covers of your application.