Saturday, September 22, 2018

Complete guide for JBoss EAP6 / AS7 remote EJB

Basically, there are differences between standalone client or calling from JBoss when making remote EJB calls.
and both of them have recommended/preferred options and not recommended options.
details please refer article below.


EAP 6 still uses JNDI and InitialContext to look up an EJB client proxy, but many things have changed between EAP 5 and 6 regarding how the EJB client operates and how to look up and configure the EJB proxy.
Java EE 6 introduced a new standard for looking up EJBs in JNDI. Please read this document for details. The examples in this document utilize the new format.
You can choose to use either the ejb: or remote: protocol when looking up your EJB client using your InitialContext, but it's recommended that you use ejb: if at all possible. The use of ejb: takes advantage of EJB client facilities that better manage the remoting connection lifecycle.
Also note that the ejb-client (ejb:) method of invoking EJBs is not using remote JNDI calls.

Standalone client (client is not running inside of JBoss EAP 6)

Logging Notes

The JBoss EJB client stack uses JBoss Logging as its logging facade 1. There are several choices for a logging backend (in order of preference):
  • JBoss LogManager
  • Log4J
  • SLF4J
  • java.util.logging (JUL), i.e. JDK logging
JBoss LogManager is the fully supported and recommended option, and its classes are already included in jboss-client.jar. To enable this option, though, you need to add the following system property: -Djava.util.logging.manager=org.jboss.logmanager.LogManager. JBoss LogManager needs a logging.properties for its configuration. See the attached standalone-client-logging-config-example.zip which contains an example logging configuration for JBoss LogManager, or use the logging.properties found in the EAP installation as a starting point.
You can use Log4J or SLF4J by including the respective libraries in your client's classpath and optionally configuring the backend logging framework in your code. Make sure to not set the java.util.logging.manager system property if you want to use Log4J or SLF4J
If the java.util.logging.manager system property is not set and neither Log4J nor SLF4J are provided on the client's classpath, then JBoss Logging will fall back to JDK logging. Its default configuration is to print basic messages to the console only, though a logging.properties can be used to configure JDK logging, if desired.

Option 1 - Using jboss-ejb-client.properties (Recommended)

Use jboss-ejb-client.properties and place it on the clients classpath, then use the code below to lookup EJBs:
  • jboss-ejb-client.properties
            endpoint.name=client-endpoint
            remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
            remote.connections=node1
            remote.connection.node1.host=192.168.1.105
            remote.connection.node1.port = 4447
            remote.connection.node1.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
            remote.connection.node1.username=appuser
            remote.connection.node1.password=apppassword
  • Note that if EJB applications are used on multiple servers (in non-clustered EJB case) then for 3-server case remote.connections=node1 would be changed to remote.connections=node1,node2,node3 and host, port, username, password, connect.options, etc. above would contain entries for each of node1,node2, node3.
  • Example client code to lookup EJB
            final Hashtable jndiProperties = new Hashtable();
            jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
            Context context = new InitialContext(jndiProperties);
            context.lookup("ejb:TestTimer/TestTimerEJB/TimerExampleBean!org.example.jboss.timer.TimerExample");
In this code we are creating a JNDI InitialContext object by passing it a JNDI property. The Context.URL_PKG_PREFIXES is set to org.jboss.ejb.client.naming. This is necessary because we should let the JNDI API know what handles the ejb: namespace that is used in the JNDI names for lookup. The "org.jboss.ejb.client.naming" has a URLContextFactory implementation which will be used by the JNDI APIs to parse and return an object for ejb: namespace lookups.

Option 2 - Using JBoss EJB Client API to programatically configure (Recommended)

Use the following code snippet to programmatically configure it, but note it requires org.jboss.* classes which are included in jboss-client.jar, found in $EAP_HOME/bin/client/ .
Notes:
  • This configuration defines the configuration for the JVM (assuming the jboss-client classes are only in one classloader). If there are multiple classloaders containing jboss-client classes and this configuration method is used, then the remote connection used would depend on which classloader the client code sees.
  • The initialization must not be used more than once, if initialisized the connection is kept until a new one.
    If the initialization is done with a high frequency (i.e. each invocation) this will cause resource problems or throw Exceptions
  • The invocation of EJB's should use an existing and cached proxy for it in to get the full power of a cluster if the EJB is deployed in a clustered environment.
    In any case it is a good practice to keep the proxy to save resources and prevent from the initialization.
