Development

EJB remoting – JBoss 6 – Wildfly9

We made a remote EJB call in isolation from the application server classloader to solve JBoss6/Wildfly9 communication issues.

December 28 2015

Problem

The ability to call remote EJB across different versions of JBoss/Wildfly AS is not supported.

In order to call remote EJB, you’ll need a client library. This client library contains classes that are normally part of application server. When the client is standalone (Java SE) application, this is correct solution, but when the client is another Java EE application server this is not possible, since many classes are clashing.

The only exception is when the application servers are the exact same version, since no client library is needed then.

Solution

Try to emulate standalone EJB client for target server.

Make remote EJB call in isolation from application server classloader, and add only client libraries needed from another server.

Path to solution

In search for a solution, we stumbled upon a Carlo de Wolf (RedHat) blog (http://wolf-71.blogspot.rs/2010/02/et-phone-home.html) which addresses similar problem. The most important part was AluniteClassLoader class, which has no parent class loader and delegates to given class loaders in turn.

Also very important was the correct way of including our own interface to be accessible to classloader, to avoid dreaded ClassCastExceptions.

Proof of concept

Cross-server communication project (https://github.com/infobip/jboss-wildfly-remoting)

  • 1 JAR module – cross-server-common, with IConnector remote interface, and JndiHandler class
  • 1 EJB module – cross-server-ejb-jboss6, with one implementation of IConnector, targeted for JBoss6
  • 1 EJB module – cross-server-ejb-wildfly9, with one implementation of IConnector, targeted for Wildfly9
  • 1 EAR module – profile based build:
    • wildfly9 profile builds ear with cross-server-common, and cross-server-ejb-wildfly9
    • jboss6 profile builds ear with cross-server-common, and cross-server-ejb-jboss6 and excludes deployment descriptor jboss-deployment-structure.xml from ear

Blog Graphic

IConnector interface has two methods, hello() and answer(). Each hello() calls answer() on another server instance.

JndiHandler class has 3 methods, one for standard JBoss6 lookup, and two methods for Wildfly9 - for EJB client API based lookup, and for http-remoting style lookup based on jboss-remote-naming project:

  • lookupJboss6
Properties properties = new Properties(); 
properties.setProperty("java.naming.provider.url", host + ":" + port); 
properties.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); 
properties.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces"); 
InitialContext ctx = new InitialContext(properties); 
T service = (T) ctx.lookup(jndiName);
  • lookupWildfly9 (EJB client API)
Properties properties = new Properties(); 
properties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming"); 
InitialContext ctx = new InitialContext(properties); 
Context ejbRootNamingContext = (Context) ctx.lookup("ejb:");
T service = (T) ctx.lookup(ejb:" + jndiName);

If you choose to use EJB client lookup, client must have jboss-ejb-client.properties on classpath.

  • lookupWildfly9HttpRemoting (jboss-remote-naming)
Properties properties = new Properties(); 
properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory"); 
properties.put(Context.PROVIDER_URL, "http-remoting://" + host + ":" + port); 
properties.put("jboss.naming.client.ejb.context", true); 
InitialContext ctx = new InitialContext(properties); 
T service = (T) ctx.lookup(jndiName);

Each server needs to have client libraries from another server on local machine, but not deployed on application server.

Lets start two application servers.

Lets say we start JBoss6 AS on standard 8080 port, and Wildfly with port-offset 100.

JBoss6-Wildfly9 communication in hello method:

ClassLoader previous = Thread.currentThread().getContextClassLoader(); 
URLClassLoader urlCl = new URLClassLoader(new URL[]{ new URL(new File(jbossHome).toURI().toURL(), "client/jboss-client.jar") }, null); 
ClassLoader cl = new AluniteClassLoader(urlCl, previous);
Thread.currentThread().setContextClassLoader(cl);
String jndiname = "/cross-server-test/cross-server-ejb-wildfly9/ConnectorWildfly9Impl!com.infobip.crossserver.IConnector"; 
IConnector connector = (IConnector)JndiHandler.lookupWildfly9HttpRemoting(jndiname, new String[] {"localhost"}, 8180);
String answer = connector.answer(name);
logger.info("Wildfly is answering: " + answer);
// in the end, we return to previous classloader 
Thread.currentThread().setContextClassLoader(previous);

Wildfly9-Jboss6 communication in hello method:

ClassLoader previous = Thread.currentThread().getContextClassLoader(); 
URLClassLoader urlCl = new URLClassLoader(new URL[]{ new URL(new File(jbossHome).toURI().toURL(), "jbossall-client.jar") }, null); 
ClassLoader cl = new AluniteClassLoader(urlCl, previous);
Thread.currentThread().setContextClassLoader(cl);
String jndiname = "ConnectorJBoss6"; 
IConnector connector = (IConnector)JndiHandler.lookupJBoss6(jndiname, new String[] {"localhost"}, JndiHandler.LOCAL_JNDI_PORT);
String answer = connector.answer(name);
logger.info("JBoss6 is answering: " + answer);
// in the end, we return to previous classloader 
Thread.currentThread().setContextClassLoader(previous); 

And magic happens…

When we run Junit test in cross-server-wildfly9:

IConnector connector = (IConnector) lookup ("localhost", "8180"); connector.hello("Infobip");

Engineering

Engineering

And similar, Junit test in cross-server-jboss6:

IConnector connector = (IConnector) lookup("localhost", "8080"); connector.hello("Infobip");

Engineering

Engineering

(By Jelena Lazic, Software Engineer)