[JBoss&EJB] EJB remoti: come effettuare la lookup da client JBoss

In questo articolo, descrivo come effettuare una lookup da un client JBoss verso un servizio EJB (Enterprise Java Bean) remoto, esposto su una macchina differente dal client stesso.

Queste sono le tecnologie utilizzate in questo tutorial:

ejb_lookup

Il nostro EJB remoto di test è il seguente:

@Stateless
@Remote(TestService.class)
@TransactionManagement(TransactionManagementType.BEAN)
public class TestServiceBean implements TestService {

       public methodTest(){

           //code implementation here

       }

}

Il precedente EJB remoto verrà esposto su un application server installato su una macchina differente (che chiamerò destination server). Di seguito, divido le attività di configurazione in due: configurazione destination server e configurazione client server.

 

Configurazione destination server

Sull’application server su cui è deployato l’EJB remoto, occorre semplicemente creare un utente applicativo, per mettere in sicurezza la chiamata remota (security realm). Utilizziamo lo script add-user.sh (o .bat, se siete su Windows), presente in ${JBOSS_HOME}/bin, per creare l’utente ejb (con password test):

add-user JBoss

Dopo aver creato l’utente su JBoss, basta startare l’AS e deployare il servizio remoto.

 

Configurazione client server

Il grosso della configurazione va fatta sul client che deve richiamare l’EJB remoto.

Nel file di configurazione di JBoss, standalone.xml (o domain.xml), occorre eseguire quanto segue:

  • creare il security realm sul client server
   <management>
        <security-realms>
            ...
            <security-realm name="ejb-security-realm">
                <server-identities>
                    <secret value="dGVzdA=="/>
                </server-identities>
            </security-realm>
        </security-realms>

Dove in secret c’è la password (test) codifica in BASE-64.

  • creare l’outbound-socket-binding sul client server
 <socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
        ...
        <outbound-socket-binding name="remote-ejb">
            <remote-destination host="127.0.01" port="4447"/>
        </outbound-socket-binding>
     </socket-binding-group>

Dove al posto dell’indirizzo IP 127.0.0.1 potete inserire quello della macchina remota del destination server (la porta di default del remoting è la 4447, a meno che non l’abbiate cambiata).

  • creare un remote-outbound-connection che usa l’outbound-socket-binding
<subsystem xmlns="urn:jboss:domain:remoting:1.1">
....
            <outbound-connections>
                <remote-outbound-connection name="remote-ejb-connection" outbound-socket-binding-ref="remote-ejb" security-realm="ejb-security-realm" username="ejb">
                    <properties>
                        <property name="SASL_POLICY_NOANONYMOUS" value="false"/>
                        <property name="SSL_ENABLED" value="false"/>
                    </properties>
                </remote-outbound-connection>
            </outbound-connections>
        </subsystem>

Dove in corrispondenza dell’attributo username c’è l’utente di test che abbiamo creato sul destination server.

Lato configurazione del client server abbiamo terminato.

Di seguito, si riportano gli interventi da fare sull’applicazione client (client application), per poter richiamare il servizio remoto:

  • inserire nel classpath dell’applicazione client il file jboss-ejb-client.xml 
<jboss-ejb-client xmlns="urn:jboss:ejb-client:1.0">
    <client-context>
        <ejb-receivers>
            <remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection"/>
        </ejb-receivers>
    </client-context>
</jboss-ejb-client>

Infine, per effettuare la lookup remota in modo programmatico, ecco un esempio di una classe client di test:

import javax.naming.Context;
import java.util.Hashtable;
import javax.naming.InitialContext;
 
