Development

EJB remoting – JBoss 6 – Wildfly

Abbiamo fatto una chiamata EJB remota isolata dal caricatore di classe del server di applicazione per risolvere i problemi di comunicazione JBoss6/Wildfly9.

December 28 2015

Problema

La funzionalità di chiamata dell’EJB remoto da versioni differenti di JBoss/Wildfly AS non è supportata.

Per chiamare l’EJB remoto, è necessaria una libreria client. Questa libreria client contiene classi che sono normalmente parte del server di applicazione. Quando il client è autonomo (Java SE), questa è la soluzione corretta, ma quando il client è un’altro server di applicazione Java EE questo non è possibile perché molte classi sono in conflitto.

L’unica eccezione è quando i server di applicazione sono nella stessa esatta versione, perché in quel caso non è necessaria alcuna libreria client.

Soluzione

Cercare di emulare il client EJB autonomo per puntare al server.

Eseguire una chiamata EJB in remoto isolata dal caricatore di classi del server, e aggiungere solo le librerie client necessarie da un altro server.

La strada verso la soluzione

In cerca di una soluzione, siamo capitati sul blog di Carlo de Wolf (RedHat) (http://wolf-71.blogspot.rs/2010/02/et-phone-home.html) che tratta un problema simile. La parte più importante era la classe AluniteClassLoade, che non ha alcun caricatore di classe principale e delega invece a determinati caricatori di classe.

Inoltre, molto importante è stato il modo corretto di includere la nostra interfaccia per renderla accessibile al caricatore di classi, per evitare le temute ClassCastExceptions.

Proof of concept

Progetto di comunicazione cross-server (https://github.com/infobip/jboss-wildfly-remoting)

  • 1 modulo JAR – cross-server-common, con interfaccia remota IConnector e classe JndiHandler
  • 1 modulo EJB – cross-server-ejb-jboss6, con un’implementazione di IConnector, assegnata per JBoss6
  • 1 modulo EJB – cross-server-ejb-wildfly9, con un’implementazione di IConnector, assegnata per Wildfly9
  • 1 modulo EAR – profile based build:
    • il profilo wildfly9 costruisce ear con cross-server-common, e cross-server-ejb-wildfly9
    • il profilo jboss6 costruisce ear con cross-server-common, e cross-server-ejb-jboss6 ed esclude il descrittore di implementazione jboss-deployment-structure.xml da ear

L’interfaccia di IConnector ha due metodi, hello() e answer(). Ogni hello() chiama answer() su un’altra istanza server.

La classe JndiHandler ha 3 metodi, uno per la ricerca JBoss6 standard, e due metodi per Wildfly9 – per la ricerca basata su API client EJB e per la ricerca in stile http-remoting basata sul progetto jboss-remote-naming:

  • 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);

Se si sceglie di utilizzare la ricerca client EJB, il client deve avere jboss-ejb-client.properties nel proprio 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);

Ogni server necessita di avere librerie client da un altro server su macchina locale, ma non implementate su un server di applicazione.

Avviamo due server di applicazione.

Abbiamo avviato JBoss6 AS su porta 8080 standard e Wildfly con offset porta 100.

Comunicazione JBoss6-Wildfly9 nel metodo hello:

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);

Comunicazione Wildfly9-Jboss6 nel metodo hello:

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); 

E avviene la magia…

Quando eseguiamo il test Junit in cross-server-wildfly9:

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

E in modo simile, il test Junit in cross-server-jboss6:

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

(Di Jelena Lazic, software engineer)