All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.ejb.client.ClusterContext Maven / Gradle / Ivy

/*
 * 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