8.7. Clustered Enterprise JavaBeans
8.7.1. About Clustered Enterprise JavaBeans (EJBs)
Note
8.7.2. Standalone and In-server Client Configuration
jboss-ejb-client.properties for standalone EJB client, or even jboss-ejb-client.xml file for a server-side application must be expanded to include a cluster configuration.
Note
in-server when the JVM doing the calling to the remote server is itself running inside of a server. In other words, an EAP instance calling out to another EAP instance would be considered an in-server client.
Example 8.4. Standalone client with jboss-ejb-client.properties configuration
remote.clusters=ejb remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false remote.cluster.ejb.connect.options.org.xnio.Options.SSL_ENABLED=false remote.cluster.ejb.username=test remote.cluster.ejb.password=password
jboss-ejb-client.xml file and add cluster configuration as shown in the following example:
Example 8.5. Client application which is deployed in another EAP 6 instance (Configuring jboss-ejb-client.xml file)
<jboss-ejb-client xmlns:xsi="urn:jboss:ejb-client:1.2" xsi:noNamespaceSchemaLocation="jboss-ejb-client_1_2.xsd">
<client-context>
<ejb-receivers>
<!-- this is the connection to access the app-one -->
<remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection-1" />
<!-- this is the connection to access the app-two -->
<remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection-2" />
</ejb-receivers>
<!-- if an outbound connection connects to a cluster; a list of members is provided after successful connection.
To connect to this node this cluster element must be defined. -->
<clusters>
<!-- cluster of remote-ejb-connection-1 -->
<cluster name="ejb" security-realm="ejb-security-realm-1" username="quickuser1">
<connection-creation-options>
<property name="org.xnio.Options.SSL_ENABLED" value="false" />
<property name="org.xnio.Options.SASL_POLICY_NOANONYMOUS" value="false" />
</connection-creation-options>
</cluster>
</clusters>
</client-context>
</jboss-ejb-client>
Note
8.7.3. Implementing a Custom Load Balancing Policy for EJB Calls
AllClusterNodeSelector for EJB calls. The node selection behavior of AllClusterNodeSelector is similar to default selector except that AllClusterNodeSelector uses all available cluster nodes even in case of a large cluster (number of nodes>20). If an unconnected cluster node is returned it is opened automatically. The following example shows AllClusterNodeSelector implementation:
package org.jboss.as.quickstarts.ejb.clients.selector;
import java.util.Arrays;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.ejb.client.ClusterNodeSelector;
public class AllClusterNodeSelector implements ClusterNodeSelector {
private static final Logger LOGGER = Logger.getLogger(AllClusterNodeSelector.class.getName());
@Override
public String selectNode(final String clusterName, final String[] connectedNodes, final String[] availableNodes) {
if(LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("INSTANCE "+this+ " : cluster:"+clusterName+" connected:"+Arrays.deepToString(connectedNodes)+" available:"+Arrays.deepToString(availableNodes));
}
if (availableNodes.length == 1) {
return availableNodes[0];
}
final Random random = new Random();
final int randomSelection = random.nextInt(availableNodes.length);
return availableNodes[randomSelection];
}
}
You can also implement the SimpleLoadFactorNodeSelector for EJB calls. Load balancing in SimpleLoadFactorNodeSelector happens based on a load factor. The load factor (2/3/4) is calculated based on the names of nodes (A/B/C) irrespective of the load on each node. The following example shows SimpleLoadFactorNodeSelector implementation:
package org.jboss.as.quickstarts.ejb.clients.selector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.ejb.client.DeploymentNodeSelector;
public class SimpleLoadFactorNodeSelector implements DeploymentNodeSelector {
private static final Logger LOGGER = Logger.getLogger(SimpleLoadFactorNodeSelector.class.getName());
private final Map<String, List<String>[]> nodes = new HashMap<String, List<String>[]>();
private final Map<String, Integer> cursor = new HashMap<String, Integer>();
private ArrayList<String> calculateNodes(Collection<String> eligibleNodes) {
ArrayList<String> nodeList = new ArrayList<String>();
for (String string : eligibleNodes) {
if(string.contains("A") || string.contains("2")) {
nodeList.add(string);
nodeList.add(string);
} else if(string.contains("B") || string.contains("3")) {
nodeList.add(string);
nodeList.add(string);
nodeList.add(string);
} else if(string.contains("C") || string.contains("4")) {
nodeList.add(string);
nodeList.add(string);
nodeList.add(string);
nodeList.add(string);
}
}
return nodeList;
}
@SuppressWarnings("unchecked")
private void checkNodeNames(String[] eligibleNodes, String key) {
if(!nodes.containsKey(key) || nodes.get(key)[0].size() != eligibleNodes.length || !nodes.get(key)[0].containsAll(Arrays.asList(eligibleNodes))) {
// must be synchronized as the client might call it concurrent
synchronized (nodes) {
if(!nodes.containsKey(key) || nodes.get(key)[0].size() != eligibleNodes.length || !nodes.get(key)[0].containsAll(Arrays.asList(eligibleNodes))) {
ArrayList<String> nodeList = new ArrayList<String>();
nodeList.addAll(Arrays.asList(eligibleNodes));
nodes.put(key, new List[] { nodeList, calculateNodes(nodeList) });
}
}
}
}
private synchronized String nextNode(String key) {
Integer c = cursor.get(key);
List<String> nodeList = nodes.get(key)[1];
if(c == null || c >= nodeList.size()) {
c = Integer.valueOf(0);
}
String node = nodeList.get(c);
cursor.put(key, Integer.valueOf(c + 1));
return node;
}
@Override
public String selectNode(String[] eligibleNodes, String appName, String moduleName, String distinctName) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("INSTANCE " + this + " : nodes:" + Arrays.deepToString(eligibleNodes) + " appName:" + appName + " moduleName:" + moduleName
+ " distinctName:" + distinctName);
}
// if there is only one there is no sense to choice
if (eligibleNodes.length == 1) {
return eligibleNodes[0];
}
final String key = appName + "|" + moduleName + "|" + distinctName;
checkNodeNames(eligibleNodes, key);
return nextNode(key);
}
}
jboss-ejb-client.properties
You need to add the property remote.cluster.ejb.clusternode.selector with the name of your implementation class (AllClusterNodeSelector or SimpleLoadFactorNodeSelector). The selector will see all configured servers which are available at the invocation time. The following example uses AllClusterNodeSelector as the deployment node selector:
remote.clusters=ejb remote.cluster.ejb.clusternode.selector=org.jboss.as.quickstarts.ejb.clients.selector.AllClusterNodeSelector remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false remote.cluster.ejb.connect.options.org.xnio.Options.SSL_ENABLED=false remote.cluster.ejb.username=test remote.cluster.ejb.password=password remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false remote.connections=one,two remote.connection.one.host=localhost remote.connection.one.port = 4447 remote.connection.one.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false remote.connection.one.username=user remote.connection.one.password=user123 remote.connection.two.host=localhost remote.connection.two.port = 4547 remote.connection.two.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
You need to add the property remote.cluster.ejb.clusternode.selector to the list for the PropertiesBasedEJBClientConfiguration constructor. The following example uses AllClusterNodeSelector as the deployment node selector:
Properties p = new Properties();
p.put("remote.clusters", "ejb");
p.put("remote.cluster.ejb.clusternode.selector", "org.jboss.as.quickstarts.ejb.clients.selector.AllClusterNodeSelector");
p.put("remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");
p.put("remote.cluster.ejb.connect.options.org.xnio.Options.SSL_ENABLED", "false");
p.put("remote.cluster.ejb.username", "test");
p.put("remote.cluster.ejb.password", "password");
p.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
p.put("remote.connections", "one,two");
p.put("remote.connection.one.port", "4447");
p.put("remote.connection.one.host", "localhost");
p.put("remote.connection.two.port", "4547");
p.put("remote.connection.two.host", "localhost");
EJBClientConfiguration cc = new PropertiesBasedEJBClientConfiguration(p);
ContextSelector<EJBClientContext> selector = new ConfigBasedEJBClientContextSelector(cc);
EJBClientContext.setSelector(selector);
p = new Properties();
p.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
InitialContext context = new InitialContext(p);
jboss-ejb-client.xml
To use the load balancing policy for server to server communication; package the class together with the application and configure it within the jboss-ejb-client.xml settings (located in META-INF folder). The following example uses AllClusterNodeSelector as the deployment node selector:
<jboss-ejb-client xmlns:xsi="urn:jboss:ejb-client:1.2" xsi:noNamespaceSchemaLocation="jboss-ejb-client_1_2.xsd">
<client-context deployment-node-selector="org.jboss.ejb.client.DeploymentNodeSelector">
<ejb-receivers>
<!-- this is the connection to access the app -->
<remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection-1" />
</ejb-receivers>
<!-- if an outbound connection connect to a cluster a list of members is provided after successful connection.
To connect to this node this cluster element must be defined.
-->
<clusters>
<!-- cluster of remote-ejb-connection-1 -->
<cluster name="ejb" security-realm="ejb-security-realm-1" username="test" cluster-node-selector="org.jboss.as.quickstarts.ejb.clients.selector.AllClusterNodeSelector">
<connection-creation-options>
<property name="org.xnio.Options.SSL_ENABLED" value="false" />
<property name="org.xnio.Options.SASL_POLICY_NOANONYMOUS" value="false" />
</connection-creation-options>
</cluster>
</clusters>
</client-context>
</jboss-ejb-client>
To use the above configuration with security, you will need to add ejb-security-realm-1 to client-server configuration. The following example shows the CLI commands for adding security realm (ejb-security-realm-1) the value is the base64 encoded password for the user "test":
core-service=management/security-realm=ejb-security-realm-1:add() core-service=management/security-realm=ejb-security-realm-1/server-identity=secret:add(value=cXVpY2sxMjMr)
Note
-Djboss.node.name= or the server configuration file standalone.xml to configure the server name (server name=""). Ensure that the server name is unique. In domain mode, the controller automatically validates that the names are unique.
8.7.4. Transaction Behavior of EJB Invocations
Transaction attributes for distributed JBoss EAP applications need to be handled in a way as if the application is called on the same server. To discontinue a transaction, the destination method must be marked REQUIRES_NEW using different interfaces.
Note
To invoke EJB session beans with a JBoss EAP 6 standalone client, the client must have a reference to the InitialContext object while the EJB proxies or UserTransaction are used. It is also important to keep the InitialContext object open while EJB proxies or UserTransaction are being used. Control of the connections will be inside the classes created by the InitialContext with the properties.
InitialContext object.
Example 8.6. EJB client API referencing InitialContext object
package org.jboss.as.quickstarts.ejb.multi.server;
import java.util.Date;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import org.jboss.as.quickstarts.ejb.multi.server.app.MainApp;
import org.jboss.ejb.client.ContextSelector;
import org.jboss.ejb.client.EJBClientConfiguration;
import org.jboss.ejb.client.EJBClientContext;
import org.jboss.ejb.client.PropertiesBasedEJBClientConfiguration;
import org.jboss.ejb.client.remoting.ConfigBasedEJBClientContextSelector;
public class Client {
/**
* @param args no args needed
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// suppress output of client messages
Logger.getLogger("org.jboss").setLevel(Level.OFF);
Logger.getLogger("org.xnio").setLevel(Level.OFF);
Properties p = new Properties();
p.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
p.put("remote.connections", "one");
p.put("remote.connection.one.port", "4447");
p.put("remote.connection.one.host", "localhost");
p.put("remote.connection.one.username", "quickuser");
p.put("remote.connection.one.password", "quick-123");
EJBClientConfiguration cc = new PropertiesBasedEJBClientConfiguration(p);
ContextSelector<EJBClientContext> selector = new ConfigBasedEJBClientContextSelector(cc);
EJBClientContext.setSelector(selector);
Properties props = new Properties();
props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
InitialContext context = new InitialContext(props);
final String rcal = "ejb:jboss-ejb-multi-server-app-main/ejb//" + ("MainAppBean") + "!" + MainApp.class.getName();
final MainApp remote = (MainApp) context.lookup(rcal);
final String result = remote.invokeAll("Client call at "+new Date());
System.out.println("InvokeAll succeed: "+result);
}
}
Note
UserTransaction reference on the client is unsupported for scenarios with a scoped EJB client context and for invocations which use the remote-naming protocol. This is because in these scenarios, InitialContext encapsulates its own EJB client context instance; which cannot be accessed using the static methods of the EJBClient class. When EJBClient.getUserTransaction() is called, it returns a transaction from default (global) EJB client context (which might not be initialized) and not from the desired one.
The following example shows how to get UserTransaction reference on a standalone client.
Example 8.7. Standalone client referencing UserTransaction object
import org.jboss.ejb.client.EJBClient;
import javax.transaction.UserTransaction;
.
.
Context context=null;
UserTransaction tx=null;
try {
Properties props = new Properties();
// REMEMBER: there must be a jboss-ejb-client.properties with the connection parameter
// in the clients classpath
props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
context = new InitialContext(props);
System.out.println("\n\tGot initial Context: "+context);
tx=EJBClient.getUserTransaction("yourServerName");
System.out.println("UserTransaction = "+tx.getStatus());
tx.begin();
// do some work
...
}catch (Exception e) {
e.printStackTrace();
tx.rollback();
}finally{
if(context != null) {
context.close();
}
}
Note
UserTransaction reference on the client side; start your server with the following system property -Djboss.node.name=yourServerName and then use it on client side as following:
tx=EJBClient.getUserTransaction("yourServerName");
Replace "yourServerName" with the name of your server. If a user transaction is started on a node all invocations are sticky on the node and the node must have all the needed EJBs. It is not possible to use UserTransaction with remote-naming protocol and scoped-context.

Where did the comment section go?
Red Hat's documentation publication system recently went through an upgrade to enable speedier, more mobile-friendly content. We decided to re-evaluate our commenting platform to ensure that it meets your expectations and serves as an optimal feedback mechanism. During this redesign, we invite your input on providing feedback on Red Hat documentation via the discussion platform.