import org.jboss.ejb.client.EJBClientConfiguration;
import org.jboss.ejb.client.PropertiesBasedEJBClientConfiguration;
import org.jboss.ejb.client.ContextSelector;
import org.jboss.ejb.client.EJBClientContext;
import org.jboss.ejb.client.remoting.ConfigBasedEJBClientContextSelector;

    public void initEJBClient() {
            Properties p = new Properties();
            p.put("remote.connections", "node1");
            p.put("remote.connection.node1.port", "4447");  // the default remoting port, replace if necessary
            p.put("remote.connection.node1.host", "localhost");  // the host, replace if necessary
            p.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false"); // the server defaults to SSL_ENABLED=false
            ...
            EJBClientConfiguration cc = new PropertiesBasedEJBClientConfiguration(p);
            ContextSelector<EJBClientContext> selector = new ConfigBasedEJBClientContextSelector(cc);
            EJBClientContext.setSelector(selector);
    }

    Hello helloProxy;
    public void setProxy() {
            Properties contextProperties = new Properties();
            contextProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
            Context context = new InitialContext(contextProperties);
            helloProxy = (Hello) context.lookup("ejb:/sample/UnsecureHelloBean!sample.Hello");
    }

    public void myBusinessInvocation() {
        ...
        System.out.println(helloProxy.hello());
        ...
    }

   public void cleanupEJBClient() {
        EJBClientContext.getCurrent().close();
   }
  • Note that if EJB applications are used on multiple servers (in non-clustered EJB case) then for 3-server case p.put("remote.connections", "node1"); would be changed to p.put("remote.connections", "node1,node2,node3"); and host, port, username, password, connect.options, etc. above would contain entries for each of node1,node2, node3.
  • Note that a call to EJBClientContext.getCurrent().close() should be made after finishing with the EJB invocations.

Option 3 - Configuring using scoped context in the client code EAP 6.1+ ejb: protocol

These options are available only for >= EAP 6.1 and take advantage of EJBCLIENT-34. This allows you to use NO JBoss classes, no properties file and configure everything in the InitialContext properties.
Note: ejb-client scoped context requires more resources as the client application is creating connections and managing them. It also has other limitations. For the best performance, it is recommended to NOT use scoped context. Applications that require a large number of concurrent scoped context calls are recommended to NOT use scoped context if possible due to the resources required. Specifying clustering options is not really necessary when using scoped context, as specifying multiple remote connections effectively simulates clustering HA & load balancing, and the scoped context will be closed after the call, so setting up clustering connections does not provide a benefit.
  • Configure all options in the Properties map passed into your InitialContext constructor, with the scoped context option, basically making the EJB client use only the host name defined in the Properties map, rather than determining the EJB receiver to use based on the client application configuration.
Note: node1 used in the properties below is arbitrary, it is just a name to group the properties (host, port, username, password, ...) for a given connection configuration together. remote.connections is a comma delimited string that specifies the connection configurations to use.
Properties p = new Properties();
p.put("remote.connections", "node1");
p.put("remote.connection.node1.port", "4447");  // the default remoting port, replace if necessary
p.put("remote.connection.node1.host", "localhost");  // the host, replace if necessary
p.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false"); // the server defaults to SSL_ENABLED=false
p.put("remote.connection.node1.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");

//these 2 lines below are not necessary, if security-realm is removed from remoting-connector
p.put("remote.connection.node1.username", "user");
p.put("remote.connection.node1.password", "password");

...
p.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
p.put("org.jboss.ejb.client.scoped.context", true); // enable scoping here

Context context = new InitialContext(p);
Context ejbRootNamingContext = (Context) context.lookup("ejb:");
try {
   final TimerExample bean = (TimerExample) ejbRootNamingContext.lookup("TestTimer/TestTimerEJB/TimerExampleBean!org.example.jboss.timer.TimerExample");
   bean.doSomething();
} finally {
   try { 
       ejbRootNamingContext.close();
   } catch(Exception e) { }
   try { 
       context.close();
   } catch(Exception e) { }
}
  • Note that if EJB applications are used on multiple servers (in non-clustered EJB case) then for 3-server case p.put("remote.connections", "node1"); would be changed to p.put("remote.connections", "node1,node2,node3"); and host, port, username, password, connect.options, etc. above would contain entries for each of node1,node2, node3.
    • If your security-realm uses <ldap/> or <jaas/>, e.g.:
<security-realm name="ejbSecurityRealm">
    <authentication>
      <jaas name="ejbSecurityDomain"/>
    </authentication>
</security-realm>
add the following line in the client code:
prop.put("remote.connection.node1.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT", "false");

Option 4 - Configuring using remote: protocol in the client code

  • This is deprecated and not recommended for looking up EJBs.
  • Make sure you hold a reference to the InitialContext as long as the EJB proxy is being used. 2
  • Make sure you close the context when done.
  • Does not support clustered EJB.
  • Does not work well under high load
  • Using Remote Naming for EJB calls is NOT recommended if possible, given it requires more resources / connections.
