org.jboss.ejb.client.ClusterContext Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.client;
import org.jboss.logging.Logger;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* A {@link ClusterContext} keeps track of a specific cluster and the {@link org.jboss.ejb.client.remoting.ClusterNode}s
* in that cluster. A {@link ClusterContext} is always associated with a {@link EJBClientContext}
*
* @author Jaikiran Pai
*/
@Deprecated // make non-public
public final class ClusterContext implements EJBClientContext.EJBReceiverContextCloseHandler {
private static final Logger logger = Logger.getLogger(ClusterContext.class);
private static final ExecutorService executorService = Executors.newCachedThreadPool(new DaemonThreadFactory("ejb-client-cluster-node-connection-creation"));
private final String clusterName;
private final EJBClientContext clientContext;
private final ConcurrentMap nodeManagers = new ConcurrentHashMap();
// default to 10, will (optionally) be overridden by the ClusterConfiguration
private long maxClusterNodeOpenConnections = 10;
// default to RandomClusterNodeSelector
private ClusterNodeSelector clusterNodeSelector = new RandomClusterNodeSelector();
private final Set clusterContextListeners = new HashSet();
/**
* The nodes to which a connected has already been established in this cluster context
*/
private final Set connectedNodes = Collections.synchronizedSet(new HashSet());
ClusterContext(final String clusterName, final EJBClientContext clientContext, final EJBClientConfiguration ejbClientConfiguration) {
this.clusterName = clusterName;
this.clientContext = clientContext;
if (ejbClientConfiguration != null && ejbClientConfiguration.getClusterConfiguration(this.clusterName) != null) {
final EJBClientConfiguration.ClusterConfiguration clusterConfiguration = ejbClientConfiguration.getClusterConfiguration(this.clusterName);
this.setupClusterSpecificConfigurations(clusterConfiguration);
} else {
this.maxClusterNodeOpenConnections = 10; // default to 10
}
}
public String getClusterName() {
return this.clusterName;
}
public EJBClientContext getEJBClientContext() {
return this.clientContext;
}
/**
* Returns a {@link EJBReceiverContext} from among the receiver contexts that are available in this cluster.
* Returns null if there is no such receiver context available.
*
* @return
*/
EJBReceiverContext getEJBReceiverContext(final EJBClientInvocationContext invocationContext) {
final Set excludedNodes = invocationContext == null ? new HashSet() : new HashSet(invocationContext.getExcludedNodes());
return this.getEJBReceiverContext(invocationContext, excludedNodes);
}
/**
* Returns a {@link EJBReceiverContext} from among the receiver contexts that are available in this cluster. The
* node names in excludedNodes
will not be considered while selecting a receiver from this
* cluster context.
*
* Returns null if there is no such receiver context available.
*
* @param excludedNodes The node names that should be excluded while choosing a receiver from the cluster context
* @return
*/
private EJBReceiverContext getEJBReceiverContext(final EJBClientInvocationContext invocationContext, final Set excludedNodes) {
final EJBLocator> ejbLocator = invocationContext.getLocator();
if (nodeManagers.isEmpty()) {
return null;
}
final Set availableNodes = new HashSet(this.nodeManagers.keySet());
// remove the excluded nodes
availableNodes.removeAll(excludedNodes);
if (availableNodes.isEmpty()) {
logger.debugf("No nodes available in cluster %s for selecting a receiver context", this.clusterName);
return null;
}
// remove the excluded nodes
connectedNodes.removeAll(excludedNodes);
// only propose connected nodes which also satisfy the locator
final Set connectedDeployedNodes = getConnectedAndDeployedNodes(ejbLocator);
// Let the cluster node selector decide which node to use from among the available nodes
final String selectedNodeName = this.clusterNodeSelector.selectNode(this.clusterName, connectedDeployedNodes.toArray(new String[connectedDeployedNodes.size()]), availableNodes.toArray(new String[availableNodes.size()]));
// only use the selected node name, if it's valid
if (selectedNodeName == null || selectedNodeName.trim().isEmpty()) {
logger.warn(clusterNodeSelector + " selected an invalid node name: " + selectedNodeName + " for cluster: "
+ clusterName + ". No EJB receiver context can be selected");
return null;
}
logger.debugf("%s has selected node %s, in cluster %s", this.clusterNodeSelector, selectedNodeName, this.clusterName);
// add this selected node name to excluded set, so that we don't try fetching a receiver for it
// again
excludedNodes.add(selectedNodeName);
final ClusterNodeManager clusterNodeManager = this.nodeManagers.get(selectedNodeName);
if (clusterNodeManager == null) {
logger.debugf("No node manager available for node: %s in cluster: %s", selectedNodeName, clusterName);
// See if the selector selected a node which got removed while the selection was happening.
// If so, then try some other node (if any) in the cluster
if (availableNodes.contains(selectedNodeName) || connectedNodes.contains(selectedNodeName)) {
// this means that the node was valid when the selection was happening, but was probably
// removed from the cluster before we could fetch a node manager for it
// let's try a different node, this current one will be excluded
final Set nodesInThisCluster = this.nodeManagers.keySet();
// if all nodes have been excluded/tried, just return null indicating no receiver is available
if (excludedNodes.containsAll(nodesInThisCluster)) {
logger.debugf("All nodes have been tried for a receiver, in cluster %s. No suitable receiver found", clusterName);
return null;
} else {
// explicit isDebugEnabled() check to prevent the Arrays conversion in the log message, when
// debug isn't enabled
if (logger.isDebugEnabled()) {
logger.debug("Retrying receiver selection in cluster " + clusterName + " with excluded nodes " + Arrays.toString(excludedNodes.toArray()));
}
// try a different node
return this.getEJBReceiverContext(invocationContext, excludedNodes);
}
}
logger.debugf("Node selector returned a non-existent %s for cluster: %s. No EJB receiver context can be selected", selectedNodeName, clusterName);
return null;
}
final EJBReceiverContext selectedNodeReceiverContext = this.clientContext.getNodeEJBReceiverContext(selectedNodeName);
// the node is already connected, so return it
if (selectedNodeReceiverContext != null) {
if (selectedNodeReceiverContext.getReceiver().acceptsModule(ejbLocator.getAppName(), ejbLocator.getModuleName(), ejbLocator.getDistinctName())) {
return selectedNodeReceiverContext;
} else {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring node " + selectedNodeName + " since it cannot handle appName=" + ejbLocator.getAppName()
+ ",moduleName=" + ejbLocator.getModuleName() + ",distinct-name=" + ejbLocator.getDistinctName());
}
}
}
// get the receiver from the node manager
final EJBReceiver ejbReceiver = clusterNodeManager.getEJBReceiver();
if (ejbReceiver != null) {
// register the receiver and let it create the receiver context
this.registerEJBReceiver(ejbReceiver);
// let the client context return the newly associated receiver context for the node name.
// if it wasn't successfully associated (for example version handshake not completing)
// then this will return null.
final EJBReceiverContext ejbReceiverContext = this.clientContext.getNodeEJBReceiverContext(selectedNodeName);
if (ejbReceiverContext != null) {
if (ejbReceiverContext.getReceiver().acceptsModule(ejbLocator.getAppName(), ejbLocator.getModuleName(), ejbLocator.getDistinctName())) {
return ejbReceiverContext;
} else {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring node " + selectedNodeName + " since it cannot handle appName=" + ejbLocator.getAppName()
+ ",moduleName=" + ejbLocator.getModuleName() + ",distinct-name=" + ejbLocator.getDistinctName());
}
}
}
}
// try some other node (if any) in this cluster. The currently selected node is
// excluded from this next attempt
final Set nodesInThisCluster = this.nodeManagers.keySet();
// if all nodes have been excluded/tried, just return null indicating no receiver is available
if (excludedNodes.containsAll(nodesInThisCluster)) {
logger.debugf("All nodes have been tried for a receiver, in cluster %s. No suitable receiver found", clusterName);
return null;
} else {
// explicit isDebugEnabled() check to prevent the Arrays conversion in the log message, when
// debug isn't enabled
if (logger.isDebugEnabled()) {
logger.debug("Retrying receiver selection in cluster " + clusterName + " with excluded nodes " + Arrays.toString(excludedNodes.toArray()));
}
// try a different node
return this.getEJBReceiverContext(invocationContext, excludedNodes);
}
}
/**
* Returns true if the cluster managed by this {@link ClusterContext} contains a node named nodeName
.
* Else returns false
*
* @param nodeName The node name
* @return
*/
boolean isNodeAvailable(final String nodeName) {
if (nodeName == null) {
return false;
}
return this.nodeManagers.containsKey(nodeName);
}
/**
* Returns true if the cluster managed by this {@link ClusterContext} contains a node named nodeName
* which is already connected.
* Else returns false
*
* @param nodeName The node name
* @return
*/
boolean isNodeConnected(final String nodeName) {
if (nodeName == null) {
return false;
}
return this.connectedNodes.contains(nodeName);
}
/**
* Returns true if the cluster managed by this {@link ClusterContext} contains a node named nodeName
* which is already connected and satisfies the locator.
* Else returns false
*
* @param nodeName The node name
* @return
*/
boolean isNodeConnectedAndDeployed(final String nodeName, EJBLocator locator) {
if (nodeName == null) {
return false;
}
EJBReceiverContext receiverContext = clientContext.getNodeEJBReceiverContext(nodeName);
if (receiverContext == null) {
return false;
} else {
if (receiverContext.getReceiver().acceptsModule(locator.getAppName(), locator.getModuleName(), locator.getDistinctName())) {
return true;
}
}
return false;
}
public Set getConnectedAndDeployedNodes(EJBLocator locator) {
Set connectedAndDeployed = Collections.synchronizedSet(new HashSet());
synchronized (this) {
for (String node : this.connectedNodes) {
if (isNodeConnectedAndDeployed(node, locator)) {
connectedAndDeployed.add(node);
}
}
}
return connectedAndDeployed;
}
/**
* Adds a cluster node and the {@link ClusterNodeManager} associated with that node, to this cluster context
*
* @param nodeName The cluster node name
* @param clusterNodeManager The cluster node manager for that node
* @deprecated Since 1.0.6.Final. Use {@link #addClusterNodes(ClusterNodeManager...)} instead
*/
public void addClusterNode(final String nodeName, final ClusterNodeManager clusterNodeManager) {
this.addClusterNodes(clusterNodeManager);
}
/**
* Adds the cluster nodes managed by the {@link ClusterNodeManager clusterNodeManagers}, to this cluster context
*
* @param clusterNodeManagers The cluster node managers
*/
public void addClusterNodes(final ClusterNodeManager... clusterNodeManagers) {
if (clusterNodeManagers == null) {
return;
}
try {
final Set> futureAssociationResults = new HashSet>();
for (int i = 0; i < clusterNodeManagers.length; i++) {
final ClusterNodeManager clusterNodeManager = clusterNodeManagers[i];
if (clusterNodeManager == null) {
continue;
}
final String nodeName = clusterNodeManager.getNodeName();
if (nodeName == null || nodeName.trim().isEmpty()) {
throw Logs.MAIN.nodeNameCannotBeNullOrEmptyStringForCluster(this.clusterName);
}
// don't add a ClusterNodeManager for a node which is already managed
if (this.nodeManagers.putIfAbsent(nodeName, clusterNodeManager) != null)
continue;
// If the connected nodes in this cluster context hasn't yet reached the max allowed limit, then create a new
// receiver and associate it with a receiver context (if the node isn't already connected to)
if (!this.connectedNodes.contains(nodeName) && this.connectedNodes.size() < maxClusterNodeOpenConnections) {
// submit a task which will create and associate a EJB receiver with this cluster context
futureAssociationResults.add(executorService.submit(new EJBReceiverAssociationTask(this, nodeName)));
}
}
// wait for the associations to be completed so that the other threads which are expecting
// some associated nodes in this cluster context, don't run into a race condition
for (final Future futureAssociationResult : futureAssociationResults) {
try {
futureAssociationResult.get(5, TimeUnit.SECONDS);
} catch (Exception e) {
// ignore
}
}
} finally {
for (final ClusterContextListener listener : this.clusterContextListeners) {
try {
listener.clusterNodesAdded(this.clusterName, clusterNodeManagers);
} catch (Throwable t) {
// ignore any errors thrown by the listeners
logger.debugf(t, "Ignoring the exception thrown by listener %s", listener);
}
}
}
}
/**
* Removes a previously assoicated cluster node, if any, from this cluster context.
*
* @param nodeName The node name
*/
public void removeClusterNode(final String nodeName) {
// remove from the node managers
this.nodeManagers.remove(nodeName);
// remove any EJB receiver contexts for this node name
// Note, we do *not* close the associated EJBReceiverContext for the node name, from the
// EJB client context, because the node receiver might have been created outside of this
// cluster context (either auto-created or associated explicitly by the user code).
this.connectedNodes.remove(nodeName);
}
/**
* Removes all previously associated cluster node(s), if any, from this cluster context
*/
public void removeAllClusterNodes() {
this.nodeManagers.clear();
this.connectedNodes.clear();
}
/**
* Close this {@link ClusterContext}. This will do any necessary cleanup of the cluster related resources
* held by this manager. The {@link ClusterContext} will no longer be functional after it is closed and will
* behave like a cluster context which contains no nodes
*/
void close() {
this.removeAllClusterNodes();
}
/**
* Register a {@link EJBReceiver} with this cluster context
*
* @param receiver The EJB receiver for that node
*/
public void registerEJBReceiver(final EJBReceiver receiver) {
if (receiver == null) {
throw Logs.MAIN.paramCannotBeNull("EJB receiver");
}
final String nodeName = receiver.getNodeName();
if (this.connectedNodes.contains(nodeName)) {
// nothing to do
return;
}
// let the client context manage the registering
this.clientContext.registerEJBReceiver(receiver, this);
// now let's get back the receiver context that got associated, so that we too can keep a track of those
// receiver contexts
final EJBReceiverContext ejbReceiverContext = this.clientContext.getNodeEJBReceiverContext(nodeName);
// it's possible that while associating a receiver context to a receiver, there were problems (like
// version handshake not being completed) which might have resulted in the receiver context not being
// created for this node. So let's do a null check here
if (ejbReceiverContext != null) {
// add it to our connected nodes
this.connectedNodes.add(nodeName);
if (logger.isDebugEnabled()) {
logger.debug(this + " Added a new EJB receiver in cluster context " + clusterName + " for node " + nodeName
+ ". Total nodes in cluster context = " + this.connectedNodes.size());
}
}
}
private void setupClusterSpecificConfigurations(final EJBClientConfiguration.ClusterConfiguration clusterConfiguration) {
final long maxLimit = clusterConfiguration.getMaximumAllowedConnectedNodes();
// don't use 0 or negative values
if (maxLimit > 0) {
this.maxClusterNodeOpenConnections = maxLimit;
}
final ClusterNodeSelector nodeSelector = clusterConfiguration.getClusterNodeSelector();
if (nodeSelector != null) {
this.clusterNodeSelector = nodeSelector;
}
}
@Override
public void receiverContextClosed(EJBReceiverContext receiverContext) {
final String nodeName = receiverContext.getReceiver().getNodeName();
this.connectedNodes.remove(nodeName);
logger.debugf("Node %s removed from cluster context %s for cluster %s", nodeName, this, this.clusterName);
}
/**
* Registers a {@link ClusterContextListener} to this {@link ClusterContext}
*
* @param listener The cluster context listener
*/
void registerListener(final ClusterContextListener listener) {
if (listener == null) {
return;
}
this.clusterContextListeners.add(listener);
}
/**
* A {@link EJBReceiverAssociationTask} creates and associates a {@link EJBReceiver}
* with a {@link ClusterContext}
*/
private class EJBReceiverAssociationTask implements Callable {
private final ClusterContext clusterContext;
private final String nodeName;
/**
* @param clusterContext The cluster context
* @param nodeName The node name
*/
EJBReceiverAssociationTask(final ClusterContext clusterContext, final String nodeName) {
this.nodeName = nodeName;
this.clusterContext = clusterContext;
}
@Override
public Void call() throws Exception {
final ClusterNodeManager clusterNodeManager = this.clusterContext.nodeManagers.get(this.nodeName);
if (clusterNodeManager == null) {
// we don't have a cluster node manager which could create the EJB receiver, for this
// node name
logger.debugf("Cannot create EJBReceiver since no cluster node manager found for node %s in cluster context for cluster %s",
nodeName, clusterName);
return null;
}
// get the EJB receiver from the node manager
final EJBReceiver ejbReceiver = clusterNodeManager.getEJBReceiver();
if (ejbReceiver == null) {
return null;
}
// associate the receiver with the cluster context
this.clusterContext.registerEJBReceiver(ejbReceiver);
return null;
}
}
interface ClusterContextListener {
void clusterNodesAdded(final String clusterName, final ClusterNodeManager... nodes);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy