Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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 java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jboss.ejb.client.remoting.ConfigBasedEJBClientContextSelector;
import org.jboss.ejb.client.remoting.ReconnectHandler;
import org.jboss.ejb.client.remoting.RemotingConnectionEJBReceiver;
import org.jboss.logging.Logger;
import org.jboss.remoting3.Connection;
/**
* The public API for an EJB client context. An EJB client context may be associated with (and used by) one or more threads concurrently.
*
* @author David M. Lloyd
*/
@SuppressWarnings({"UnnecessaryThis"})
public final class EJBClientContext extends Attachable implements Closeable {
private static final boolean WILDFLY_TESTSUITE_HACK = Boolean.getBoolean("org.jboss.ejb.client.wildfly-testsuite-hack");
private static final Logger logger = Logger.getLogger(EJBClientContext.class);
private static final RuntimePermission SET_SELECTOR_PERMISSION = new RuntimePermission("setEJBClientContextSelector");
private static final RuntimePermission ADD_INTERCEPTOR_PERMISSION = new RuntimePermission("registerInterceptor");
private static final RuntimePermission CREATE_CONTEXT_PERMISSION = new RuntimePermission("createEJBClientContext");
private static final AtomicReferenceFieldUpdater registrationsUpdater = AtomicReferenceFieldUpdater.newUpdater(EJBClientContext.class, EJBClientInterceptor.Registration[].class, "registrations");
private static final EJBClientInterceptor.Registration[] NO_INTERCEPTORS = new EJBClientInterceptor.Registration[0];
/**
* EJB client context selector. By default the {@link ConfigBasedEJBClientContextSelector} is used.
*/
private static volatile ContextSelector SELECTOR;
static {
final Properties ejbClientProperties = EJBClientPropertiesLoader.loadEJBClientProperties();
if (ejbClientProperties == null) {
SELECTOR = new ConfigBasedEJBClientContextSelector(null);
} else {
final EJBClientConfiguration clientConfiguration = new PropertiesBasedEJBClientConfiguration(ejbClientProperties);
SELECTOR = new ConfigBasedEJBClientContextSelector(clientConfiguration);
}
}
private static volatile boolean SELECTOR_LOCKED;
private final Map ejbReceiverAssociations = new IdentityHashMap();
private final Map receiverContextCloseHandlers = Collections.synchronizedMap(new IdentityHashMap());
private volatile EJBClientInterceptor.Registration[] registrations = NO_INTERCEPTORS;
private Set clientInterceptorsInClasspath;
/**
* Cluster contexts mapped against their cluster name
*/
private final Map clusterContexts = new HashMap();
private final EJBClientConfiguration ejbClientConfiguration;
private final ClusterFormationNotifier clusterFormationNotifier = new ClusterFormationNotifier();
private final DeploymentNodeSelector deploymentNodeSelector;
private final ExecutorService ejbClientContextTasksExecutorService = Executors.newCachedThreadPool(new DaemonThreadFactory("ejb-client-context-tasks"));
private final List reconnectHandlers = new ArrayList();
private final Collection ejbClientContextListeners = Collections.synchronizedSet(new HashSet());
private volatile boolean closed;
private final AtomicBoolean attemptingReconnect = new AtomicBoolean(false);
private final Lock reconnectCompletedLock = new ReentrantLock();
private final Condition reconnectCompletedCondition = reconnectCompletedLock.newCondition();
private EJBClientContext(final EJBClientConfiguration ejbClientConfiguration) {
this.ejbClientConfiguration = ejbClientConfiguration;
if (ejbClientConfiguration != null && ejbClientConfiguration.getDeploymentNodeSelector() != null) {
this.deploymentNodeSelector = ejbClientConfiguration.getDeploymentNodeSelector();
} else {
this.deploymentNodeSelector = new RandomDeploymentNodeSelector();
}
}
private void init(ClassLoader classLoader) {
if (classLoader == null) {
classLoader = EJBClientContext.class.getClassLoader();
}
for (final EJBClientContextInitializer contextInitializer : SecurityActions.loadService(EJBClientContextInitializer.class, classLoader)) {
try {
contextInitializer.initialize(this);
} catch (Throwable ignored) {
logger.debugf(ignored, "EJB client context initializer %s failed to initialize context %s", contextInitializer, this);
}
}
// TODO: Perhaps have system property which disables scanning the classpath for client interceptors?
// load any EJBClientInterceptor(s) from classpath using the ServiceLoader. These interceptors will be added to the end of the chain
try {
for (final EJBClientInterceptor interceptor : SecurityActions.loadService(EJBClientInterceptor.class, classLoader)) {
if (this.clientInterceptorsInClasspath == null) {
// we need to maintain order, so the LinkedHashSet
this.clientInterceptorsInClasspath = new LinkedHashSet();
}
this.clientInterceptorsInClasspath.add(interceptor);
}
} catch (Throwable t) {
// just log a message and don't cause the application to fail, perhaps due to a rogue library in the classpath
logger.debugf(t, "Failed to load EJB client interceptor(s) from the classpath via classloader %s for EJB client context %s", classLoader, this);
}
}
/**
* Creates and returns a new client context.
*
* @return the newly created context
*/
public static EJBClientContext create() {
return create(null, EJBClientContext.class.getClassLoader());
}
/**
* Creates and returns a new client context, using the given class loader to look for initializers.
*
* @param classLoader the class loader. Cannot be null
* @return the newly created context
*/
public static EJBClientContext create(ClassLoader classLoader) {
return create(null, classLoader);
}
/**
* Creates and returns a new client context. The passed ejbClientConfiguration will
* be used by this client context during any of the context management activities (like auto-creation
* of remoting EJB receivers)
*
* @param ejbClientConfiguration The EJB client configuration. Can be null.
* @return
*/
public static EJBClientContext create(final EJBClientConfiguration ejbClientConfiguration) {
return create(ejbClientConfiguration, EJBClientContext.class.getClassLoader());
}
/**
* Creates and returns a new client context, using the given class loader to look for initializers.
* The passed ejbClientConfiguration will be used by this client context during any of
* the context management activities (like auto-creation of remoting EJB receivers)
*
* @param ejbClientConfiguration The EJB client configuration. Can be null.
* @param classLoader The class loader. Cannot be null
* @return
*/
public static EJBClientContext create(final EJBClientConfiguration ejbClientConfiguration, final ClassLoader classLoader) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(CREATE_CONTEXT_PERMISSION);
}
final EJBClientContext context = new EJBClientContext(ejbClientConfiguration);
// run it through the initializers
context.init(classLoader);
return context;
}
/**
* Sets the EJB client context selector. Replaces the existing selector, which is then returned by this method
*
* @param newSelector The selector to set. Cannot be null
* @return Returns the previously set EJB client context selector.
* @throws SecurityException if a security manager is installed and you do not have the {@code setEJBClientContextSelector}
* {@link RuntimePermission}
*/
public static ContextSelector setSelector(final ContextSelector newSelector) {
if (newSelector == null) {
throw Logs.MAIN.paramCannotBeNull("EJB client context selector");
}
if (SELECTOR_LOCKED) {
throw Logs.MAIN.ejbClientContextSelectorMayNotBeChanged();
}
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SET_SELECTOR_PERMISSION);
}
final ContextSelector oldSelector = SELECTOR;
SELECTOR = newSelector;
return oldSelector;
}
/**
* Set a constant EJB client context. Replaces the existing selector, which is then returned by this method
*
* @param context the context to set
* @return Returns the previously set EJB client context selector.
* @throws SecurityException if a security manager is installed and you do not have the {@code setEJBClientContextSelector} {@link RuntimePermission}
*/
public static ContextSelector setConstantContext(final EJBClientContext context) {
return setSelector(new ConstantContextSelector(context));
}
/**
* Returns the current EJB client context selector
*
* @return
*/
public static ContextSelector getSelector() {
return SELECTOR;
}
/**
* Prevent the selector from being changed again. Attempts to do so will result in a {@code SecurityException}.
*
* @throws SecurityException if a security manager is installed and you do not have the {@code setEJBClientContextSelector}
* {@link RuntimePermission}
*/
public static void lockSelector() {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SET_SELECTOR_PERMISSION);
}
SELECTOR_LOCKED = true;
}
/**
* Returns true if the EJB client context cannot be changed because it has been {@link #lockSelector()}.
* Returns false otherwise.
*
* @return
*/
public static boolean isSelectorLocked() {
return SELECTOR_LOCKED;
}
/**
* Get the current client context for this thread.
*
* @return the current client context
*/
public static EJBClientContext getCurrent() {
return SELECTOR.getCurrent();
}
/**
* Get the current client context for this thread, throwing an exception if none is set.
*
* @return the current client context
* @throws IllegalStateException if the current client context is not set
*/
public static EJBClientContext requireCurrent() throws IllegalStateException {
final EJBClientContext clientContext = getCurrent();
if (clientContext == null) {
throw Logs.MAIN.noEJBClientContextAvailable();
}
return clientContext;
}
/**
* Returns a {@link EJBClientContext} which is identified by the passed {@link EJBClientContextIdentifier ejbClientContextIdentifier}.
* The {@link EJBClientContext} is identified with the help of the {@link #getSelector() current selector}. If the
* {@link #getSelector() current selector} is not of type {@link IdentityEJBClientContextSelector} or if that selector
* can't {@link IdentityEJBClientContextSelector#getContext(EJBClientContextIdentifier) return} a {@link EJBClientContext}
* for the passed {@link EJBClientContextIdentifier ejbClientContextIdentifier} then this method throws an {@link IllegalStateException}
*
* @param ejbClientContextIdentifier The EJB client context identifier
* @return
* @throws IllegalStateException If this method couldn't find a {@link EJBClientContext} for the passed ejbClientContextIdentifier
*/
public static EJBClientContext require(final EJBClientContextIdentifier ejbClientContextIdentifier) throws IllegalStateException {
final ContextSelector currentSelector = SELECTOR;
// see if the selector is capable of handling requests for identity based client contexts
if (!(currentSelector instanceof IdentityEJBClientContextSelector)) {
// the current selector can't handle identity based contexts.
throw new IllegalStateException("No EJB client context available for context identifier: " + ejbClientContextIdentifier + ",since the " +
" current EJB client context selector " + currentSelector + " is not capable of returning identity based EJB client contexts");
}
// the selector is capable of handling scoped contexts, so use it to fetch the right one
final EJBClientContext identityEjbClientContext = ((IdentityEJBClientContextSelector) currentSelector).getContext(ejbClientContextIdentifier);
if (identityEjbClientContext == null) {
throw new IllegalStateException("No EJB client context available for context identifier: " + ejbClientContextIdentifier);
}
return identityEjbClientContext;
}
/**
* Register an EJB receiver with this client context.
*
* If the same {@link EJBReceiver} has already been associated in this client context or if a {@link EJBReceiver receiver}
* with the same {@link org.jboss.ejb.client.EJBReceiver#getNodeName() node name} has already been associated in this client
* context, then this method does not register the passed receiver and returns false.
*
* @param receiver the receiver to register
* @return Returns true if the receiver was registered in this client context. Else returns false.
* @throws IllegalArgumentException If the passed receiver is null
*/
public boolean registerEJBReceiver(final EJBReceiver receiver) {
return this.registerEJBReceiver(receiver, null);
}
/**
* Registers a {@link EJBReceiver} in this context and uses the {@link EJBReceiverContextCloseHandler receiverContextCloseHandler}
* to notify of a {@link EJBReceiverContext} being closed.
*
* If the same {@link EJBReceiver} has already been associated in this client context or if a {@link EJBReceiver receiver}
* with the same {@link org.jboss.ejb.client.EJBReceiver#getNodeName() node name} has already been associated in this client
* context, then this method does not register the passed receiver and returns false.
*
* @param receiver The EJB receiver to register
* @param receiverContextCloseHandler The receiver context close handler. Can be null.
* @return Returns true if the receiver was registered in this client context. Else returns false.
*/
boolean registerEJBReceiver(final EJBReceiver receiver, final EJBReceiverContextCloseHandler receiverContextCloseHandler) {
// make sure the EJB client context has not been closed
this.assertNotClosed();
if (receiver == null) {
throw Logs.MAIN.paramCannotBeNull("EJB receiver");
}
final EJBReceiverContext ejbReceiverContext;
final ReceiverAssociation association;
synchronized (this.ejbReceiverAssociations) {
if (this.ejbReceiverAssociations.containsKey(receiver)) {
logger.debugf("Skipping registration of receiver %s since the same instance already exists in this client context %s", receiver, this);
// nothing to do
return false;
}
// see if we already have a receiver for the node name corresponding to the receiver
// being registered
final EJBReceiver existingReceiverForNode = this.getNodeEJBReceiver(receiver.getNodeName(), false);
if (existingReceiverForNode != null) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping registration of receiver " + receiver + " since an EJB receiver already exists for "
+ "node name " + receiver.getNodeName() + " in client context " + this);
}
return false;
}
ejbReceiverContext = new EJBReceiverContext(receiver, this);
association = new ReceiverAssociation(ejbReceiverContext);
this.ejbReceiverAssociations.put(receiver, association);
// register a close handler, if any, for this receiver context
if (receiverContextCloseHandler != null) {
this.receiverContextCloseHandlers.put(ejbReceiverContext, receiverContextCloseHandler);
}
}
// associate it with a context
receiver.associate(ejbReceiverContext);
synchronized (this.ejbReceiverAssociations) {
association.associated = true;
// Associating a receiver with a context might be either successful or might fail (for example:
// failure in version handshake between client/server), in which case the receiver context
// will be closed and ultimately the association removed from this client context.
// So registration is successful only if the association is still in the associations map of this
// client context
final boolean registered = this.ejbReceiverAssociations.get(receiver) != null;
// let the EJBClientContextListener(s) know that a receiver was registered
if (registered) {
// we *don't* want to send these notification to listeners synchronously since the listeners can be any arbitrary
// application code and can potential block for a long time. So invoke the listeners asynchronously via our ExecutorService
EJBClientContextListener[] listeners;
synchronized (this.ejbClientContextListeners) {
listeners = this.ejbClientContextListeners.toArray(new EJBClientContextListener[this.ejbClientContextListeners.size()]);
}
for (final EJBClientContextListener listener : listeners) {
this.ejbClientContextTasksExecutorService.submit(new Runnable() {
@Override
public void run() {
try {
listener.receiverRegistered(ejbReceiverContext);
} catch (Throwable t) {
if (logger.isDebugEnabled()) {
// log and ignore
logger.debug("Exception trying to invoke EJBClientContextListener " + listener
+ " for EJB client context " + EJBClientContext.this + " on registertation of EJBReceiver " + receiver, t);
}
}
}
});
}
}
return registered;
}
}
/**
* Unregister (a previously registered) EJB receiver from this client context.
*
* This EJB client context will not use this unregistered receiver for any subsequent
* invocations
*
* @param receiver The EJB receiver to unregister
* @throws IllegalArgumentException If the passed receiver is null
*/
public void unregisterEJBReceiver(final EJBReceiver receiver) {
if (receiver == null) {
throw Logs.MAIN.paramCannotBeNull("EJB receiver");
}
synchronized (this.ejbReceiverAssociations) {
final ReceiverAssociation association = this.ejbReceiverAssociations.remove(receiver);
if (association != null) {
final EJBReceiverContext receiverContext = association.context;
// EJBCLIENT-119 - disassociate to ensure clean up & not leaking classloaders
receiver.disassociate(receiverContext);
final EJBReceiverContextCloseHandler receiverContextCloseHandler = this.receiverContextCloseHandlers.remove(receiverContext);
if (receiverContextCloseHandler != null) {
receiverContextCloseHandler.receiverContextClosed(receiverContext);
}
// we *don't* want to send these notification to listeners synchronously since the listeners can be any arbitrary
// application code and can potential block for a long time. So invoke the listeners asynchronously via our ExecutorService
EJBClientContextListener[] listeners;
synchronized (this.ejbClientContextListeners) {
listeners = this.ejbClientContextListeners.toArray(new EJBClientContextListener[this.ejbClientContextListeners.size()]);
}
for (final EJBClientContextListener listener : listeners) {
this.ejbClientContextTasksExecutorService.submit(new Runnable() {
@Override
public void run() {
try {
listener.receiverUnRegistered(receiverContext);
} catch (Throwable t) {
if (logger.isDebugEnabled()) {
// log and ignore
logger.debug("Exception trying to invoke EJBClientContextListener " + listener
+ " for EJB client context " + EJBClientContext.this + " on un-registertation of EJBReceiver " + receiver, t);
}
}
}
});
}
}
}
}
/**
* Register a Remoting connection with this client context.
*
* @param connection the connection to register
*/
@Deprecated
public void registerConnection(final Connection connection) {
//TODO: the protocol is hard coded to remote here. If this method is used to
//add a cconnection that is then used to setup a cluster this could cause issues
registerEJBReceiver(new RemotingConnectionEJBReceiver(connection, RemotingConnectionEJBReceiver.HTTP_REMOTING));
}
/**
* Register a Remoting connection with this client context.
*
* @param connection the connection to register
* @param remotingProtocol The remoting protocol. Can be 'remote', 'http-remoting' or 'https-remoting'
*/
public void registerConnection(final Connection connection, String remotingProtocol) {
//TODO: the protocol is hard coded to remote here. If this method is used to
//add a cconnection that is then used to setup a cluster this could cause issues
registerEJBReceiver(new RemotingConnectionEJBReceiver(connection, remotingProtocol));
}
/**
* Register a client interceptor with this client context.
*
* If the passed clientInterceptor is already added to this context with the same priority
* then this method just returns the old {@link org.jboss.ejb.client.EJBClientInterceptor.Registration}. If however,
* the clientInterceptor is already registered in this context with a different priority then this method
* throws an {@link IllegalArgumentException}
*
* @param priority the absolute priority of this interceptor (lower runs earlier; higher runs later)
* @param clientInterceptor the interceptor to register
* @return a handle which may be used to later remove this registration
* @throws IllegalArgumentException if the given interceptor is {@code null}, the priority is less than 0, or the
* given interceptor is already registered with a different priority
*/
public EJBClientInterceptor.Registration registerInterceptor(final int priority, final EJBClientInterceptor clientInterceptor) throws IllegalArgumentException {
// make sure the EJB client context has not been closed
this.assertNotClosed();
if (clientInterceptor == null) {
throw Logs.MAIN.paramCannotBeNull("EJB client interceptor");
}
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(ADD_INTERCEPTOR_PERMISSION);
}
final EJBClientInterceptor.Registration newRegistration = new EJBClientInterceptor.Registration(this, clientInterceptor, priority);
EJBClientInterceptor.Registration[] oldRegistrations, newRegistrations;
do {
oldRegistrations = registrations;
for (EJBClientInterceptor.Registration oldRegistration : oldRegistrations) {
if (oldRegistration.getInterceptor() == clientInterceptor) {
if (oldRegistration.compareTo(newRegistration) == 0) {
// This means that a client interceptor which has already been added to this context,
// is being added with the same priority. In such cases, this new registration request
// is effectively a no-op and we just return the old registration
return oldRegistration;
} else {
// This means that a client interceptor which has been added to this context, is being added
// again with a different priority. We don't allow that to happen
throw Logs.MAIN.ejbClientInterceptorAlreadyRegistered(clientInterceptor);
}
}
}
final int length = oldRegistrations.length;
newRegistrations = Arrays.copyOf(oldRegistrations, length + 1);
newRegistrations[length] = newRegistration;
Arrays.sort(newRegistrations);
} while (!registrationsUpdater.compareAndSet(this, oldRegistrations, newRegistrations));
return newRegistration;
}
/**
* Returns the {@link EJBClientConfiguration} applicable to this EJB client context. Returns null
* if this EJB client context isn't configured with a {@link EJBClientConfiguration}
*
* @return
*/
public EJBClientConfiguration getEJBClientConfiguration() {
return this.ejbClientConfiguration;
}
/**
* Registers a {@link ReconnectHandler} in this {@link EJBClientContext}
*
* @param reconnectHandler The reconnect handler. Cannot be null
*/
public void registerReconnectHandler(final ReconnectHandler reconnectHandler) {
if (this.closed) {
if (logger.isTraceEnabled()) {
logger.trace("EJB client context " + this + " has been closed, reconnect handler " + reconnectHandler + " will not be added to the context");
}
return;
}
if (reconnectHandler == null) {
throw Logs.MAIN.paramCannotBeNull("Reconnect handler");
}
synchronized (this.reconnectHandlers) {
this.reconnectHandlers.add(reconnectHandler);
}
}
/**
* Unregisters a {@link ReconnectHandler} from this {@link EJBClientContext}
*
* @param reconnectHandler The reconnect handler to unregister
*/
public void unregisterReconnectHandler(final ReconnectHandler reconnectHandler) {
synchronized (this.reconnectHandlers) {
this.reconnectHandlers.remove(reconnectHandler);
}
}
/**
* Registers a {@link EJBClientContextListener} for this {@link EJBClientContext}. The {@link EJBClientContextListener}
* will be notified of lifecycle events related to the {@link EJBClientContext}
*
* @param listener The EJB client context listener. Cannot be null.
* @return Returns true if the passed listener was registered with this EJB client context. False otherwise.
*/
public boolean registerEJBClientContextListener(final EJBClientContextListener listener) {
if (listener == null) {
throw Logs.MAIN.paramCannotBeNull("EJB client context listener");
}
return this.ejbClientContextListeners.add(listener);
}
void removeInterceptor(final EJBClientInterceptor.Registration registration) {
EJBClientInterceptor.Registration[] oldRegistrations, newRegistrations;
do {
oldRegistrations = registrations;
newRegistrations = null;
final int length = oldRegistrations.length;
final int newLength = length - 1;
if (length == 1) {
if (oldRegistrations[0] == registration) {
newRegistrations = NO_INTERCEPTORS;
}
} else {
for (int i = 0; i < length; i++) {
if (oldRegistrations[i] == registration) {
if (i == newLength) {
newRegistrations = Arrays.copyOf(oldRegistrations, newLength);
break;
} else {
newRegistrations = new EJBClientInterceptor.Registration[newLength];
if (i > 0) System.arraycopy(oldRegistrations, 0, newRegistrations, 0, i);
System.arraycopy(oldRegistrations, i + 1, newRegistrations, i, newLength - i);
break;
}
}
}
}
if (newRegistrations == null) {
return;
}
} while (!registrationsUpdater.compareAndSet(this, oldRegistrations, newRegistrations));
}
Collection getEJBReceivers(final String appName, final String moduleName, final String distinctName) {
return this.getEJBReceivers(appName, moduleName, distinctName, true);
}
private Collection getEJBReceivers(final String appName, final String moduleName, final String distinctName,
final boolean attemptReconnect) {
if (this.closed) {
if (logger.isTraceEnabled()) {
logger.trace("EJB client context " + this + " has been closed, returning an empty collection of EJB receivers");
}
return Collections.emptySet();
}
final Collection eligibleEJBReceivers = new HashSet();
synchronized (this.ejbReceiverAssociations) {
for (final Map.Entry entry : this.ejbReceiverAssociations.entrySet()) {
if (entry.getValue().associated) {
final EJBReceiver ejbReceiver = entry.getKey();
if (ejbReceiver.acceptsModule(appName, moduleName, distinctName)) {
eligibleEJBReceivers.add(ejbReceiver);
}
}
}
}
if (eligibleEJBReceivers.isEmpty() && attemptReconnect) {
// we found no receivers, so see if we there are re-connect handlers which can create possible
// receivers
this.attemptReconnections();
// now that the re-connect handlers have run, let's fetch the receivers (if any) for this app/module/distinct-name
// combination. We won't attempt any reconnections now.
eligibleEJBReceivers.addAll(this.getEJBReceivers(appName, moduleName, distinctName, false));
}
return eligibleEJBReceivers;
}
/**
* Get the first EJB receiver which matches the given combination of app, module and distinct name.
*
* @param appName the application name, or {@code null} for a top-level module
* @param moduleName the module name
* @param distinctName the distinct name, or {@code null} for none
* @return the first EJB receiver to match, or {@code null} if none match
*/
EJBReceiver getEJBReceiver(final String appName, final String moduleName, final String distinctName) {
return this.getEJBReceiver((Collection) null, appName, moduleName, distinctName);
}
/**
* Get the first EJB receiver which matches the given combination of app, module and distinct name. If there's
* no such EJB receiver, then this method throws a {@link IllegalStateException}
*
* @param appName the application name, or {@code null} for a top-level module
* @param moduleName the module name
* @param distinctName the distinct name, or {@code null} for none
* @return the first EJB receiver to match
* @throws IllegalStateException If there's no {@link EJBReceiver} which can handle a EJB for the passed combination
* of app, module and distinct name.
*/
EJBReceiver requireEJBReceiver(final String appName, final String moduleName, final String distinctName)
throws IllegalStateException {
// try and find a receiver which can handle this combination
EJBReceiver ejbReceiver = this.getEJBReceiver(appName, moduleName, distinctName);
if (ejbReceiver == null) {
if(WILDFLY_TESTSUITE_HACK) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ejbReceiver = this.getEJBReceiver(appName, moduleName, distinctName);
if (ejbReceiver != null) {
return ejbReceiver;
}
}
throw Logs.MAIN.noEJBReceiverAvailableForDeployment(appName, moduleName, distinctName);
}
return ejbReceiver;
}
/**
* Get the first EJB receiver which matches the given combination of app, module and distinct name.
*
* @param invocationContext
* @param appName the application name, or {@code null} for a top-level module
* @param moduleName the module name
* @param distinctName the distinct name, or {@code null} for none
* @return the first EJB receiver to match, or {@code null} if none match
*/
EJBReceiver getEJBReceiver(final EJBClientInvocationContext invocationContext, final String appName, final String moduleName, final String distinctName) {
final Iterator iterator = getEJBReceivers(appName, moduleName, distinctName).iterator();
if (!iterator.hasNext()) {
return null;
}
final Set excludedNodes = invocationContext == null ? Collections.emptySet() : invocationContext.getExcludedNodes();
final Map eligibleReceivers = new HashMap();
while (iterator.hasNext()) {
final EJBReceiver receiver = iterator.next();
final String nodeName = receiver.getNodeName();
// The receiver is not eligible if the invocation context has marked that
// node as excluded
if (excludedNodes.contains(nodeName)) {
if (logger.isDebugEnabled()) {
logger.debug(nodeName + " is excluded from handling appname=" + appName + ",modulename=" + moduleName
+ ",distinctname=" + distinctName + " in invocation context " + invocationContext);
}
continue;
}
// make a note that the receiver is eligible
eligibleReceivers.put(receiver.getNodeName(), receiver);
}
if (eligibleReceivers.isEmpty()) {
return null;
}
// let the deployment node selector, select a node
final String selectedNode = this.deploymentNodeSelector.selectNode(eligibleReceivers.keySet().toArray(new String[eligibleReceivers.size()]), appName, moduleName, distinctName);
if (logger.isDebugEnabled()) {
logger.debug(this.deploymentNodeSelector + " deployment node selector selected " + selectedNode
+ " node for appname=" + appName + ",modulename=" + moduleName + ",distinctname=" + distinctName);
}
// if the deployment node selector picked a node which didn't belong to the eligible receivers
// then let's just return (a random) eligible node from the iterator
if (selectedNode == null || selectedNode.trim().isEmpty() || !eligibleReceivers.containsKey(selectedNode)) {
logger.debugf("Selected node %s doesn't belong to eligible receivers. Continuing with a random eligible receiver", selectedNode);
return iterator.next();
}
return eligibleReceivers.get(selectedNode);
}
/**
* Get the first EJB receiver which matches the given combination of app, module and distinct name. If there's
* no such EJB receiver, then this method throws a {@link IllegalStateException}
*
* @param appName the application name, or {@code null} for a top-level module
* @param moduleName the module name
* @param distinctName the distinct name, or {@code null} for none
* @return the first EJB receiver to match
* @throws IllegalStateException If there's no {@link EJBReceiver} which can handle a EJB for the passed combination
* of app, module and distinct name.
*/
EJBReceiver requireEJBReceiver(final EJBClientInvocationContext clientInvocationContext, final String appName, final String moduleName, final String distinctName)
throws IllegalStateException {
// try and find a receiver which can handle this combination
EJBReceiver ejbReceiver = this.getEJBReceiver(clientInvocationContext, appName, moduleName, distinctName);
if (ejbReceiver == null) {
if(WILDFLY_TESTSUITE_HACK) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ejbReceiver = this.getEJBReceiver(clientInvocationContext, appName, moduleName, distinctName);
if (ejbReceiver != null) {
return ejbReceiver;
}
}
throw Logs.MAIN.noEJBReceiverAvailableForDeploymentDuringInvocation(appName, moduleName, distinctName, clientInvocationContext);
}
return ejbReceiver;
}
/**
* Get the first EJB receiver which matches the given combination of app, module and distinct name.
*
* @param excludedNodeNames The node names of the EJBReceiver(s) which should be ignored while selecting the eligible receivers. Can be null or empty collection.
* @param appName the application name, or {@code null} for a top-level module
* @param moduleName the module name
* @param distinctName the distinct name, or {@code null} for none
* @return the first EJB receiver to match, or {@code null} if none match
*/
EJBReceiver getEJBReceiver(final Collection excludedNodeNames, final String appName, final String moduleName, final String distinctName) {
final Iterator iterator = getEJBReceivers(appName, moduleName, distinctName).iterator();
if (!iterator.hasNext()) {
return null;
}
final Map eligibleReceivers = new HashMap();
while (iterator.hasNext()) {
final EJBReceiver receiver = iterator.next();
final String nodeName = receiver.getNodeName();
// The receiver is not eligible if this is an excluded node
if (excludedNodeNames != null && excludedNodeNames.contains(nodeName)) {
logger.debugf("%s has been asked to be excluded from handling appName=%s, moduleName=%s=, distinctName=%s", nodeName, appName, moduleName, distinctName);
continue;
}
// make a note that the receiver is eligible
eligibleReceivers.put(receiver.getNodeName(), receiver);
}
if (eligibleReceivers.isEmpty()) {
return null;
}
// let the deployment node selector, select a node
final String selectedNode = this.deploymentNodeSelector.selectNode(eligibleReceivers.keySet().toArray(new String[eligibleReceivers.size()]), appName, moduleName, distinctName);
logger.debugf("%s deployment node selector selected %s node for appname=%s, modulename=%s, distinctName=%s", this.deploymentNodeSelector, selectedNode, appName, moduleName, distinctName);
// if the deployment node selector picked a node which didn't belong to the eligible receivers
// then let's just return (a random) eligible node from the iterator
if (selectedNode == null || selectedNode.trim().isEmpty() || !eligibleReceivers.containsKey(selectedNode)) {
logger.debugf("Selected node %s doesn't belong to eligible receivers. Continuing with a random eligible receiver", selectedNode);
return iterator.next();
}
return eligibleReceivers.get(selectedNode);
}
/**
* Get the first EJB receiver which matches the given combination of app, module and distinct name. If there's
* no such EJB receiver, then this method throws a {@link IllegalStateException}
*
* @param excludedNodeNames The node names of the EJBReceiver(s) which should be ignored while selecting the eligible receivers. Can be null or empty collection.
* @param appName the application name, or {@code null} for a top-level module
* @param moduleName the module name
* @param distinctName the distinct name, or {@code null} for none
* @return the first EJB receiver to match
* @throws IllegalStateException If there's no {@link EJBReceiver} which can handle a EJB for the passed combination
* of app, module and distinct name.
*/
EJBReceiver requireEJBReceiver(final Collection excludedNodeNames, final String appName, final String moduleName, final String distinctName)
throws IllegalStateException {
// try and find a receiver which can handle this combination
EJBReceiver ejbReceiver = this.getEJBReceiver(excludedNodeNames, appName, moduleName, distinctName);
if (ejbReceiver == null) {
if(WILDFLY_TESTSUITE_HACK) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ejbReceiver = this.getEJBReceiver(excludedNodeNames, appName, moduleName, distinctName);
if (ejbReceiver != null) {
return ejbReceiver;
}
}
throw Logs.MAIN.noEJBReceiverAvailableForDeployment(appName, moduleName, distinctName);
}
return ejbReceiver;
}
/**
* Returns a {@link EJBReceiverContext} for the passed receiver. If the receiver
* hasn't been registered with this {@link EJBClientContext}, either through a call to {@link #registerConnection(org.jboss.remoting3.Connection, String)}
* or to {@link #requireEJBReceiver(String, String, String)}, then this method throws an {@link IllegalStateException}
*
*
* @param receiver The {@link EJBReceiver} for which the {@link EJBReceiverContext} is being requested
* @return The {@link EJBReceiverContext}
* @throws IllegalStateException If the passed receiver hasn't been registered with this {@link EJBClientContext}
*/
EJBReceiverContext requireEJBReceiverContext(final EJBReceiver receiver) throws IllegalStateException {
// make sure the EJB client context has not been closed
this.assertNotClosed();
synchronized (this.ejbReceiverAssociations) {
final ReceiverAssociation association = this.ejbReceiverAssociations.get(receiver);
if (association == null) {
throw Logs.MAIN.receiverNotAssociatedWithClientContext(receiver, this);
}
return association.context;
}
}
EJBReceiver requireNodeEJBReceiver(final String nodeName) {
final EJBReceiver receiver = getNodeEJBReceiver(nodeName);
if (receiver != null) return receiver;
throw Logs.MAIN.noEJBReceiverForNode(nodeName);
}
EJBReceiver getNodeEJBReceiver(final String nodeName) {
return this.getNodeEJBReceiver(nodeName, true);
}
private EJBReceiver getNodeEJBReceiver(final String nodeName, final boolean attemptReconnect) {
if (this.closed) {
if (logger.isTraceEnabled()) {
logger.trace("EJB client context " + this + " has been closed, no EJB receiver will be returned for node name " + nodeName);
}
return null;
}
if (nodeName == null) {
throw Logs.MAIN.paramCannotBeNull("Node name");
}
synchronized (this.ejbReceiverAssociations) {
for (final Map.Entry entry : this.ejbReceiverAssociations.entrySet()) {
if (entry.getValue().associated) {
final EJBReceiver ejbReceiver = entry.getKey();
if (nodeName.equals(ejbReceiver.getNodeName())) {
return ejbReceiver;
}
}
}
}
// no EJB receiver found for the node name, so let's see if there are re-connect handlers which can
// create the EJB receivers
if (attemptReconnect) {
this.attemptReconnections();
// now that re-connections have been attempted, let's fetch any EJB receiver for this node name.
// we won't try reconnecting again now
return this.getNodeEJBReceiver(nodeName, false);
}
return null;
}
EJBReceiverContext requireNodeEJBReceiverContext(final String nodeName) {
final EJBReceiver ejbReceiver = requireNodeEJBReceiver(nodeName);
return requireEJBReceiverContext(ejbReceiver);
}
EJBReceiverContext getNodeEJBReceiverContext(final String nodeName) {
final EJBReceiver ejbReceiver = getNodeEJBReceiver(nodeName);
return ejbReceiver == null ? null : requireEJBReceiverContext(ejbReceiver);
}
/**
* Returns true if the nodeName belongs to a cluster named clusterName. Else
* returns false.
*
* @param clusterName The name of the cluster
* @param nodeName The name of the node within the cluster
* @return
*/
boolean clusterContains(final String clusterName, final String nodeName) {
if (this.closed) {
if (logger.isTraceEnabled()) {
logger.trace("EJB client context " + this + " has been closed, node named " + nodeName + " is not considered part of cluster named " + clusterName);
}
return false;
}
final ClusterContext clusterContext;
synchronized (clusterContexts) {
clusterContext = this.clusterContexts.get(clusterName);
}
if (clusterContext == null) {
return false;
}
return clusterContext.isNodeAvailable(nodeName);
}
/**
* Returns a {@link EJBReceiverContext} for the clusterName. If there's no such receiver context
* for the cluster, then this method returns null
*
* @param invocationContext
* @param clusterName The name of the cluster
* @return
*/
EJBReceiverContext getClusterEJBReceiverContext(final EJBClientInvocationContext invocationContext, final String clusterName) throws IllegalArgumentException {
return this.getClusterEJBReceiverContext(invocationContext, clusterName, true);
}
private EJBReceiverContext getClusterEJBReceiverContext(final EJBClientInvocationContext invocationContext, final String clusterName, final boolean attemptReconnect) throws IllegalArgumentException {
if (this.closed) {
if (logger.isTraceEnabled()) {
logger.trace("EJB client context " + this + " has been closed, returning no EJB receiver for cluster named " + clusterName);
}
return null;
}
final ClusterContext clusterContext;
synchronized (clusterContexts) {
clusterContext = this.clusterContexts.get(clusterName);
}
if (clusterContext == null) {
return null;
}
final EJBReceiverContext ejbReceiverContext = clusterContext.getEJBReceiverContext(invocationContext);
if (ejbReceiverContext == null && attemptReconnect) {
// no receiver context was found for the cluster. So let's see if there are any re-connect handlers
// which can generate the EJB receivers
this.attemptReconnections();
// now that we have attempted the re-connections, let's fetch any EJB receiver context for the cluster.
// we won't try re-connecting again now
return this.getClusterEJBReceiverContext(invocationContext, clusterName, false);
}
return ejbReceiverContext;
}
/**
* Returns a {@link EJBReceiverContext} for the clusterName. If there's no such receiver context
* for the cluster, then this method throws an {@link IllegalArgumentException}
*
* @param clusterName The name of the cluster
* @return
* @throws IllegalArgumentException If there's no EJB receiver context available for the cluster
*/
EJBReceiverContext requireClusterEJBReceiverContext(final String clusterName) throws IllegalArgumentException {
return this.requireClusterEJBReceiverContext(null, clusterName);
}
/**
* Returns a {@link EJBReceiverContext} for the clusterName. If there's no such receiver context
* for the cluster, then this method throws an {@link IllegalArgumentException}
*
* @param invocationContext
* @param clusterName The name of the cluster
* @return
* @throws IllegalArgumentException If there's no EJB receiver context available for the cluster
*/
EJBReceiverContext requireClusterEJBReceiverContext(final EJBClientInvocationContext invocationContext, final String clusterName) throws IllegalArgumentException {
// make sure the EJB client context has not been closed
this.assertNotClosed();
ClusterContext clusterContext;
synchronized (clusterContexts) {
clusterContext = this.clusterContexts.get(clusterName);
}
if (clusterContext == null) {
// let's wait for some time to see if the asynchronous cluster topology becomes available.
// Note that this isn't a great thing to do for clusters which might have been removed or for clusters
// which will never be formed, since this wait results in a 5 second delay in the invocation. But ideally
// such cases should be pretty low.
logger.debugf("Waiting for cluster topology information to be available for cluster named %s", clusterName);
this.waitForClusterTopology(clusterName);
// see if the cluster context was created during this wait time
synchronized (clusterContexts) {
clusterContext = this.clusterContexts.get(clusterName);
}
if (clusterContext == null) {
throw Logs.MAIN.noClusterContextAvailable(clusterName);
}
}
final EJBReceiverContext ejbReceiverContext = this.getClusterEJBReceiverContext(invocationContext, clusterName);
if (ejbReceiverContext == null) {
throw Logs.MAIN.noReceiverContextsInCluster(clusterName);
}
return ejbReceiverContext;
}
/**
* Returns the interceptor chain consisting of {@link EJBClientInterceptor}s which have been either explicitly {@link #registerInterceptor(int, EJBClientInterceptor) registered}
* or have been found in the classpath of the application via the {@link ServiceLoader} for {@link EJBClientInterceptor}s. The {@ink EJBClientInterceptor}s
* found in the classpath will be added to the end of the chain, in the order they were retrieved by the {@link ServiceLoader}
*
* @return
*/
EJBClientInterceptor[] getInterceptorChain() {
// todo optimize to eliminate copy
final EJBClientInterceptor.Registration[] registeredInterceptors = this.registrations;
final int totalInterceptorLength = this.clientInterceptorsInClasspath != null ? registeredInterceptors.length + this.clientInterceptorsInClasspath.size() : registeredInterceptors.length;
final EJBClientInterceptor[] interceptors = new EJBClientInterceptor[totalInterceptorLength];
// The interceptors which have been added in a specific priority/order go first.
for (int i = 0; i < registeredInterceptors.length; i++) {
interceptors[i] = registeredInterceptors[i].getInterceptor();
}
// lastly all the interceptors (if any) which were on the classpath and loaded via the ServiceLoader are added to the end of the chain.
if (this.clientInterceptorsInClasspath != null && !this.clientInterceptorsInClasspath.isEmpty()) {
int i = registeredInterceptors.length;
for (final EJBClientInterceptor interceptor : this.clientInterceptorsInClasspath) {
interceptors[i++] = interceptor;
}
}
return interceptors;
}
/**
* Returns a {@link ClusterContext} corresponding to the passed clusterName. If no
* such cluster context exists, a new one is created and returned. Subsequent invocations on this
* {@link EJBClientContext} for the same cluster name will return this same {@link ClusterContext}, unless
* the cluster has been removed from this client context.
*
* @param clusterName The name of the cluster
* @return
*/
@Deprecated // make non-public
public ClusterContext getOrCreateClusterContext(final String clusterName) {
// make sure the EJB client context has not been closed
this.assertNotClosed();
ClusterContext clusterContext;
synchronized (clusterContexts) {
clusterContext = this.clusterContexts.get(clusterName);
if (clusterContext == null) {
clusterContext = new ClusterContext(clusterName, this, this.ejbClientConfiguration);
// register a listener which will trigger a notification when nodes are added to the cluster
clusterContext.registerListener(this.clusterFormationNotifier);
this.clusterContexts.put(clusterName, clusterContext);
}
}
return clusterContext;
}
/**
* Returns a {@link ClusterContext} corresponding to the passed clusterName. If no
* such cluster context exists, then this method returns null.
*
* @param clusterName The name of the cluster
* @return
*/
@Deprecated // make non-public
public ClusterContext getClusterContext(final String clusterName) {
if (this.closed) {
if (logger.isTraceEnabled()) {
logger.trace("EJB client context " + this + " has been closed, returning no cluster context for cluster named " + clusterName);
}
return null;
}
ClusterContext clusterContext = null;
synchronized (clusterContexts) {
clusterContext = this.clusterContexts.get(clusterName);
}
return clusterContext;
}
/**
* Removes the cluster identified by the clusterName from this client context
*
* @param clusterName The name of the cluster
*/
@Deprecated // make non-public
public void removeCluster(final String clusterName) {
final ClusterContext clusterContext;
synchronized (clusterContexts) {
clusterContext = this.clusterContexts.remove(clusterName);
}
if (clusterContext == null) {
return;
}
try {
// close the cluster context to allow it to cleanup any resources
clusterContext.close();
} catch (Throwable t) {
// ignore
logger.debugf(t, "Ignoring an error that occured while closing a cluster context for cluster named %s", clusterName);
}
}
/**
* Waits for (a maximum of 5 seconds for) a cluster topology to be available for clusterName
*
* @param clusterName The name of the cluster
*/
private void waitForClusterTopology(final String clusterName) {
final CountDownLatch clusterFormationLatch = new CountDownLatch(1);
// register for the notification
this.clusterFormationNotifier.registerForClusterFormation(clusterName, clusterFormationLatch);
// now wait (max 5 seconds)
try {
final boolean receivedClusterTopology = clusterFormationLatch.await(5, TimeUnit.SECONDS);
if (receivedClusterTopology) {
logger.debugf("Received the cluster topology for cluster named %s during the wait time", clusterName);
}
} catch (InterruptedException e) {
// ignore
} finally {
// unregister from the cluster formation notification
this.clusterFormationNotifier.unregisterFromClusterNotification(clusterName, clusterFormationLatch);
}
}
private void attemptReconnections() {
final CountDownLatch reconnectTasksCompletionNotifierLatch;
final List reconnectHandlersToAttempt;
// check for circumstances where we don't want to attempt reconnection
synchronized (this) {
// no need to reconnect if the context is closed
if (this.closed) {
if (logger.isTraceEnabled()) {
logger.trace("EJB client context " + this + " has been closed, no reconnections, to register EJB receivers, will be attempted");
}
return;
}
// no need to attempt reconnection of no handlers
reconnectHandlersToAttempt = new ArrayList(this.reconnectHandlers);
if (reconnectHandlersToAttempt.isEmpty()) {
// no re-connections to attempt, just return
return;
}
}
// first thread through performs the reconnect attempt, others block
if (attemptingReconnect.compareAndSet(false, true)) {
// initiate the reconnection tasks
if (logger.isTraceEnabled()) {
logger.trace("EJB client context " + this + " attempting reconnect on thread " + Thread.currentThread().getName());
}
reconnectTasksCompletionNotifierLatch = new CountDownLatch(reconnectHandlersToAttempt.size());
for (final ReconnectHandler reconnectHandler : reconnectHandlersToAttempt) {
// submit each of the re-connection tasks
this.ejbClientContextTasksExecutorService.submit(new ReconnectAttempt(reconnectHandler, reconnectTasksCompletionNotifierLatch, this));
}
// now wait for all tasks to complete (with a upper bound on time limit)
try {
long reconnectWaitTimeout = 10000; // default 10 seconds
if (this.ejbClientConfiguration != null && this.ejbClientConfiguration.getReconnectTasksTimeout() > 0) {
reconnectWaitTimeout = this.ejbClientConfiguration.getReconnectTasksTimeout();
}
reconnectTasksCompletionNotifierLatch.await(reconnectWaitTimeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// restore the interrupt status
Thread.currentThread().interrupt();
} finally {
// when we reach here, the reconnection should be completed
// notify the waiting threads
try {
reconnectCompletedLock.lock();
if (logger.isTraceEnabled()) {
logger.trace("Reconnection attempt for EJB client context " + this + " complete on thread " + Thread.currentThread().getName());
}
reconnectCompletedCondition.signalAll();
} finally {
reconnectCompletedLock.unlock();
}
// reset the attempting connect flag
this.attemptingReconnect.compareAndSet(true, false);
}
} else {
// subsequent threads block until reconnect is completed
try {
reconnectCompletedLock.lock();
if (logger.isTraceEnabled()) {
logger.trace("Waiting for reconnection attempt for EJB client context " + this + " on thread " + Thread.currentThread().getName());
}
reconnectCompletedCondition.await();
} catch (InterruptedException e) {
// restore the interrupt status
Thread.currentThread().interrupt();
} finally {
reconnectCompletedLock.unlock();
}
}
}
/**
* Closes the EJB client context and notifies any registered {@link EJBClientContextListener}s about
* the context being closed.
*
* @throws IOException
*/
@Override
public synchronized void close() throws IOException {
if (closed) {
return;
}
// mark this context as closed. The real cleanup like closing of EJB receivers
// *isn't* the responsibility of the EJB client context. We'll just let our EJBClientContextListeners
// (if any) know about the context being closed and let them handle closing the receivers if they want to
this.closed = true;
// WFLY-4016 - call unregisterEJBReceiver so that listener.receiverUnRegistered(...) is called to prevent memory leak
EJBReceiver[] ejbReceivers;
synchronized (this.ejbReceiverAssociations) {
ejbReceivers = this.ejbReceiverAssociations.keySet().toArray(new EJBReceiver[this.ejbReceiverAssociations.size()]);
}
for(final EJBReceiver receiver : ejbReceivers) {
unregisterEJBReceiver(receiver);
}
// use a new array to iterate on to avoid ConcurrentModificationException EJBCLIENT-92
EJBClientContextListener[] listeners;
synchronized (this.ejbClientContextListeners) {
listeners = this.ejbClientContextListeners.toArray(new EJBClientContextListener[this.ejbClientContextListeners.size()]);
}
for (final EJBClientContextListener listener : listeners) {
try {
listener.contextClosed(this);
} catch (Throwable t) {
logger.debugf(t, "Ignoring the exception thrown by an EJB client context listener while closing the context %s", this);
}
}
}
@Override
protected void finalize() throws Throwable {
try {
if (!closed) {
this.close();
}
} finally {
super.finalize();
}
}
/**
* Throws a {@link IllegalStateException} if this EJB client context is closed. Else just returns.
*/
private void assertNotClosed() {
if (this.closed) {
throw Logs.MAIN.ejbClientContextIsClosed(this);
}
}
/**
* A {@link EJBReceiverContextCloseHandler} will be notified through a call to
* {@link #receiverContextClosed(EJBReceiverContext)} whenever a {@link EJBReceiverContext}, to which
* the {@link EJBReceiverContextCloseHandler}, has been {@link EJBClientContext#registerEJBReceiver(EJBReceiver, org.jboss.ejb.client.EJBClientContext.EJBReceiverContextCloseHandler) associated}
* is closed by this {@link EJBClientContext}
*/
interface EJBReceiverContextCloseHandler {
/**
* A callback method which will be invoked when the {@link EJBReceiverContext receiverContext}
* is closed. This method can do the necessary cleanup (if any) of resources associated with the
* receiver context
*
* @param receiverContext The receiver context which was closed
*/
void receiverContextClosed(final EJBReceiverContext receiverContext);
}
private static final class ReceiverAssociation {
final EJBReceiverContext context;
boolean associated = false;
private ReceiverAssociation(final EJBReceiverContext context) {
this.context = context;
}
}
/**
* A notifier which can be used for waiting for cluster formation events
*/
private final class ClusterFormationNotifier implements ClusterContext.ClusterContextListener {
private final Map> clusterFormationListeners = new HashMap>();
/**
* Register for cluster formation event notification.
*
* @param clusterName The name of the cluster
* @param latch The {@link CountDownLatch} which the caller can use to wait for the cluster formation
* to take place. The {@link ClusterFormationNotifier} will invoke the {@link java.util.concurrent.CountDownLatch#countDown()}
* when the cluster is formed
*/
void registerForClusterFormation(final String clusterName, final CountDownLatch latch) {
synchronized (this.clusterFormationListeners) {
List listeners = this.clusterFormationListeners.get(clusterName);
if (listeners == null) {
listeners = new ArrayList();
this.clusterFormationListeners.put(clusterName, listeners);
}
listeners.add(latch);
}
}
/**
* Callback invocation for the cluster formation event. This method will invoke {@link java.util.concurrent.CountDownLatch#countDown()}
* on each of the waiting {@link CountDownLatch} for the cluster.
*
* @param clusterName The name of the cluster
*/
void notifyClusterFormation(final String clusterName) {
final List listeners;
synchronized (this.clusterFormationListeners) {
// remove the waiting listeners
listeners = this.clusterFormationListeners.remove(clusterName);
}
if (listeners == null) {
return;
}
// notify any waiting listeners
for (final CountDownLatch latch : listeners) {
latch.countDown();
}
}
/**
* Unregisters from cluster formation notifications for the cluster
*
* @param clusterName The name of the cluster
* @param latch The {@link CountDownLatch} which will be unregistered from the waiting {@link CountDownLatch}es
*/
void unregisterFromClusterNotification(final String clusterName, final CountDownLatch latch) {
synchronized (this.clusterFormationListeners) {
final List listeners = this.clusterFormationListeners.get(clusterName);
if (listeners == null) {
return;
}
listeners.remove(latch);
}
}
@Override
public void clusterNodesAdded(String clusterName, ClusterNodeManager... nodes) {
this.notifyClusterFormation(clusterName);
}
}
private class ReconnectAttempt implements Runnable {
private final ReconnectHandler reconnectHandler;
private final CountDownLatch taskCompletionNotifierLatch;
private final EJBClientContext parent;
ReconnectAttempt(final ReconnectHandler reconnectHandler, final CountDownLatch taskCompletionNotifierLatch, final EJBClientContext parent) {
this.reconnectHandler = reconnectHandler;
this.taskCompletionNotifierLatch = taskCompletionNotifierLatch;
this.parent = parent;
}
@Override
public void run() {
try {
synchronized (parent) {
if (closed) {
return;
}
}
this.reconnectHandler.reconnect();
} catch (Exception e) {
logger.debugf(e, "Exception trying to re-establish a connection from EJB client context %s", EJBClientContext.this);
} finally {
this.taskCompletionNotifierLatch.countDown();
}
}
}
}