final Hashtable jndiProperties = new Hashtable();
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
jndiProperties.put("jboss.naming.client.ejb.context", true);
jndiProperties.put("java.naming.factory.initial", "org.jboss.naming.remote.client.InitialContextFactory");
jndiProperties.put("java.naming.provider.url", "remote://localhost:4447");
jndiProperties.put(Context.SECURITY_PRINCIPAL,"user");  // add this user as ApplicationUser via script add-user.sh
jndiProperties.put(Context.SECURITY_CREDENTIALS, "password");

Context context = new InitialContext(jndiProperties);
TimerExample bean = context.lookup("TestTimer/TestTimerEJB/TimerExampleBean!org.example.jboss.timer.TimerExample");
// make ejb calls
// close the context when done calling the ejb to close the remote connection
try {
    if(context != null)
        context.close();
} catch(Exception e) {
}

Client in EAP 6 calling an EJB remote interface on EAP 6

Option 1 - Configuring remote servers in the JBoss profile (Recommended)

All of the following pieces are required to correctly configure EJB clients from within the JBoss container.
  • Client application configuration
    • jboss-ejb-client.xml goes in the WEB-INF if the top level deployment is a war or in the top level deployment's META-INF if not a war
      <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>
      
  • Client application code
    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);
    
    final Greeter bean = (Greeter) context.lookup("ejb:myapp/myejb/GreeterBean!" + org.myapp.ejb.Greeter.class.getName());
    
  • JBoss Profile configuration: This is configuration that goes in your standalone.xml or domain.xml.
  1. Create a security realm on the client server (ie. the base64 decoded password() created on server side using "./add-user.sh")
Standalone Mode
/core-service=management/security-realm=ejb-security-realm:add()
/core-service=management/security-realm=ejb-security-realm/server-identity=secret:add(value="cmVkaGF0MSE=")
Domain Mode (host master used in this example)
/host=master/core-service=management/security-realm=ejb-security-realm:add()
/host=master/core-service=management/security-realm=ejb-security-realm/server-identity=secret:add(value="cmVkaGF0MSE=")
The xml added looks like:
<management>
  <security-realms>
    ...
    <security-realm name="ejb-security-realm">
      <server-identities>
        <secret value="cmVkaGF0MSE="/>
      </server-identities>
    </security-realm>
    ...
  </security-realms>
  ...
</management>
2.Create a outbound-socket-binding on the "Client Server"
Standalone Mode
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-socket-ejb:add(host=localhost, port=4447)
Domain Mode
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-socket-ejb:add(host=server, port=4447)
The xml added looks like:
            <socket-binding-group name="standard-sockets" 
                                  default-interface="public" 
                                  port-offset="${jboss.socket.binding.port-offset:0}">
                    ...
              <outbound-socket-binding name="remote-socket-ejb">
                <remote-destination host="localhost" port="4447"/>
              </outbound-socket-binding>
            </socket-binding-group>
3.Create a "remote-outbound-connection" which uses this newly created "outbound-socket-binding"
Standalone Mode
/subsystem=remoting/remote-outbound-connection=remote-ejb-connection:add(outbound-socket-binding-ref=remote-socket-ejb,security-realm=ejb-security-realm,username=ejb)
/subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=SASL_POLICY_NOANONYMOUS:add(value=false)
/subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=SSL_ENABLED:add(value=false)
Domain Mode (default profile used in this example)
/profile=default/subsystem=remoting/remote-outbound-connection=remote-ejb-connection:add(outbound-socket-binding-ref=remote-socket-ejb,security-realm=ejb-security-realm,username=ejb)
/profile=default/subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=SASL_POLICY_NOANONYMOUS:add(value=false)
/profile=default/subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=SSL_ENABLED:add(value=false)

/profile=default/subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=org.jboss.remoting3.RemotingOptions.HEARTBEAT_INTERVAL:add(value=50000)
/profile=default/subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=KEEP_ALIVE:add(value=true)
/profile=default/subsystem=remoting/remote-outbound-connection=remote-ejb-connection/property=READ_TIMEOUT:add(value=60000)
The xml added looks like:
            <subsystem xmlns="urn:jboss:domain:remoting:1.1">
            ....
              <outbound-connections>
                <remote-outbound-connection name="remote-ejb-connection" 
                                            outbound-socket-binding-ref="remote-socket-ejb" 
                                            security-realm="ejb-security-realm" 
                                            username="ejb">
                  <properties>
                    <property name="SASL_POLICY_NOANONYMOUS" value="false"/>
                    <property name="SSL_ENABLED" value="false"/>
                    <property name="org.jboss.remoting3.RemotingOptions.HEARTBEAT_INTERVAL" value="50000"/>
                    <property name="KEEP_ALIVE" value="true"/>
                    <property name="READ_TIMEOUT" value="60000"/>
                  </properties>
                </remote-outbound-connection>
              </outbound-connections>
            </subsystem>

