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

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 EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS 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).

There is a newer version: 32.0.0.Final
Show newest version
/*
 * 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 - 2024 Weber Informatics LLC | Privacy Policy