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 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:
- create the actual JGroups channel using JBoss protocol configuration,
- bind the newly created JGroups channel to the JNDI.
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!