Using JGroups Directly From JBoss AS 7 Component

UPDATE (5.08.2013) – I’ve updated this post to support JBoss EAP 6.1 Final (JBoss AS 7.2). Enjoy

UPDATE (2.09.2013) – Bela Ban pointed to his very interesting post about new ForkChannel feature and easy channel hijacking directly from JBoss AS / Infinispan. Read about it here

JGroups is Bela Ban’s piece of software for reliable message exchange that is highly configurable and can use either TCP or UDP as a transport protocol. Basically – you run the JGroups on number of clients, they form a cluster and they can send and receive messages within the cluster.

JGroups logo

JGroups is used internally by JBoss Infinispan. Infinispan, however, unlike JGroups adds the distributed cache semantics (replicated / distributed modes, entries invalidation, transactional behavior, Map access API, etc.) It even allows you to use the cluster as a compute grid.

Infinispan in turn is used to provide JBoss AS 7 clustering functionalities. Therefore, it means that the underlying JGroups subsystem can and is configured using a standard JBoss AS 7 standalone*.xml file. You can access Infinispan cache from your Java EE component (e.g. EJB) without any problems as described here.

However, there are cases when you’d like to use just the underlying JGroups messaging instead of all the cache semantics Infinispan gives you. And here’s the place things are becoming more complicated. You can always use JGroups directly and store the configuration for it as an application-local resources. It might become arguable if this is or isn’t a violation of the Java EE spec which says that an application should not manage low-level connections, spawn threads, open sockets, etc. This is something that is better to be left to the application server – it also allows us to use one configuration file instead of spreading it across multiple places.

So, the question is – how to access the JGroups subsystem from our EJB application? The whole solution involves few steps which will be described below.

If you want to check the whole working project – take a look at my JGroups AS7 Github project.

1. Write Custom JBoss AS 7 Service Activator

This activator (JGroupsChannelServiceActivator.java) will do two things:

First part is done in JGroupsChannelServiceActivator#createChannel(-). I don’t know the ServiceActivator nor other internals of the JBoss AS 7 modules but from what you can read:

InjectedValue<ChannelFactory> channelFactory = new InjectedValue<>();
ServiceName serviceName = ChannelFactoryService.getServiceName(STACK_NAME);
ChannelService channelService = new ChannelService(CHANNEL_NAME, 
                                                             channelFactory);

target.addService(channelServiceName, channelService)
      .addDependency(serviceName, ChannelFactory.class, channelFactory)
      .install();  

it seems that it creates a new service (ChannelService) and let the JBoss MSC automatically inject its dependent ChannelFactory during the installation. The ChannelFactory will use the UDP protocol stack.

Now in case of JBoss < 7.2 it would be enough because channel service would automatically connect to our channel. However, since JBoss AS 7.2 this behavior was changed and we now need to connect to the channel manually using our own service – JGroupsService. So this is what this code does:

// Our own service that will just connect to already configured channel
ServiceName cService = ServiceName.of(JGROUPS_CHANNEL_SERVICE_PREFIX, 
                                                               CHANNEL_NAME);

InjectedValue<Channel> channel = new InjectedValue<>();
target.addService(cService, new JGroupsService(channel, CHANNEL_NAME))
      .addDependency(ServiceBuilder.DependencyType.REQUIRED, 
                           ChannelService.getServiceName(CHANNEL_NAME), 
                           Channel.class, 
                           channel)
      .setInitialMode(ServiceController.Mode.ACTIVE).install();

The second part is done in JGroupsChannelServiceActivator#bindChannelToJNDI(-) and it binds the newly created Channel instance to the JNDI under user-defined location. In our case it is java:jboss/channel/myChannel.

2. Register the Activator

We now need to tell JBoss AS 7 to invoke our custom Activator. It is done using standardized JDK ServiceLoader API. In a nutshell it means we need to provide a META-INF/services/org.jboss.msc.service.ServiceActivator file with fully qualified name of our activator class. Take a look at this example.

3. Add Required Modules to our Application

Ok, so we have an activator that should do the magic. If we’d try to deploy it as such we’ll get a bunch of ClassNotFoundExceptions. It is because the JBoss Modules. Our application is not packed with all those JBoss artifacts like JGroups, ServiceActivator API and the JNDI related classes. We don’t want to clutter our app with those libraries – we just want to define the dependencies on modules provided by JBoss AS 7 itself. We do it in the META-INF/jboss-deployment-structure.xml. Note that we could do it in MANIFEST.MF Dependencies: section but Intellij IDEA doesn’t seem to be working with Maven generated MANIFEST.MF:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.jgroups"/>
            <module name="org.jboss.as.naming"/>
            <module name="org.jboss.as.clustering.jgroups"/>
        </dependencies>
    </deployment>
</jboss-deployment-structure>

JGroups modules are required for accessing the JChannel, ChannelService, etc. The naming module is required for the JNDI binding code.

4. Develop the EJB Using JGroups Channel

JGroupsSampleDataProducer is a Singleton EJB that shows how to access the JGroups channel. It’s rather simple because of the JNDI binding. We can just use:

@Resource(lookup = "java:jboss/channel/myChannel")
private JChannel channel;   

and there it is. This EJB registers a timer that is invoked every 2 seconds and sends some random String message.

5. Deploy the EJB-JAR

We are now ready to deploy our application to JBoss AS 7 server. The most important part here is to make sure our server will be running with the appropriate configuration, which means one with JGroups protocol stack defined. It is done using <subsystem xmlns="urn:jboss:domain:jgroups:1.1">.

I am using JBoss EAP 6.1 Final and standalone-full-ha.xml config.

Note Because Intellij IDEA doesn’t allow you to easily change the configuration file for your JBoss AS as the Eclipse does, we will need to specify it using VM options: -Djboss.server.default.config=standalone-full-ha.xml.
Alternatively you can go to ‘Run configuration’ -> Startup/Connection -> Run -> and modify ‘Startup script’ to include -c standalone-full-ha.xml.

We also need to make sure JGroups will use IPv4 (it sometimes chooses the IPv6 which can lead to some weird and hard to solve problems.) To do so, add the -Djava.net.preferIPv4Stack=true option to the Server configuration.

6. Run the Client Application

You can find a rather simple Client code here. It just connects to JGroups cluster using specified configuration file. Mind that multicast port number and address should be set to the same values for server and client.

Also remember to add -Djava.net.preferIPv4Stack=true VM option while running your client.

Hope you’ll find this tutorial helpful and it’ll save you some configuration time.

Great thanks to Bela Ban for a lot of important advice and Paul Ferraro for pointing me to relavant forum topics (like this or this one) regarding similar problems.

Cheers!