Option 2 - Configuring using scoped context in the client code

This option is available only for >= EAP 6.1 and takes advantage of EJBCLIENT-34. This allows you to use NO JBoss classes, no properties file and configure everything in the InitialContext properties.
  • Configure all options in the Properties map passed into your InitialContext constructor, with the scoped context option, basically making the EJB client use only the host name defined in the Properties map, rather than determining the EJB receiver to use based on the client application configuration.
Note: ejb-client scoped context requires more resources as the client application is creating connections and managing them. It also has other limitations. For the best performance, it is recommended to NOT use scoped context. Applications that require a large number of concurrent scoped context calls are recommended to NOT use scoped context if possible due to the resources required. Specifying clustering options is not really necessary when using scoped context, as specifying multiple remote connections effectively simulates clustering HA & load balancing, and the scoped context will be closed after the call, so setting up clustering connections does not provide a benefit.
The approach will not work if CMT is used and the transaction span both EJB's, in this case a commit/rollback will fail if the scoped-context is closed inside of the invoking method context!
  • Note a module dependency on the module org.jboss.xnio to be specified in the applications jboss-deployment-structure.xml or MANIFEST.MF Dependencies so that the application can specify the xnio options below.
Properties p = new Properties();
p.put("remote.connections", "node1");
p.put("remote.connection.node1.port", "4447");  // the default remoting port, replace if necessary
p.put("remote.connection.node1.host", "localhost");  // the host, replace if necessary
p.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false"); // the server defaults to SSL_ENABLED=false

//these 3 lines below are not necessary, if security-realm is removed from remoting-connector
p.put("remote.connection.node1.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");
p.put("remote.connection.node1.username", "user");
p.put("remote.connection.node1.password", "password");

...
// if the ejb is clustered, add the following lines...otherwise the client will log an 
// exception (JBREM000200) even though the remote ejb call completes successfully
p.put("remote.clusters", "ejb");
p.put("remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false"); 
p.put("remote.cluster.ejb.username", "user");
p.put("remote.cluster.ejb.password", "password");

...
p.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
p.put("org.jboss.ejb.client.scoped.context", true); // enable scoping here

Context context = new InitialContext(p);
Context ejbRootNamingContext = (Context) context.lookup("ejb:");
try {
   final TimerExample bean = ejbRootNamingContext.lookup("TestTimer/TestTimerEJB/TimerExampleBean!org.example.jboss.timer.TimerExample");
   bean.doSomething();
} finally {
   try { 
       ejbRootNamingContext.close();
   } catch(Exception e) { }
   try { 
       context.close();
   } catch(Exception e) { }
}
Specify a module dependency on org.jboss.xnio if org.xnio.Options are specified in the scoped context configuration properties such as:
<jboss-deployment-structure>
  <deployment>
    <dependencies>
      <module name="org.jboss.xnio" export="true" />    
    </dependencies>
  </deployment>
</jboss-deployment-structure>

Option 3 - Configuring using remote: protocol in the client code

  • This is deprecated and not recommended for looking up EJBs.
  • Make sure you hold a reference to the InitialContext as long as the EJB proxy is being used. [^4]
  • Make sure you close the context when done.
  • Does not support clustered EJB.
  • Using Remote Naming for EJB calls is NOT recommended if possible, given it requires more resources / connections.
Warning: The approach will not work if CMT is used and the transaction span both EJB's, in this case a commit/rollback will fail if the initialContext is closed inside of the invoking method context!
final Hashtable jndiProperties = new Hashtable();
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
jndiProperties.put("jboss.naming.client.ejb.context", true);
jndiProperties.put("java.naming.factory.initial", "org.jboss.naming.remote.client.InitialContextFactory");
jndiProperties.put("java.naming.provider.url", "remote://localhost:4447");
jndiProperties.put(Context.SECURITY_PRINCIPAL,"user");  // add this user as ApplicationUser via script add-user.sh
jndiProperties.put(Context.SECURITY_CREDENTIALS, "password");

Context context = new InitialContext(jndiProperties);
TimerExample bean = context.lookup("TestTimer/TestTimerEJB/TimerExampleBean!org.example.jboss.timer.TimerExample");
// make ejb calls
// close the context when done calling the ejb to close the remote connection
try {
    if(context != null)
        context.close();
} catch(Exception e) {
}

Additional Notes

If there is a firewall, or other network devices, between your client and server, it may be possible to configure the closing of idle connections after some timeout.
If this is possible in your setup, it is a good idea to configure a heartbeat, so that this does not cause the connection to be closed. See "EJB invocation gets failed with error 'Connection reset by peer" for details.

No comments:

Post a Comment

LinkWithin

Related Posts Plugin for WordPress, Blogger...