...
public void invokeOnBean() {
        try {
            final Hashtable props = new Hashtable();
            // setup the ejb: namespace URL factory
            props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
            // create the InitialContext
            final Context context = new javax.naming.InitialContext(props);
 
            // Lookup
            final TestService bean = (TestService) context.lookup("ejb:" + "myapp" + "/" + "myejb" + "/" + "" + "/" + "TestService" + "!" + it.francescoficetola.ejb.TestService.class.getName());
 
            // invoke on the bean
            final String greeting = bean.test();
 
 
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
}

Nota. Quando si deploya l’EJB remoto, il JNDI name viene automaticamente registrato dall’application server. Ad esempio, per gli EJB stateless, il JNDI name è come questo:

ejb:jboss-as-ejb-remote-app/TestServiceBean!it.francescoficetola.ejb.TestService

Come si riporta in questo articolo (EJB invocations from a remote client using JNDI), ecco la regola per la generazione del JNDI name:

ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>

Poiché in app-namemodule-name mi venivano registrati anche i numeri di versione dei moduli applicativi (dopo aver buildato con Maven), ho effettuato un override dei loro valori in questo modo:

  • per settare l’app-name, ho inserito l’application-name nel file application.xml dell’EAR in cui è deployato l’EJB remoto:

<application-name>myapp</application-name>

  • per settare il module-name, occorre inserire nel classpath del modulo EJB (nella directory META-INF) il file ejb-jar.xml:
<ejb-jar xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/ejb-jar_3_2.xsd"
    version="3.2">
    <module-name>myejb</module-name>
</ejb-jar>

 

Riferimenti:

[JMS&JBoss] Configurazione delle code JMS su JBoss 7

hornetQIn questo tutorial condivido la configurazione delle code JMS su JBoss 7, utilizzando HornetQ Servermodulo già presente nelle versioni 7.x di JBoss.

Nel file standalone.xml in ${JBOSS_HOME}/standalone/configuration (o nel file domain.xml, se è questo il file utilizzato per la configurazione di JBoss), aggiungere ciò che segue:

  • Inserire il modulo messaging nelle estensioni di JBoss (se non c’è), ossia all’interno del tag <extensions>.:
<extension module="org.jboss.as.messaging"/>
  • Per configurare l’hornetQ server e registrare i JNDI name delle code (sia queue che topic), occorre aggiungere un subsystem all’interno del tag <profile>:
<subsystem xmlns="urn:jboss:domain:messaging:1.4">
 <hornetq-server>
 <persistence-enabled>true</persistence-enabled>
 <security-enabled>false</security-enabled>
 <journal-file-size>102400</journal-file-size>
 <journal-min-files>2</journal-min-files>

 <connectors>
 <netty-connector name="netty" socket-binding="messaging">
 <param key="host" value="${jboss.bind.address}"/>
 </netty-connector>
 <netty-connector name="netty-throughput" socket-binding="messaging-throughput">
 <param key="host" value="${jboss.bind.address}"/>
 <param key="batch-delay" value="50"/>
 </netty-connector>
 <in-vm-connector name="in-vm" server-id="0"/>
 </connectors>

 <acceptors>
 <netty-acceptor name="netty" socket-binding="messaging"/>
 <netty-acceptor name="netty-throughput" socket-binding="messaging-throughput">
 <param key="batch-delay" value="50"/>
 <param key="direct-deliver" value="false"/>
 </netty-acceptor>
 <in-vm-acceptor name="in-vm" server-id="0"/>
 </acceptors>

 <security-settings>
 <security-setting match="#">
 <permission type="send" roles="guest"/>
 <permission type="consume" roles="guest"/>
 <permission type="createNonDurableQueue" roles="guest"/>
 <permission type="deleteNonDurableQueue" roles="guest"/>
 </security-setting>
 </security-settings>

 <address-settings>
 <address-setting match="jms.queue.testQueue">
 <dead-letter-address>jms.queue.DLQ</dead-letter-address>
 <expiry-address>jms.queue.ExpiryQueue</expiry-address>
 <redelivery-delay>0</redelivery-delay>
 <max-delivery-attempts>5</max-delivery-attempts>
 <max-size-bytes>10485760</max-size-bytes>
 <address-full-policy>BLOCK</address-full-policy>
 <message-counter-history-day-limit>10</message-counter-history-day-limit>
 </address-setting>
 
 <address-setting match="jms.queue.DLQ">
 <max-delivery-attempts>5</max-delivery-attempts>
 <max-size-bytes>10485760</max-size-bytes>
 <address-full-policy>BLOCK</address-full-policy>
 <message-counter-history-day-limit>10</message-counter-history-day-limit>
 </address-setting>
 </address-settings>

 <jms-connection-factories>
 <connection-factory name="InVmConnectionFactory">
 <connectors>
 <connector-ref connector-name="in-vm"/>
 </connectors>
 <entries>
 <entry name="java:/ConnectionFactory"/>
 </entries>
 <client-failure-check-period>2147483646</client-failure-check-period>
 <connection-ttl>-1</connection-ttl>
 <reconnect-attempts>-1</reconnect-attempts>
 </connection-factory>
 <connection-factory name="RemoteConnectionFactory">
 <connectors>
 <connector-ref connector-name="netty"/>
 </connectors>
 <entries>
 <entry name="RemoteConnectionFactory"/>
 <entry name="java:jboss/exported/jms/RemoteConnectionFactory"/>
 </entries>
 <ha>true</ha>
 <block-on-acknowledge>true</block-on-acknowledge>
 <retry-interval>1000</retry-interval>
 <retry-interval-multiplier>1.0</retry-interval-multiplier>
 <reconnect-attempts>-1</reconnect-attempts>
 </connection-factory>
 <pooled-connection-factory name="hornetq-ra">
 <transaction mode="xa"/>
 <connectors>
 <connector-ref connector-name="in-vm"/>
 </connectors>
 <entries>
 <entry name="java:/JmsXA"/>
 </entries>
 <client-failure-check-period>2147483646</client-failure-check-period>
 <connection-ttl>-1</connection-ttl>
 <reconnect-attempts>-1</reconnect-attempts>
 </pooled-connection-factory>
 </jms-connection-factories>

 <jms-destinations>
 <jms-queue name="testQueue">
 <entry name="queue/TestQueue"/>
 <entry name="java:jboss/exported/jms/queues/TestQueue"/>
 </jms-queue>
 <jms-topic name="testTopic">
 <entry name="topic/MyTopic"/>
 </jms-topic>
 </jms-destinations>
 </hornetq-server>
</subsystem>
  • Aggiungere i seguenti messaging port nel tag <socket-binding-group>:
<socket-binding name="messaging" port="5445"/>
<socket-binding name="messaging-throughput" port="5455"/>
  • Immediatamente sotto il tag subsystem <subsystem xmlns=”urn:jboss:domain:ejb3:1.4″> (e prima del tag session-bean) aggiungere quanto segue:
<mdb>
    <resource-adapter-ref resource-adapter-name="hornetq-ra"/>
     <bean-instance-pool-ref pool-name="mdb-strict-max-pool"/>
</mdb>
  • Sempre in <subsystem xmlns=”urn:jboss:domain:ejb3:1.4″>, controllare che in pools ci sia quanto sottolineato di seguito (altrimenti aggiungerlo):
<pools>
    <bean-instance-pools>
          <strict-max-pool name="slsb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
          <strict-max-pool name="mdb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
     </bean-instance-pools>
</pools>

NOTA 1: la porta mappata nel socket-binding (nell’esempio , la porta 5445) non deve avere restrizioni di rete.

NOTA 2. Per evitare la seguente eccezione:

11:56:54,753 WARN  [org.hornetq.jms.server] (ServerService Thread Pool — 52) HQ122017: Tried to correct invalid “host” value “0.0.0.0” for “netty” connector, but received an exception.: java.net.UnknownHostException: XXXXXXX: XXXXXXXX

Occorre mappare sul file etc/hosts la seguente regola:

0.0.0.0  HOSTNAME

Dove HOSTNAME è il nome macchina (valore restituito lanciando il comando hostname da terminale).

TEST: per verificare la correttezza della configurazione delle code JMS, basta restartare il server e verificare che non ci sia nessuna eccezione sul log server.log.

 

Configurazione delle code JMS in ambiente clusterizzato (Clustering HornetQ)

hornetq-ha-cluster

Aggiungere alla configurazione precedente quanto segue (solo in ambiente clusterizzato):

<server name="xyz" xmlns="urn:jboss:domain:1.1">
  <profile>
    ...
    <subsystem xmlns="urn:jboss:domain:messaging:1.1">
      <hornetq-server>
        ...
        <connectors>
          <netty-connector name="netty" socket-binding="messaging"/>
          ...
          <netty-connector name="server2-connector" socket-binding="messaging-server2"/>
          <netty-connector name="server3-connector" socket-binding="messaging-server3"/>
        </connectors>
        ...
        <cluster-connections>
          <cluster-connection name="default-cluster-connection">
            <address>
              jms
            </address>
            <connector-ref>
              netty
            </connector-ref>
            <retry-interval>
              500
            </retry-interval>
            <forward-when-no-consumers>
              true
            </forward-when-no-consumers>
            <static-connectors>
              <connector-ref>
                server2-connector
              </connector-ref>
              <connector-ref>
                server3-connector
              </connector-ref>
            </static-connectors>
          </cluster-connection>
        </cluster-connections>
        ...
      </hornetq-server>
    </subsystem>
    ...
  </profile>
  <socket-binding-group name="standard-sockets" default-interface="public">
    ...
    <socket-binding name="messaging" port="5445"/>
      ...
      <outbound-socket-binding name="messaging-server2">
        <remote-destination host=”#Indirizzo_Cluster_1” port="5445"/>
      </outbound-socket-binding>
      <outbound-socket-binding name="messaging-server3">
        <remote-destination host=”#Indirizzo_Cluster_2” port="5445"/>
      </outbound-socket-binding>
  </socket-binding-group>
</server>

NOTA. Sostituire gli indirizzi IP in remote-destination (in outbound-socket-binding) con quello dei nodi cluster JBoss (così come su evidenziato e commentato).

Riferimento: Clustering HornetQ – Red Hat Documentation