/*
* Copyright 2008 the original author or authors.
* Copyright 2005 Sun Microsystems, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.rioproject.impl.associations;
import com.sun.jini.admin.DestroyAdmin;
import net.jini.admin.Administrable;
import net.jini.admin.JoinAdmin;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.EmptyConfiguration;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.*;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryManagement;
import net.jini.id.Uuid;
import net.jini.id.UuidFactory;
import net.jini.lookup.LookupCache;
import net.jini.lookup.ServiceDiscoveryEvent;
import net.jini.lookup.entry.Name;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import org.rioproject.admin.ServiceBeanControl;
import org.rioproject.associations.*;
import org.rioproject.config.Constants;
import org.rioproject.deploy.ServiceBeanInstance;
import org.rioproject.deploy.ServiceRecord;
import org.rioproject.impl.associations.filter.VersionMatchFilter;
import org.rioproject.impl.client.DiscoveryManagementPool;
import org.rioproject.impl.client.JiniClient;
import org.rioproject.impl.client.LookupCachePool;
import org.rioproject.impl.client.ServiceDiscoveryAdapter;
import org.rioproject.impl.container.ServiceBeanContainer;
import org.rioproject.impl.container.ServiceBeanContainerListener;
import org.rioproject.impl.container.ServiceBeanDelegate;
import org.rioproject.impl.fdh.FaultDetectionHandler;
import org.rioproject.impl.fdh.FaultDetectionHandlerFactory;
import org.rioproject.impl.fdh.FaultDetectionListener;
import org.rioproject.impl.util.ThrowableUtil;
import org.rioproject.opstring.OperationalStringManager;
import org.rioproject.servicebean.ServiceBeanContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.IntrospectionException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The DefaultAssociationManagement class implements the DefaultAssociationManagement interface.
*
* @author Dennis Reedy
*/
public class DefaultAssociationManagement implements AssociationManagement {
/**
* Registered AssociationListener instances.
*/
private final List<AssociationListener> listeners = new ArrayList<>();
/**
* Collection of AssociationDescriptor.
*/
private final List<AssociationHandler> associationHandlers = new ArrayList<>();
/**
* The ServiceBeanControl object for the ServiceBean.
*/
private ServiceBeanControl control;
/**
* If true, and the service has an Association with a type of
* AssociationType.REQUIRES and the Association is broken, the
* DefaultAssociationManagement object will unadvertise the ServiceBean using the
* ServiceBean instance's ServiceBeanControl object. If false, the
* DefaultAssociationManagement object will not unadvertise the ServiceBean.
*/
private final AtomicBoolean unadvertiseOnBroken = new AtomicBoolean(true);
/**
* Counter to indicate that we have at least one Association that is
* AssociationType.REQUIRES.
*/
private final AtomicInteger numRequires = new AtomicInteger(0);
/**
* Indicates whether the JSB is advertised or not.
*/
private final AtomicBoolean advertised = new AtomicBoolean(false);
/**
* Indicates that all required associations have been met, and for some
* reason the ServiceBeanControl object has not been set.
*/
private final AtomicBoolean advertisePending = new AtomicBoolean(false);
/**
* The ServiceBeanContext.
*/
private ServiceBeanContext context;
/**
* Internal AssociationListener
*/
private Listener listener;
/**
* The ClassLoader which will be used to provide the caller/client
* with a properly annotated proxy for associated services
*/
private ClassLoader callerCL;
/**
* The ServiceBeanContainer the service is running in
*/
private ServiceBeanContainer container;
/**
* Name of the service (client) that has the associations.
*/
private String clientName="<unknown>";
/**
* The configuration to use for obtaining a proxy preparer
*/
private Configuration config;
/**
* The Logger.
*/
private static final Logger logger = LoggerFactory.getLogger(DefaultAssociationManagement.class);
/**
* Configuration component attribute
*/
private static final String CONFIG_COMPONENT = "service.association";
/**
* Create an DefaultAssociationManagement instance. Uses the current thread's context
* class loader to provide the caller/client with properly annotated
* proxies for associated services
*
*/
public DefaultAssociationManagement() {
this(null);
}
/**
* Create an DefaultAssociationManagement instance. Uses the specified class loader
* to provide the caller/client with properly annotated proxies for
* associated services, or the current thread's context class
* loader if cl is null.
*
* @param cl The class loader to provide the caller/client with properly
*/
public DefaultAssociationManagement(final ClassLoader cl) {
if(cl==null) {
final Thread currentThread = Thread.currentThread();
callerCL = AccessController.doPrivileged(
(PrivilegedAction<ClassLoader>) currentThread::getContextClassLoader);
} else {
callerCL = cl;
}
listener = new Listener();
}
/**
* If an association is broken, unadvertise the service.
*
* @param unadvertiseOnBroken if {@code true}, unadvertise the service if the association is broken, otherwise
* do not unadvertise.
*/
public void setUnadvertiseOnBroken(final boolean unadvertiseOnBroken) {
this.unadvertiseOnBroken.set(unadvertiseOnBroken);
}
/**
* @see org.rioproject.associations.AssociationManagement#setServiceBeanControl
*/
public void setServiceBeanControl(final ServiceBeanControl control) {
if(control == null)
throw new IllegalArgumentException("control is null");
this.control = control;
if(advertisePending.get())
advertise();
}
/**
* Set the ServiceBeanContainer object
*
* @param container The ServiceBeanContainer that the ServiceBean is
* running in
*/
public void setServiceBeanContainer(final ServiceBeanContainer container) {
if(container == null)
throw new IllegalArgumentException("control is null");
this.container = container;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public void register(final AssociationListener... listeners) {
if (listeners == null)
throw new IllegalArgumentException("listeners are null");
synchronized (this.listeners) {
this.listeners.addAll(Arrays.asList(listeners));
}
for (Association association : getAssociations()) {
if (association.getState() == Association.State.PENDING ||
association.getState() == Association.State.BROKEN)
continue;
for (AssociationListener listener : listeners) {
if (checkAssociationListener(listener, association)) {
listener.discovered(association, association.getService());
}
}
}
}
/**
* {@inheritDoc}
*/
public void remove(final AssociationListener... listeners) {
if (listeners == null)
throw new IllegalArgumentException("listeners is null");
synchronized (this.listeners) {
this.listeners.removeAll(Arrays.asList(listeners));
}
}
/**
* {@inheritDoc}
*/
public void terminate() {
synchronized(associationHandlers) {
for (AssociationHandler ah : associationHandlers) {
ah.terminate();
}
if(listener!=null && listener.associationInjector!=null)
listener.associationInjector.terminate();
listeners.clear();
associationHandlers.clear();
}
callerCL = null;
control = null;
logger.debug("Terminated DefaultAssociationManagement for {}", clientName);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <T> Association<T> getAssociation(final Class<T> serviceType,
final String serviceName,
final String opStringName) {
if(serviceType == null)
throw new IllegalArgumentException("A serviceType must be provided");
Association<T> association = null;
synchronized(associationHandlers) {
for (AssociationHandler ah : associationHandlers) {
Association a = ah.getAssociation();
Object service = a.getService();
boolean serviceTypeMatches = false;
if(service!=null) {
serviceTypeMatches = isAssignableFrom(serviceType, service.getClass());
} else {
ServiceTemplate template = ah.getServiceTemplate();
if(template!=null) {
for(Class c : template.serviceTypes) {
if(isAssignableFrom(serviceType, c)) {
serviceTypeMatches = true;
break;
}
}
}
}
boolean found = false;
if (serviceTypeMatches) {
if(serviceName!=null) {
if (opStringName != null) {
if (a.getName().equals(serviceName) &&
a.getOperationalStringName().equals( opStringName)) {
found = true;
}
} else if (a.getName().equals(serviceName)) {
found = true;
}
} else {
found = true;
}
}
if(found) {
association = a;
break;
}
}
}
return association;
}
/**
* {@inheritDoc}
*/
public List<Association<?>> getAssociations() {
List<Association<?>> associations = new ArrayList<>();
synchronized(associationHandlers) {
for (AssociationHandler ah : associationHandlers) {
associations.add(ah.getAssociation());
}
}
return Collections.unmodifiableList(associations);
}
/**
* Set the PropertyDescriptor elements. This will be used to inject
* associated services
*
* @param backend The backend (implementation) to use for property injection
*/
public void setBackend(final Object backend) {
try {
listener.createAssociationInjector(backend);
} catch(IntrospectionException e) {
logger.warn("Creating AssociationInjector", e);
}
}
/**
* Set the name of the client that has the associations.
*
* @param clientName The name of the client that has the associations.
* This is used for logging and diagnostics
*/
private void setClientName(final String clientName) {
this.clientName = clientName;
}
/**
* Set the ServiceBeanContext object for the ServiceBean
*
* @param context The ServiceBeanContext object for the ServiceBean
*/
public void setServiceBeanContext(final ServiceBeanContext context) {
if(context==null)
throw new IllegalArgumentException("context is null");
boolean update = false;
if(this.context!=null)
update = true;
this.context = context;
StringBuilder nameBuilder = new StringBuilder();
if(context.getServiceElement().getOperationalStringName()!=null &&
context.getServiceElement().getOperationalStringName().length()>0) {
nameBuilder.append(context.getServiceElement().getOperationalStringName()).append("/");
}
nameBuilder.append(context.getServiceElement().getName());
if(context.getServiceElement().getServiceBeanConfig().getInstanceID()!=null) {
nameBuilder.append(":").append(context.getServiceElement().getServiceBeanConfig().getInstanceID());
}
setClientName(nameBuilder.toString());
ClassLoader currentCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(callerCL);
setConfiguration(context.getConfiguration());
} catch (ConfigurationException e) {
logger.warn("Unable to get Configuration from ServiceBeanContext. Will proceed without configuration", e);
} finally {
Thread.currentThread().setContextClassLoader(currentCL);
}
AssociationDescriptor[] newDesc = context.getServiceElement().getAssociationDescriptors();
if(newDesc != null) {
if(!update) {
if(logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("[").append(clientName).append("]");
sb.append(" DefaultAssociationManagement descriptors=").append(newDesc.length);
if(newDesc.length>0)
sb.append("\n");
for(int i=0; i<newDesc.length; i++) {
if(i>0)
sb.append("\n");
sb.append(i+1).append(". ").append(newDesc[i].toString());
}
logger.trace(sb.toString());
}
addAssociationDescriptors(newDesc);
if(numRequires.get()==0) {
advertised.set(true);
}
} else {
/* Determine if any of the AssociationDescriptors are new or
* are no longer needed */
AssociationDescriptor[] current = getAssociationDescriptors();
/* Add new AssociationDescriptor instances */
AssociationDescriptor[] addDesc = getDifference(newDesc, current);
addAssociationDescriptors(addDesc);
/* Remove AssociationHandlers which are no longer configured */
AssociationDescriptor[] remDesc = getDifference(current, newDesc);
AssociationHandler[] handlers = getAssociationHandlers();
for (AssociationDescriptor aRemDesc : remDesc) {
for (AssociationHandler handler : handlers) {
if (handler.associationDescriptor.equals(aRemDesc)) {
logger.trace("[{}] During update: terminate AssociationHandler for {}",
clientName, handler.associationDescriptor.toString());
handler.terminate();
if (handler.associationDescriptor.getAssociationType()== AssociationType.REQUIRES) {
numRequires.decrementAndGet();
listener.requiredAssociations.remove(handler.association);
}
synchronized (associationHandlers) {
associationHandlers.remove(handler);
}
break;
}
}
}
listener.checkAdvertise();
}
}
}
protected Configuration getConfiguration() {
return config==null? EmptyConfiguration.INSTANCE:config;
}
public void setConfiguration(final Configuration config) {
this.config = config;
}
/*
* Check if the AssociationListener method has a type that is assignable
*/
private boolean checkAssociationListener(final AssociationListener<?> al, final Association a) {
return checkAssociationListener(al, a.getService());
}
/*
* Check if the AssociationListener method has a type that is assignable
*/
private boolean checkAssociationListener(final AssociationListener<?> al, final Object service) {
if(service==null)
return false;
boolean ok = true;
Class<?> svcClass = service.getClass();
Method[] methods = al.getClass().getMethods();
for(Method m : methods) {
if(m.getName().equals("discovered")) {
Class<?>[] types = m.getParameterTypes();
if(!(types[1].isAssignableFrom(svcClass))) {
ok = false;
break;
}
}
}
return ok;
}
/*
* Return an array of AssociationDescriptor instances which are
* in the desc1 array but not in the desc2 array of AssociationDescriptor
* instances
*/
private AssociationDescriptor[] getDifference(final AssociationDescriptor[] desc1,
final AssociationDescriptor[] desc2) {
ArrayList<AssociationDescriptor> diffList = new ArrayList<>();
for (AssociationDescriptor aDesc1 : desc1) {
boolean matched = false;
for (AssociationDescriptor aDesc2 : desc2) {
if (aDesc1.equals(aDesc2)) {
matched = true;
break;
}
}
if (!matched) {
diffList.add(aDesc1);
}
}
return diffList.toArray(new AssociationDescriptor[diffList.size()]);
}
/*
* Get an AssociationHandler for an Association
*
* @return Array of AssociationHandler instances
*/
public AssociationHandler getAssociationHandler(final Association association) {
AssociationHandler handler = null;
AssociationHandler[] handlers = getAssociationHandlers();
for (AssociationHandler handler1 : handlers) {
if (handler1.getAssociation().equals(association)) {
handler = handler1;
break;
}
}
return handler;
}
/**
* Get all AssociationHandler instances
*
* @return Array of AssociationHandler instances
*/
private AssociationHandler[] getAssociationHandlers() {
ArrayList<AssociationHandler> list = new ArrayList<>();
synchronized(associationHandlers) {
for (AssociationHandler ah : associationHandlers) {
list.add(ah);
}
}
return list.toArray(new AssociationHandler[list.size()]);
}
/**
* Get all AssociationDescriptor instances
*
* @return Array of AssociationDescriptor instances
*/
private AssociationDescriptor[] getAssociationDescriptors() {
ArrayList<AssociationDescriptor> list = new ArrayList<>();
synchronized(associationHandlers) {
for (AssociationHandler ah : associationHandlers) {
list.add(ah.associationDescriptor);
}
}
return list.toArray(new AssociationDescriptor[list.size()]);
}
/**
* Get all AssociationListener instances
*
* @return An Array of AssociationListener
* instances. A new array is allocated each time. If there are no
* AssociationListener instances a zero-length array is returned
*/
private AssociationListener<?>[] getAssociationListeners() {
AssociationListener[] aListeners;
synchronized(listeners) {
aListeners = listeners.toArray(new AssociationListener[listeners.size()]);
}
return aListeners;
}
/**
* Create an AssociationHandler
*
* @param aDesc The AssociationDescriptor to create an AssociationHandler
* for
*
* @return An AssociationHandler for the AssociationDescriptor. A new
* AssociationHandler is created each time this method is called
*
* @throws IllegalArgumentException if the AssociationDescriptor is null
*/
private AssociationHandler createAssociationHandler(final AssociationDescriptor aDesc) {
if(aDesc == null)
throw new IllegalArgumentException("associationDescriptor is null");
return new AssociationHandler(aDesc);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public <T> Association<T> addAssociationDescriptor(final AssociationDescriptor aDesc) {
if(aDesc == null)
throw new IllegalArgumentException("The AssociationDescriptor cannot be null");
Association<T> association;
AssociationHandler handler = getAssociationHandler(aDesc);
if (handler!=null) {
logger.warn("Already managing [{}] for [{}]", aDesc.toString(), clientName);
association = handler.getAssociation();
} else {
if (aDesc.getAssociationType()==AssociationType.REQUIRES)
numRequires.incrementAndGet();
synchronized (associationHandlers) {
handler = createAssociationHandler(aDesc);
associationHandlers.add(handler);
}
handler.exec();
association = handler.getAssociation();
}
return association;
}
/**
* Checks to see if the service should be advertised.
*/
public void checkAdvertise() {
listener.checkAdvertise();
}
/**
* {@inheritDoc}
*/
public List<Association<?>> addAssociationDescriptors(final AssociationDescriptor... aDescs) {
if(aDescs == null)
throw new IllegalArgumentException("The AssociationDescriptor cannot be null");
List<AssociationHandler> newHandlers = new ArrayList<>();
List<Association<?>> associations = new ArrayList<>();
for (AssociationDescriptor aDesc : aDescs) {
AssociationHandler handler = getAssociationHandler(aDesc);
if (handler!=null) {
logger.warn("Already managing [{}] for [{}]", aDesc.toString(), clientName);
associations.add(handler.getAssociation());
continue;
}
if (aDesc.getAssociationType()==AssociationType.REQUIRES)
numRequires.incrementAndGet();
synchronized (associationHandlers) {
handler = createAssociationHandler(aDesc);
associationHandlers.add(handler);
newHandlers.add(handler);
}
}
for (AssociationHandler handler : newHandlers) {
handler.exec();
associations.add(handler.getAssociation());
}
return Collections.unmodifiableList(associations);
}
/*
* Determine if we already have an association
*/
private AssociationHandler getAssociationHandler(final AssociationDescriptor aDesc) {
AssociationHandler aHandler = null;
synchronized(associationHandlers) {
for (AssociationHandler ah : associationHandlers) {
if (ah.associationDescriptor.equals(aDesc)) {
aHandler = ah;
break;
}
}
}
return aHandler;
}
/**
* Internal AssociationListener to control advertise and unadvertise
* support
*/
@SuppressWarnings("unchecked")
class Listener implements AssociationListener {
private final List<Association> requiredAssociations = Collections.synchronizedList(new ArrayList<>());
private AssociationInjector associationInjector;
AssociationInjector getAssociationInjector() {
return associationInjector;
}
void addRequiredAssociation(final Association association) {
requiredAssociations.add(association);
}
/*
* Create injector
*/
private void createAssociationInjector(final Object backend) throws IntrospectionException {
if(associationInjector==null)
associationInjector = new AssociationInjector(backend);
else
associationInjector.setBackend(backend);
associationInjector.setCallerClassLoader(callerCL);
}
/*
* @see org.rioproject.associations.AssociationListener#discovered
*/
public void discovered(final Association assoc, final Object service) {
if(numRequires.get() > 0) {
AssociationType aType = assoc.getAssociationType();
if(aType.equals(AssociationType.REQUIRES)) {
if(!requiredAssociations.contains(assoc)) {
requiredAssociations.add(assoc);
}
if(associationInjector!=null) {
associationInjector.discovered(assoc, service);
}
checkAdvertise();
}
} else {
if(associationInjector!=null)
associationInjector.discovered(assoc, service);
checkAdvertise();
}
}
/*
* Check to see if the service can be advertised. If all required
* associations have been satisfied, and we're not advertised, then
* advertise the service
*/
void checkAdvertise() {
logger.trace("Check advertise for [{}] advertised={}, numRequires={}, requiredAssociations.size()={}",
clientName, advertised.get(), numRequires, requiredAssociations.size());
if((requiredAssociations.size() >= numRequires.get()) && !advertised.get()) {
if(context!=null) {
Map<String, Object> configParms = context.getServiceBeanConfig().getConfigurationParameters();
if(!configParms.containsKey(Constants.STARTING)) {
advertise();
} else {
logger.debug("[{}] is still starting, do not advertise", clientName);
context.getServiceElement().setAutoAdvertise(true);
advertisePending.set(true);
}
} else {
logger.debug("DefaultAssociationManagement created without ServiceBeanContext, unable to verify service {} " +
"lifecycle. Proceeding with service advertisement", clientName);
advertise();
}
} else if((requiredAssociations.size() < numRequires.get()) && advertised.get())
unadvertise();
}
/*
* Check to see if the service should be unadvertised. This check is
* done if the AssociationDescriptors have changed. In this case we
* may have added a requires association, and we need to make sure all
* required associations are satisfied. If they are not, unadvertise,
* otherwise check to make sure the proper state is set
*/
/*void checkUnadvertise() {
if((requiredAssociations.size() < numRequires.get()) && advertised.get())
unadvertise();
}*/
/*
* @see org.rioproject.associations.AssociationListener#changed
*/
public void changed(final Association association, final Object service) {
if(associationInjector!=null)
associationInjector.changed(association, service);
}
/*
* @see org.rioproject.associations.AssociationListener#broken
*/
public void broken(final Association association, final Object service) {
if(associationInjector!=null)
associationInjector.broken(association, service);
if(association.getAssociationType()==AssociationType.REQUIRES) {
requiredAssociations.remove(association);
unadvertise();
}
}
}
/**
* Advertise the ServiceBean
*/
private synchronized void advertise() {
try {
if(control != null && !advertised.get()) {
logger.info("Advertise Service: {}", clientName);
control.advertise();
advertised.set(true);
advertisePending.set(false);
} else {
advertisePending.set(true);
logger.debug("Service [{}] advertisement pending, ServiceBeanControl is null", clientName);
}
} catch(Exception e) {
logger.warn("Failed advertising service: {}", clientName, e);
decrementOnFailedAdvertiseLifecycle();
}
}
/**
* Unadvertise the Service
*/
private void unadvertise() {
logger.info("[{}] unadvertiseOnBroken : {}", clientName, unadvertiseOnBroken.get());
if(unadvertiseOnBroken.get()) {
try {
if(control != null) {
control.unadvertise();
advertised.set(false);
logger.debug("Unadvertise Service : {}", clientName);
} else {
logger.warn("Cannot unadvertise Service [{}], ServiceBeanControl is null", clientName);
}
} catch(Exception e) {
logger.warn("Failed unadvertising Service [{}] while processing Broken Association", clientName, e);
decrementOnFailedAdvertiseLifecycle();
}
}
}
private void decrementOnFailedAdvertiseLifecycle() {
if(context!=null) {
OperationalStringManager manager = context.getServiceBeanManager().getOperationalStringManager();
try {
manager.decrement(context.getServiceBeanManager().getServiceBeanInstance(),
true,
true);
logger.debug("Decremented {}, the instance will not be re-allocated", clientName);
} catch (Exception e1) {
logger.warn("Unable to remove service {}/{}:{} instance from OperationalStringManager",
context.getServiceElement().getOperationalStringName(),
clientName,
context.getServiceElement().getServiceBeanConfig().getInstanceID(), e1);
}
} else {
logger.warn("The ServiceBeanContext is null, unable to remove the service {}", clientName);
}
}
/**
* Notify listeners on discovery
*
* @param association The Association
* @param service The discovered service
*/
@SuppressWarnings("unchecked")
private void notifyOnDiscovery(final Association association, final Object service) {
AssociationListener[] listeners = getAssociationListeners();
logger.trace("[{}] DISCOVERED [{}], Notify [{}] listeners", clientName, association.getName(), listeners.length+1);
try {
for (AssociationListener al : listeners) {
if(checkAssociationListener(al, association)) {
al.discovered(association, service);
}
}
} finally {
listener.discovered(association, service);
}
}
/**
* Notify listeners on Association change
*
* @param association The Association
* @param service The discovered service
*/
@SuppressWarnings("unchecked")
private void notifyOnChange(final Association association, final Object service) {
AssociationListener[] listeners = getAssociationListeners();
logger.trace("[{}] CHANGED [{}], Notify [{}] listeners", clientName, association.getName(), listeners.length);
try {
for (AssociationListener al : listeners) {
if(checkAssociationListener(al, association))
al.changed(association, service);
}
} finally {
listener.changed(association, service);
}
}
/**
* Notify listeners that the Association is broken
*
* @param association The Association
* @param service The discovered service
*/
@SuppressWarnings("unchecked")
private void notifyOnBroken(final Association association, final Object service) {
AssociationListener[] listeners = getAssociationListeners();
logger.trace("[{}] BROKEN [{}], Type={}, Notify [{}] listeners",
clientName, association.getName(), association.getAssociationType().toString(), listeners.length);
try {
for (AssociationListener al : listeners) {
if(checkAssociationListener(al, service))
al.broken(association, service);
}
} finally {
listener.broken(association, service);
}
/* If we have a colocated association that is broken destroy the
* service */
AssociationHandler handler = getAssociationHandler(association);
boolean colocatedDestroy = true;
if(handler!=null) {
try {
colocatedDestroy = (Boolean) getConfiguration().getEntry(CONFIG_COMPONENT,
"colocatedDestroy",
boolean.class,
true);
} catch(Exception e) {
logger.warn("Getting {}.colocatedDestroy property from association configuration", CONFIG_COMPONENT, e);
}
}
if(association.getAssociationType()==AssociationType.COLOCATED &&
colocatedDestroy) {
try {
logger.trace("[{}] Colocated Association to [{}] BROKEN, terminate service",
clientName, association.getName());
if(context==null) {
logger.debug("ServiceBeanContext is null, unable to destroy {}", clientName);
return;
}
Object proxy = context.getServiceBeanManager().getServiceBeanInstance().getService();
if(proxy instanceof Administrable) {
Object admin = ((Administrable)proxy).getAdmin();
if(admin instanceof DestroyAdmin) {
((DestroyAdmin)admin).destroy();
} else {
logger.warn("{} admin object does not implement {} access, unable to terminate",
clientName, DestroyAdmin.class.getName());
}
} else {
logger.warn("{} does not provide an {} access, unable to terminate",
clientName, Administrable.class.getName());
}
} catch(Exception e) {
logger.warn("{} Destroying from broken colocated asssociation", clientName, e);
}
}
}
/*
* Tests if class <code>c1</code> is equal to, or a superclass of, class
* <code>c2</code>, using the class equivalence semantics of the lookup
* service: same name.
*/
private boolean isAssignableFrom(final Class<?> c1, final Class<?> c2) {
if(c1.isAssignableFrom(c2))
return true;
String n1 = c1.getName();
for(Class sup = c2; sup != null; sup = sup.getSuperclass()) {
if(n1.equals(sup.getName()))
return true;
}
return false;
}
/**
* The AssociationHandler handle an Association created from
* AssociationDescriptor.
*/
public class AssociationHandler extends ServiceDiscoveryAdapter implements FaultDetectionListener <ServiceID> {
/** The AssociationDescriptor */
private final AssociationDescriptor associationDescriptor;
/** Number of instances of the associated service */
private int instances;
/**
* Table of service IDs to FaultDetectionHandler instances, one for
* each service
*/
private final Map<ServiceID, FaultDetectionHandler> fdhTable = new Hashtable<>();
/** The Association for the AssociationHandler */
private final Association association;
/** The LookupCache for the ServiceDiscoveryManager */
private LookupCache lCache;
/** The LookupServiceHandler */
private LookupServiceHandler lookupServiceHandler;
/** A ProxyPreparer for discovered services */
private ProxyPreparer proxyPreparer;
private ServiceTemplate template;
/**
* Create an AssociationHandler
*
* @param assocDesc The AssociationDescriptor describing the
* attributes of the Association
*/
AssociationHandler(final AssociationDescriptor assocDesc) {
associationDescriptor = assocDesc;
association = new DefaultAssociation(associationDescriptor);
/* If the association has a version declared and no AssociationMatchFilter, add a VersionMatchFilter */
if(associationDescriptor.getVersion()!=null && associationDescriptor.getAssociationMatchFilter()==null) {
associationDescriptor.setAssociationMatchFilter(new VersionMatchFilter());
if(logger.isInfoEnabled()) {
logger.info("[{}] Added a {}, for [{}]",
clientName, VersionMatchFilter.class.getName(), association.getName());
}
}
logger.trace("DefaultAssociationManagement created for [{}], [{}]", clientName, assocDesc);
}
AssociationDescriptor getAssociationDescriptor() {
return associationDescriptor;
}
/**
* Begin handling the association
*/
@SuppressWarnings("unchecked")
protected void exec() {
if(!associationDescriptor.isLazyInject()) {
listener.getAssociationInjector().injectEmpty(association);
listener.addRequiredAssociation(association);
listener.checkAdvertise();
}
try {
boolean lookupService = false;
/* See if the association is a lookup service */
String[] iFaces = associationDescriptor.getInterfaceNames();
for (String iFace : iFaces) {
if (iFace.equals("net.jini.core.lookup.ServiceRegistrar")) {
lookupService = true;
break;
}
}
/* Process colocated associations without discovery management */
if(association.getAssociationType()==AssociationType.COLOCATED && container!=null) {
try {
handleColocatedServiceInjection(this);
} catch (Exception e) {
e.printStackTrace();
}
//new ColocatedListener();
} else if(!lookupService) {
try {
proxyPreparer = (ProxyPreparer)getConfiguration().getEntry(CONFIG_COMPONENT,
"proxyPreparer",
ProxyPreparer.class,
new BasicProxyPreparer());
} catch (ConfigurationException e) {
logger.warn("Could not create ProxyPreparer from configuration, using default", e);
proxyPreparer = new BasicProxyPreparer();
}
logger.trace("Association [{}] ProxyPreparer : {}",
associationDescriptor.getName(), proxyPreparer.getClass().getName());
template = JiniClient.getServiceTemplate(associationDescriptor, callerCL);
logger.trace("Created ServiceTemplate {}", template.toString());
LookupCachePool lcPool = LookupCachePool.getInstance();
String sharedName = associationDescriptor.getOperationalStringName();
lCache = lcPool.getLookupCache(sharedName, associationDescriptor.getGroups(), associationDescriptor.getLocators(), template);
lCache.addListener(this);
logger.debug("DefaultAssociationManagement for [{}], obtained LookupCache for [{}]",
clientName, associationDescriptor.getName());
} else {
lookupServiceHandler = new LookupServiceHandler(associationDescriptor, this, getConfiguration());
}
} catch(IllegalStateException e) {
logger.warn("Creating an AssociationHandler, {}", e.getMessage());
} catch(Exception e) {
logger.error("Creating an AssociationHandler", e);
}
}
ServiceTemplate getServiceTemplate() {
return template;
}
/**
* Stop the AssociationHandler, terminating the FaultDetectionListener
* and ServiceDiscoveryManager
*/
void terminate() {
association.terminate();
if(lCache!=null) {
try {
lCache.removeListener(this);
} catch (Throwable t) {
logger.warn("Exception {} removing Listener from LookupCache", t.getClass().getName());
}
}
Set<ServiceID> keySet = fdhTable.keySet();
for (ServiceID aKeySet : keySet) {
FaultDetectionHandler fdh = fdhTable.get(aKeySet);
if (fdh != null) {
fdh.terminate();
}
}
if(lookupServiceHandler != null)
lookupServiceHandler.terminate();
}
/*
* Create a ServiceItem from a ServiceBeanInstance
*/
private ServiceItem makeServiceItem(final ServiceBeanInstance instance)
throws IOException, ClassNotFoundException {
Uuid uuid = instance.getServiceBeanID();
ServiceID serviceID = new ServiceID(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits());
Entry[] attrs = null;
Object service = instance.getService();
if(service instanceof Administrable) {
try {
Object admin = ((Administrable)service).getAdmin();
if(admin instanceof JoinAdmin) {
attrs = ((JoinAdmin)admin).getLookupAttributes();
}
} catch(RemoteException e) {
logger.warn("Getting attributes from [{}]", getAssociation().getName(), e);
}
}
return new ServiceItem(serviceID, service, attrs);
}
/**
* Notification that a service has been discovered. This method will
* first marshall then unmarshal the discovered ServiceItem (and
* hence all contained classes) using the caller's classloader.
*
* Since we are using the LookupCachePool, the "original" proxy
* reference may have been obtained using a sibling class loader, and
* the proxy may not be "usable" by the caller.
*
* This method will additionally use the configured ProxyPreparer to
* prepare the discovered proxy. If no ProxyPreparer has been
* configured, the default proxy preparer (BasicProxyPreparer that
* does nothing but return the original proxy) is used
*
* @param sdEvent The ServiceDiscoveryEvent
*/
public void serviceAdded(final ServiceDiscoveryEvent sdEvent) {
ServiceItem item = sdEvent.getPostEventServiceItem();
if(item.service==null) {
logger.warn("[{}] serviceAdded [{}], service is null, abort notification",
clientName, association.getName());
return;
}
if(associationDescriptor.getAssociationMatchFilter()!=null) {
if(!associationDescriptor.getAssociationMatchFilter().check(associationDescriptor, item)) {
return;
}
}
if(callerCL.equals(ClassLoader.getSystemClassLoader())) {
doServiceDiscovered(item);
return;
}
final Thread currentThread = Thread.currentThread();
final ClassLoader cCL = AccessController.doPrivileged(
(PrivilegedAction<ClassLoader>) currentThread::getContextClassLoader);
try {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
currentThread.setContextClassLoader(callerCL);
return null;
});
item = new MarshalledObject<>(item).get();
item.service = proxyPreparer.prepareProxy(item.service);
doServiceDiscovered(item);
} catch(Exception e) {
logger.warn("(Un)Marshalling ServiceItem", e);
} finally {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
currentThread.setContextClassLoader(cCL);
return null;
});
}
}
private void doServiceDiscovered(ServiceItem item) {
try {
serviceDiscovered(item);
} catch(Exception e) {
if(!ThrowableUtil.isRetryable(e)) {
lCache.discard(item.service);
}
logger.trace("Handling service discovery, unable to attach FDH", e);
}
}
/**
* Add a service
*
* @param item The service being added
*
* @throws Exception if the fault detection handler cannot be attached
*/
void serviceDiscovered(final ServiceItem item) throws Exception {
logger.trace("[{}] Service Discovered [{}] CL=[{}]",
clientName, item.service.getClass().getName(),
item.service.getClass().getClassLoader().toString());
if(association.addServiceItem(item)) {
Association.State state = association.getState();
if(state == Association.State.PENDING || Association.State.BROKEN == state)
association.setState(Association.State.DISCOVERED);
increment();
setFaultDetectionHandler(item.service, item.serviceID);
notifyOnDiscovery(association, item.service);
} else {
logger.warn("Already added {}", item.service.getClass().getName());
}
}
/**
* @see org.rioproject.impl.fdh.FaultDetectionListener#serviceFailure
*/
@SuppressWarnings("unchecked")
public void serviceFailure(final Object proxy, final ServiceID sid) {
decrement();
lCache.discard(proxy);
ServiceItem item = association.removeService(proxy);
if(item!=null) {
notifyOnFailure(item);
if(logger.isDebugEnabled())
logger.debug("[{}] Service FAILURE : {}", clientName, item.service.getClass().getName());
} else {
StringBuilder builder = new StringBuilder();
builder.append("[").append(clientName).append("] ");
builder.append("Unable to notify Listeners on failure, returned ServiceItem is null for association ");
builder.append(association.getName());
builder.append(". The Association reports ").append(association.getServiceCount()).append(" services");
logger.warn(builder.toString());
}
fdhTable.remove(sid);
}
/**
* Notify on service failure
*
* @param item The service that has failed
*/
void notifyOnFailure(final ServiceItem item) {
if(instances >0) {
association.setState(Association.State.CHANGED);
notifyOnChange(association, item.service);
} else {
association.setState(Association.State.BROKEN);
notifyOnBroken(association, item.service);
}
}
/**
* Get the number of instances
*
* @return instance count
*/
protected int instances() {
return instances;
}
/**
* Increment instances
*/
protected void increment() {
instances++;
}
/**
* Decrement instances
*/
protected void decrement() {
instances--;
}
/**
* Get the Association AssociationHandler
*
* @return The Association instance for this AssociationHandler
*/
public Association getAssociation() {
return association;
}
/**
* Set the FaultDetectionHandler for this AssociationHandler
*
* @param service The Object used to communicate to the service
* @param serviceID Unique service identifier
*
* @throws Exception if the fault detection handler cannot be attached
*/
void setFaultDetectionHandler(final Object service, final ServiceID serviceID) throws Exception {
FaultDetectionHandler<ServiceID> fdh = null;
if(container!=null) {
ServiceBeanDelegate delegate =
container.getServiceBeanDelegate(UuidFactory.create(serviceID.getMostSignificantBits(),
serviceID.getLeastSignificantBits()));
if(delegate!=null) {
logger.debug("Create FDH to monitor colocated service [{}]", association.getName());
fdh = new ColocatedListener();
}
}
if(fdh==null) {
logger.debug("Create FDH to monitor external service [{}]", association.getName());
ClassLoader cl = service.getClass().getClassLoader();
fdh = FaultDetectionHandlerFactory.getFaultDetectionHandler(associationDescriptor, cl);
}
registerFaultDetectionHandler(service, serviceID, fdh);
}
/**
* Register a FaultDetectionHandler and have it start monitoring
*
* @param service The service proxy
* @param serviceID The service id
* @param fdh The FaultDetectionHandler
*
* @throws Exception If error(s) occur
*/
void registerFaultDetectionHandler(final Object service,
final ServiceID serviceID,
final FaultDetectionHandler<ServiceID> fdh) throws Exception {
fdh.setLookupCache(lCache);
fdh.register(this);
fdh.monitor(service, serviceID);
fdhTable.put(serviceID, fdh);
}
}
/*
* Load interfaces from an AssociationDescriptor
*/
private Class[] loadAssociatedInterfaces(final AssociationDescriptor a, final ClassLoader cl)
throws ClassNotFoundException {
String[] iNames = a.getInterfaceNames();
Class[] interfaces = new Class[iNames.length];
for(int i = 0; i < interfaces.length; i++) {
interfaces[i] = Class.forName(iNames[i], false, cl);
}
return interfaces;
}
/*
* Handle colocated service injection
*/
private void handleColocatedServiceInjection(final AssociationHandler aHandler) throws Exception {
AssociationDescriptor aDesc = aHandler.getAssociationDescriptor();
ServiceBeanInstance[] instances = container.getServiceBeanInstances(null);
final Thread currentThread = Thread.currentThread();
final ClassLoader cCL =
AccessController.doPrivileged(
(PrivilegedAction<ClassLoader>) currentThread::getContextClassLoader);
try {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
currentThread.setContextClassLoader(callerCL);
return null;
});
for (ServiceBeanInstance instance : instances) {
Object service = instance.getService();
//check name
if (aHandler.associationDescriptor.matchOnName()) {
boolean hailMaryCheck = false;
if (service instanceof Administrable) {
Administrable admin = (Administrable) service;
Object adminObject = admin.getAdmin();
if (adminObject instanceof JoinAdmin) {
JoinAdmin joinAdmin = (JoinAdmin) adminObject;
Entry[] attrs = joinAdmin.getLookupAttributes();
boolean matched = false;
for (Entry attr : attrs) {
if (attr instanceof Name) {
if (aDesc.getName().equals(((Name) attr).name)) {
matched = true;
break;
}
}
}
if (!matched) {
// name does not match, move on to
// next instance
continue;
}
} else {
hailMaryCheck = true;
logger.debug("DefaultAssociationManagement for [{}], associated service proxy " +
"for colocated service does not implement {}, using declared service name",
clientName, JoinAdmin.class.getName());
}
} else {
hailMaryCheck = true;
logger.debug("DefaultAssociationManagement for [{}], associated service proxy for" +
"colocated service does not implement {}, using declared service name",
clientName, Administrable.class.getName());
}
if (hailMaryCheck) {
if (!aDesc.getName().equals(
instance.getServiceBeanConfig().getName())) {
//name does not match, move on to next instance
continue;
}
}
}
Class[] interfaces = loadAssociatedInterfaces(aDesc, callerCL);
if (isAssignableFrom(interfaces[0],service.getClass())) {
ServiceItem item = aHandler.makeServiceItem(instance);
aHandler.serviceDiscovered(item);
}
}
} finally {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
currentThread.setContextClassLoader(cCL);
return null;
});
}
}
/**
* This class handles the condition when the association is a Jini Lookup
* Service
*/
private static class LookupServiceHandler implements DiscoveryListener {
/** An instance of DiscoveryManagement */
DiscoveryManagement dm;
/**
* An instance of the AssociationHandler to delegate association
* actions to
*/
private final AssociationHandler handler;
/** Table of ServiceRegistrar instances to ServiceIDs */
private final Map<ServiceRegistrar, ServiceID> registrarTable = new HashMap<>();
/** A ProxyPreparer for discovered services */
ProxyPreparer proxyPreparer;
/**
* Create a new LookupServiceHandler
*
* @param aDesc The associationDesc
* @param handler The AssociationHandler to delegate association
* actions to
* @param config Configuration to use when discovering lookup services
*/
LookupServiceHandler(final AssociationDescriptor aDesc,
final AssociationHandler handler,
final Configuration config) {
this.handler = handler;
try {
DiscoveryManagementPool discoPool = DiscoveryManagementPool.getInstance();
dm = discoPool.getDiscoveryManager(aDesc.getOperationalStringName(),
aDesc.getGroups(),
aDesc.getLocators());
dm.addDiscoveryListener(this);
proxyPreparer = (ProxyPreparer)config.getEntry(CONFIG_COMPONENT,
"proxyPreparer",
ProxyPreparer.class,
new BasicProxyPreparer());
logger.debug("Association [{}] ProxyPreparer: {}",
aDesc.getName(), proxyPreparer.getClass().getName());
} catch(Exception e) {
logger.error("Creating LookupServiceHandler", e);
}
}
/**
* Stop listening for DiscoveryEvents
*/
void terminate() {
dm.removeDiscoveryListener(this);
}
/**
* This method is invoked as ServiceRegistrar instances are discovered.
* This method uses the configured ProxyPreparer to prepare the
* discovered proxy. If no ProxyPreparer has been configured, the
* default proxy preparer (BasicProxyPreparer that does nothing but
* return the original proxy) is used
*
* @see net.jini.discovery.DiscoveryListener#discovered
*/
public void discovered(DiscoveryEvent dEvent) {
ServiceRegistrar[] registrars = dEvent.getRegistrars();
for (ServiceRegistrar registrar : registrars) {
try {
ServiceRegistrar sr = (ServiceRegistrar) proxyPreparer.prepareProxy(registrar);
registrarDiscovered(sr);
} catch (RemoteException e) {
logger.error("Preparing ServiceRegistrar Proxy", e);
}
}
}
/**
* Handle the discovery of a ServiceRegistrar
*
* @param registrar The ServiceRegistrar that has been discovered
*/
private void registrarDiscovered(ServiceRegistrar registrar) {
try {
ServiceID serviceID = registrar.getServiceID();
ServiceTemplate template = new ServiceTemplate(serviceID, null, null);
ServiceMatches matches = registrar.lookup(template, 1);
if(matches.items.length > 0) {
registrarTable.put(registrar, serviceID);
handler.serviceDiscovered(matches.items[0]);
}
} catch(Exception e) {
logger.error("Adding Registrar", e);
}
}
/**
* @see net.jini.discovery.DiscoveryListener#discarded
*/
public void discarded(DiscoveryEvent dEvent) {
ServiceRegistrar[] registrars = dEvent.getRegistrars();
for (ServiceRegistrar registrar : registrars) {
ServiceID serviceID = registrarTable.remove(registrar);
handler.serviceFailure(registrar, serviceID);
}
}
}
/**
* Listen for colocated service removals
*/
private class ColocatedListener implements FaultDetectionHandler <ServiceID>, ServiceBeanContainerListener {
private Uuid uuid;
private ServiceID serviceID;
private Object service;
private FaultDetectionListener<ServiceID> listener;
ColocatedListener() {
container.addListener(this);
}
public void serviceInstantiated(ServiceRecord record) {
}
public void serviceDiscarded(ServiceRecord record) {
if(uuid.equals(record.getServiceID()))
listener.serviceFailure(service, serviceID);
}
public void configure(Properties p) {
}
@Override public void setLookupCache(LookupCache lCache) {
}
public void register(FaultDetectionListener<ServiceID> listener) {
this.listener = listener;
}
public void unregister(FaultDetectionListener listener) {
}
public void monitor(Object service, ServiceID id) {
serviceID = id;
uuid = UuidFactory.create(serviceID.getMostSignificantBits(), serviceID.getLeastSignificantBits());
this.service = service;
}
public void terminate() {
container.removeListener(this);
}
}
}