jadex.bridge.service.component.RequiredServicesComponentFeature Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jadex-platform-bridge Show documentation
Show all versions of jadex-platform-bridge Show documentation
Jadex bridge is a base package for kernels and platforms, i.e., it is used by both and provides commonly used interfaces and classes for active components and their management.
package jadex.bridge.service.component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jadex.base.Starter;
import jadex.bridge.IInternalAccess;
import jadex.bridge.SFuture;
import jadex.bridge.component.ComponentCreationInfo;
import jadex.bridge.component.INFPropertyComponentFeature;
import jadex.bridge.component.impl.AbstractComponentFeature;
import jadex.bridge.modelinfo.ConfigurationInfo;
import jadex.bridge.modelinfo.IModelInfo;
import jadex.bridge.modelinfo.NFRPropertyInfo;
import jadex.bridge.nonfunctional.AbstractNFProperty;
import jadex.bridge.nonfunctional.INFMixedPropertyProvider;
import jadex.bridge.nonfunctional.INFProperty;
import jadex.bridge.service.IService;
import jadex.bridge.service.IServiceIdentifier;
import jadex.bridge.service.RequiredServiceBinding;
import jadex.bridge.service.RequiredServiceInfo;
import jadex.bridge.service.ServiceIdentifier;
import jadex.bridge.service.ServiceScope;
import jadex.bridge.service.component.interceptors.FutureFunctionality;
import jadex.bridge.service.search.IServiceRegistry;
import jadex.bridge.service.search.MultiplicityException;
import jadex.bridge.service.search.ServiceEvent;
import jadex.bridge.service.search.ServiceNotFoundException;
import jadex.bridge.service.search.ServiceQuery;
import jadex.bridge.service.search.ServiceQuery.Multiplicity;
import jadex.bridge.service.search.ServiceRegistry;
import jadex.bridge.service.types.registry.ISearchQueryManagerService;
import jadex.bridge.service.types.registry.SlidingCuckooFilter;
import jadex.commons.MethodInfo;
import jadex.commons.SReflect;
import jadex.commons.SUtil;
import jadex.commons.TimeoutException;
import jadex.commons.Tuple2;
import jadex.commons.future.Future;
import jadex.commons.future.IFuture;
import jadex.commons.future.IIntermediateFuture;
import jadex.commons.future.IIntermediateResultListener;
import jadex.commons.future.ISubscriptionIntermediateFuture;
import jadex.commons.future.ITerminableFuture;
import jadex.commons.future.ITerminableIntermediateFuture;
import jadex.commons.future.IntermediateEmptyResultListener;
import jadex.commons.future.IntermediateFuture;
import jadex.commons.future.SubscriptionIntermediateDelegationFuture;
import jadex.commons.future.SubscriptionIntermediateFuture;
import jadex.commons.future.TerminableFuture;
import jadex.commons.future.TerminableIntermediateFuture;
import jadex.commons.future.TerminationCommand;
import jadex.javaparser.SJavaParser;
/**
* Feature for provided services.
*/
public class RequiredServicesComponentFeature extends AbstractComponentFeature implements IRequiredServicesFeature, IInternalServiceMonitoringFeature, IInternalRequiredServicesFeature
{
/** Marker for duplicate declarations of same type. */
private static final RequiredServiceInfo DUPLICATE_SERVICE_TYPE_MARKER = new RequiredServiceInfo();
//-------- attributes --------
/** The required service infos. */
protected Map requiredserviceinfos;
//-------- monitoring attributes --------
/** The current subscriptions. */
protected Set> subscriptions;
protected ISearchQueryManagerService sqms;
protected List, SubscriptionIntermediateDelegationFuture>>> delayedremotequeries;
//-------- constructors --------
/**
* Factory method constructor for instance level.
*/
public RequiredServicesComponentFeature(IInternalAccess component, ComponentCreationInfo cinfo)
{
super(component, cinfo);
}
/**
* Init the required services
*/
public IFuture init()
{
ServiceQuery query = new ServiceQuery<>(ISearchQueryManagerService.class);
//UnresolvedServiceInvocationHandler h = new UnresolvedServiceInvocationHandler(component, query);
//sqms = (ISearchQueryManagerService) ProxyFactory.newProxyInstance(getComponent().getClassLoader(), new Class[]{IService.class, ISearchQueryManagerService.class}, h);
sqms = getLocalService(new ServiceQuery<>(query).setMultiplicity(0));
if(sqms == null)
{
delayedremotequeries = new ArrayList<>();
ISubscriptionIntermediateFuture sqmsfut = addQuery(query);
sqmsfut.addResultListener(new IntermediateEmptyResultListener()
{
public void intermediateResultAvailable(ISearchQueryManagerService result)
{
//System.out.println("ISearchQueryManagerService "+result);
if(sqms == null)
{
sqms = result;
sqmsfut.terminate();
for (Tuple2, SubscriptionIntermediateDelegationFuture>> sqi : delayedremotequeries)
{
ISubscriptionIntermediateFuture> dfut = addQuery(sqi.getFirstEntity());
FutureFunctionality.connectDelegationFuture(sqi.getSecondEntity(), dfut);
}
delayedremotequeries = null;
}
}
});
}
/*else
{
System.out.println("directly found ISearchQueryManagerService");
}*/
IModelInfo model = getComponent().getModel();
ClassLoader cl = getComponent().getClassLoader();
String config = getComponent().getConfiguration();
// Required services. (Todo: prefix for capabilities)
RequiredServiceInfo[] ms = model.getServices();
Map sermap = new LinkedHashMap();
for(int i=0; i nfprops = rsi.getNFRProperties();
// if(nfprops!=null)
// {
// INFMixedPropertyProvider nfpp = getRequiredServicePropertyProvider(null); // null for unbound
//
// for(NFRPropertyInfo nfprop: nfprops)
// {
// MethodInfo mi = nfprop.getMethodInfo();
// Class> clazz = nfprop.getClazz().getType(cl, model.getAllImports());
// INFProperty, ?> nfp = AbstractNFProperty.createProperty(clazz, getComponent(), null, nfprop.getMethodInfo());
// if(mi==null)
// {
// nfpp.addNFProperty(nfp);
// }
// else
// {
// nfpp.addMethodNFProperty(mi, nfp);
// }
// }
// }
// }
return IFuture.DONE;
}
/**
* Check if the feature potentially executed user code in body.
* Allows blocking operations in user bodies by using separate steps for each feature.
* Non-user-body-features are directly executed for speed.
* If unsure just return true. ;-)
*/
public boolean hasUserBody()
{
return false;
}
/**
* Called when the feature is shutdowned.
*/
public IFuture shutdown()
{
// Remove the persistent queries
ServiceRegistry.getRegistry(component).removeQueries(getComponent().getId());
return IFuture.DONE;
}
/**
* Add required services for a given prefix.
* @param prefix The name prefix to use.
* @param required services The required services to set.
*/
protected void addRequiredServiceInfos(RequiredServiceInfo[] requiredservices)
{
// if(shutdowned)
// throw new ComponentTerminatedException(id);
if(requiredservices!=null && requiredservices.length>0)
{
if(this.requiredserviceinfos==null)
this.requiredserviceinfos = new HashMap();
for(int i=0; i IFuture getService(String name)
{
return resolveService(getServiceQuery(getServiceInfo(name)), getServiceInfo(name));
}
/**
* Resolve a required service of a given type.
* Asynchronous method for locally as well as remotely available services.
* @param type The service type.
* @return The service.
*/
public IFuture getService(Class type)
{
RequiredServiceInfo info = getServiceInfo(type);
if(info==null)
{
// Convenience case: switch to search when type not declared
return searchService(new ServiceQuery<>(type));
}
else
{
return resolveService(getServiceQuery(info), info);
}
}
/**
* Resolve a required services of a given name.
* Asynchronous method for locally as well as remotely available services.
* @param name The services name.
* @return Each service as an intermediate result and a collection of services as final result.
*/
public ITerminableIntermediateFuture getServices(String name)
{
return resolveServices(getServiceQuery(getServiceInfo(name)), getServiceInfo(name));
}
/**
* Resolve a required services of a given type.
* Asynchronous method for locally as well as remotely available services.
* @param type The services type.
* @return Each service as an intermediate result and a collection of services as final result.
*/
public ITerminableIntermediateFuture getServices(Class type)
{
RequiredServiceInfo info = getServiceInfo(type);
if(info==null)
{
// Convenience case: switch to search when type not declared
return searchServices(new ServiceQuery<>(type));
}
else
{
return resolveServices(getServiceQuery(info), info);
}
}
/**
* Resolve a declared required service of a given name.
* Synchronous method only for locally available services.
* @param name The service name.
* @return The service.
*/
public T getLocalService(String name)
{
return resolveLocalService(getServiceQuery(getServiceInfo(name)), getServiceInfo(name));
}
/**
* Resolve a required service of a given type.
* Synchronous method only for locally available services.
* @param type The service type.
* @return The service.
*/
public T getLocalService(Class type)
{
RequiredServiceInfo info = getServiceInfo(type);
if(info==null)
{
// Convenience case: switch to search when type not declared
return getLocalService(new ServiceQuery<>(type));
}
else
{
return resolveLocalService(getServiceQuery(info), info);
}
}
/**
* Resolve a required service of a given type.
* Synchronous method only for locally available services.
* @param type The service type.
* @return The service.
*/
public T getLocalService0(Class type)
{
RequiredServiceInfo info = getServiceInfo(type);
if(info==null)
{
// Convenience case: switch to search when type not declared
return getLocalService(new ServiceQuery<>(type).setMultiplicity(Multiplicity.ZERO_ONE));
}
else
{
ServiceQuery sq = (ServiceQuery)getServiceQuery(info).setMultiplicity(Multiplicity.ZERO_ONE);
return resolveLocalService(sq, info);
}
}
/**
* Resolve a required services of a given name.
* Synchronous method only for locally available services.
* @param name The services name.
* @return Each service as an intermediate result and a collection of services as final result.
*/
public Collection getLocalServices(String name)
{
return resolveLocalServices(getServiceQuery(getServiceInfo(name)), getServiceInfo(name));
}
/**
* Resolve a required services of a given type.
* Synchronous method only for locally available services.
* @param type The services type.
* @return Each service as an intermediate result and a collection of services as final result.
*/
public Collection getLocalServices(Class type)
{
RequiredServiceInfo info = getServiceInfo(type);
if(info==null)
{
// Convenience case: switch to search when type not declared
return getLocalServices(new ServiceQuery<>(type));
}
else
{
return resolveLocalServices(getServiceQuery(info), info);
}
}
//-------- methods for searching --------
/**
* Search for matching services and provide first result.
* @param query The search query.
* @return Future providing the corresponding service or ServiceNotFoundException when not found.
*/
public IFuture searchService(ServiceQuery query)
{
return resolveService(query, ServiceQuery.createServiceInfo(query));
}
/**
* Search for matching services and provide first result.
* Synchronous method only for locally available services.
* @param query The search query.
* @return Future providing the corresponding service or ServiceNotFoundException when not found.
*/
public T getLocalService(ServiceQuery query)
{
return resolveLocalService(query, ServiceQuery.createServiceInfo(query));
}
/**
* Search for all matching services.
* @param query The search query.
* @return Future providing the corresponding services or ServiceNotFoundException when not found.
*/
public ITerminableIntermediateFuture searchServices(ServiceQuery query)
{
return resolveServices(query, ServiceQuery.createServiceInfo(query));
}
/**
* Search for all matching services.
* Synchronous method only for locally available services.
* @param query The search query.
* @return Future providing the corresponding services or ServiceNotFoundException when not found.
*/
public Collection getLocalServices(ServiceQuery query)
{
return resolveLocalServices(query, ServiceQuery.createServiceInfo(query));
}
/**
* Performs a sustained search for a service. Attempts to find a service
* for a maximum duration until timeout occurs.
*
* @param query The search query.
* @param timeout Maximum time period to search, -1 for no wait.
* @return Service matching the query, exception if service is not found.
*/
public IFuture searchService(ServiceQuery query, long timeout)
{
Future ret = new Future();
timeout = timeout != 0 ? timeout : Starter.getDefaultTimeout(component.getId());
ISubscriptionIntermediateFuture queryfut = addQuery(query);
queryfut.addResultListener(new IntermediateEmptyResultListener()
{
public void exceptionOccurred(Exception exception)
{
ret.setExceptionIfUndone(exception);
}
public void intermediateResultAvailable(T result)
{
ret.setResultIfUndone(result);
queryfut.terminate();
}
});
long to = timeout;
//isRemote(query) // TODO: only realtime for remote queries?
if(to>0)
{
component.waitForDelay(timeout, Starter.isRealtimeTimeout(getComponent().getId(), true)).then(done ->
{
Multiplicity m = query.getMultiplicity();
if(m.getFrom()>0)
{
queryfut.terminate(new ServiceNotFoundException("Service " + query + " not found in search period " + to));
}
else
{
queryfut.terminate();
}
});
}
return ret;
}
//-------- query methods --------
/**
* Add a query for a declared required service.
* Continuously searches for matching services.
* @param name The name of the required service declaration.
* @return Future providing the corresponding services as intermediate results.
*/
public ISubscriptionIntermediateFuture addQuery(ServiceQuery query, long timeout)
{
SubscriptionIntermediateDelegationFuture ret = new SubscriptionIntermediateDelegationFuture<>();
timeout = timeout != 0 ? timeout : Starter.getDefaultTimeout(component.getId());
ISubscriptionIntermediateFuture queryfut = addQuery(query);
final int[] resultcnt = new int[1];
queryfut.addResultListener(new IIntermediateResultListener()
{
public void resultAvailable(Collection result)
{
for(T r: result)
intermediateResultAvailable(r);
finished();
}
public void exceptionOccurred(Exception exception)
{
ret.setExceptionIfUndone(exception);
}
public void intermediateResultAvailable(T result)
{
resultcnt[0]++;
ret.addIntermediateResultIfUndone(result);
}
public void finished()
{
ret.setFinishedIfUndone();
}
public void maxResultCountAvailable(int max)
{
ret.setMaxResultCount(max);
}
});
long to = timeout;
//isRemote(query)
if(to>0)
{
component.waitForDelay(timeout, Starter.isRealtimeTimeout(component.getId(), true)).then(done ->
{
Exception e;
Multiplicity m = query.getMultiplicity();
if(m.getFrom()>0 && resultcnt[0]0 && resultcnt[0]>m.getTo())
{
e = new MultiplicityException("["+m.getFrom()+"-"+m.getTo()+"]"+", resultcnt="+resultcnt[0]);
}
else
{
e = new TimeoutException(""+to);
//new ServiceNotFoundException("Service " + query + " not found in search period " + to)
}
queryfut.terminate(e);
});
}
return ret;
}
/**
* Add a query for a declared required service.
* Continuously searches for matching services.
* @param name The name of the required service declaration.
* @return Future providing the corresponding services as intermediate results.
*/
public ISubscriptionIntermediateFuture addQuery(String name)
{
return resolveQuery(getServiceQuery(getServiceInfo(name)), getServiceInfo(name));
}
/**
* Add a query for a declared required service.
* Continuously searches for matching services.
* @param type The type of the required service declaration.
* @return Future providing the corresponding services as intermediate results.
*/
public ISubscriptionIntermediateFuture addQuery(Class type)
{
return resolveQuery(getServiceQuery(getServiceInfo(type)), getServiceInfo(type));
}
/**
* Add a service query.
* Continuously searches for matching services.
* @param query The search query.
* @return Future providing the corresponding service or ServiceNotFoundException when not found.
*/
public ISubscriptionIntermediateFuture addQuery(ServiceQuery query)
{
return resolveQuery(query, ServiceQuery.createServiceInfo(query));
}
//-------- event interface --------
/**
* Get the required services.
* @return The required services.
*/
public RequiredServiceInfo[] getServiceInfos()
{
// if(shutdowned)
// throw new ComponentTerminatedException(id);
// Convert to set to remove duplicate entries (name+type) and exclude marker.
Set ret = new LinkedHashSet<>();
if(requiredserviceinfos!=null)
{
for(RequiredServiceInfo info: requiredserviceinfos.values())
{
if(!DUPLICATE_SERVICE_TYPE_MARKER.equals(info))
{
ret.add(info);
}
}
}
return ret.toArray(new RequiredServiceInfo[ret.size()]);
}
/**
* Listen to service call events (call, result and commands).
*/
// Todo: only match specific calls?
// Todo: Commands
public ISubscriptionIntermediateFuture getServiceEvents()
{
if(subscriptions==null)
{
subscriptions = new LinkedHashSet>();
}
@SuppressWarnings("unchecked")
final SubscriptionIntermediateFuture ret = (SubscriptionIntermediateFuture)
SFuture.getNoTimeoutFuture(SubscriptionIntermediateFuture.class, getInternalAccess());
ret.setTerminationCommand(new TerminationCommand()
{
@Override
public void terminated(Exception reason)
{
subscriptions.remove(ret);
if(subscriptions.isEmpty())
{
subscriptions = null;
}
}
});
subscriptions.add(ret);
return ret;
}
/**
* Post a service call event.
*/
public void postServiceEvent(ServiceCallEvent event)
{
if(subscriptions!=null)
{
for(SubscriptionIntermediateFuture sub: subscriptions)
{
sub.addIntermediateResult(event);
}
}
}
/**
* Check if there is someone monitoring.
* To Avoid posting when nobody is listening.
*/
public boolean isMonitoring()
{
return subscriptions!=null;
}
//-------- convenience methods --------
/**
* Get a service raw (i.e. w/o required proxy).
* @return null when not found.
*/
public T getRawService(Class type)
{
try
{
ServiceQuery query = new ServiceQuery<>(type).setMultiplicity(Multiplicity.ZERO_ONE);
query.setRequiredProxyType(ServiceQuery.PROXYTYPE_RAW);
return resolveLocalService(query, ServiceQuery.createServiceInfo(query));
}
catch(ServiceNotFoundException snfe)
{
return null;
}
}
/**
* Get a service raw (i.e. w/o required proxy).
*/
public Collection getRawServices(Class type)
{
ServiceQuery query = new ServiceQuery<>(type);
query.setRequiredProxyType(ServiceQuery.PROXYTYPE_RAW);
return resolveLocalServices(query, ServiceQuery.createServiceInfo(query));
}
//-------- impl/raw methods --------
/**
*
* @param result
* @param info
* @return
*/
protected Object processResult(Object result, RequiredServiceInfo info)
{
if(result instanceof ServiceEvent)
return processServiceEvent((ServiceEvent)result, info);
else if(result instanceof IServiceIdentifier)
return getServiceProxy((IServiceIdentifier)result, info);
else if(result instanceof IService)
return addRequiredServiceProxy((IService)result, info);
else
return result;
}
/**
* Search for matching services and provide first result.
* @param query The search query.
* @param info Used for required service proxy configuration -> null for no proxy.
* @return Future providing the corresponding service or ServiceNotFoundException when not found.
*/
public ITerminableFuture resolveService(ServiceQuery query, RequiredServiceInfo info)
{
enhanceQuery(query, false);
Future ret = null;
// Try to find locally
IServiceIdentifier sid = ServiceRegistry.getRegistry(getInternalAccess()).searchService(query);
if(sid!=null)
{
ret = new TerminableFuture<>();
@SuppressWarnings("unchecked")
T t = (T)getServiceProxy(sid, info);
ret.setResult(t);
}
// If not found -> try to find remotely
else if(isRemote(query) && sqms != null)
{
// ISearchQueryManagerService sqms = getLocalService(new ServiceQuery<>(ISearchQueryManagerService.class).setMultiplicity(Multiplicity.ZERO_ONE));
// if(sqms!=null)
// {
@SuppressWarnings("rawtypes")
ITerminableFuture fut = sqms.searchService(query);
@SuppressWarnings("unchecked")
ITerminableFuture castedfut = (ITerminableFuture) fut;
ret = FutureFunctionality.getDelegationFuture(castedfut, new FutureFunctionality(getComponent().getLogger())
{
@Override
public Object handleResult(Object result) throws Exception
{
// todo: remove after superpeer fix
result = processResult(result, info);
if(result==null)
{
if(query.getMultiplicity().getFrom()!=0)
{
throw new ServiceNotFoundException(query);
}
}
return result;
//return processResult(result, info);
}
});
// }
}
// Not found locally and query not remote or no remote search manager available
if(ret==null)
{
ret = new TerminableFuture<>();
if(query.getMultiplicity().getFrom()==0)
{
ret.setResult(null);
}
else
{
ret.setException(new ServiceNotFoundException(query));
}
}
@SuppressWarnings("unchecked")
ITerminableFuture iret = (ITerminableFuture)ret;
return iret;
}
/**
* Search for matching services and provide first result.
* Synchronous method only for locally available services.
* @param query The search query.
* @param info Used for required service proxy configuration -> null for no proxy.
* @return Future providing the corresponding service or ServiceNotFoundException when not found.
*/
public T resolveLocalService(ServiceQuery query, RequiredServiceInfo info)
{
enhanceQuery(query, false);
IServiceIdentifier sid = ServiceRegistry.getRegistry(getInternalAccess()).searchService(query);
if(sid==null && query.getMultiplicity().getFrom()>0)
throw new ServiceNotFoundException(query);
// Fetches service and wraps result in proxy, if required.
@SuppressWarnings("unchecked")
T ret = sid!=null ? (T)getServiceProxy(sid, info) : null;
return ret;
}
/**
* Search for all matching services.
* @param query The search query.
* @param info Used for required service proxy configuration -> null for no proxy.
* @return Future providing the corresponding services or ServiceNotFoundException when not found.
*/
public ITerminableIntermediateFuture resolveServices(ServiceQuery query, RequiredServiceInfo info)
{
ITerminableIntermediateFuture ret;
// if(query.getServiceType().toString().indexOf("ITransportInfoService")!=-1)
// System.out.println("here");
// Check if remote
// ISearchQueryManagerService sqms = isRemote(query) ? getLocalService(new ServiceQuery<>(ISearchQueryManagerService.class).setMultiplicity(Multiplicity.ZERO_ONE)) : null;
// if(isRemote(query) && sqms==null)
// {
// getComponent().getLogger().warning("No ISearchQueryManagerService found for remote search: "+query);
//// return new TerminableIntermediateFuture<>(new IllegalStateException("No ISearchQueryManagerService found for remote search: "+query));
// }
//final int min = query.getMultiplicity()!=null? query.getMultiplicity().getFrom(): -1;
final int max = query.getMultiplicity()!=null? query.getMultiplicity().getTo(): -1;
final int[] resultcnt = new int[1];
// Local only -> create future, fill results, and set to finished.
if(!isRemote(query) || sqms == null)
{
TerminableIntermediateFuture fut = new TerminableIntermediateFuture<>();
ret = fut;
// Find local matches (also enhances query, hack!?)
Collection locals = resolveLocalServices(query, info);
for(T result: locals)
{
if(max<0 || ++resultcnt[0]<=max)
{
fut.addIntermediateResult(result);
// if next result is not allowed any more
if(max>0 && resultcnt[0]+1>max)
{
// Finish the user side and terminate the source side
fut.setFinishedIfUndone();
Exception reason = new MultiplicityException("Max number of values received: "+max);
fut.terminate(reason);
}
}
else
{
break;
}
}
fut.setFinishedIfUndone();
}
// Find remote matches, if needed
else
{
enhanceQuery(query, true);
SlidingCuckooFilter scf = new SlidingCuckooFilter();
// Search remotely and connect to delegation future.
ITerminableIntermediateFuture remotes = sqms.searchServices(query);
final IntermediateFuture[] futs = new IntermediateFuture[1];
// Combined delegation future for local and remote results.
futs[0] = (IntermediateFuture)FutureFunctionality
.getDelegationFuture(ITerminableIntermediateFuture.class, new ComponentFutureFunctionality(getInternalAccess())
{
@Override
public Object handleIntermediateResult(Object result) throws Exception
{
// Drop result when already in cuckoo filter
if(scf.contains(result.toString()))
{
return DROP_INTERMEDIATE_RESULT;
}
else
{
if(max<0 || ++resultcnt[0]<=max)
{
scf.insert(result.toString());
return processResult(result, info);
}
else
{
//System.out.println("fut drop: "+hashCode());
return DROP_INTERMEDIATE_RESULT;
}
}
}
@Override
public void handleAfterIntermediateResult(Object result) throws Exception
{
if(DROP_INTERMEDIATE_RESULT.equals(result))
return;
// if next result is not allowed any more
if(max>0 && resultcnt[0]+1>max)
{
// Finish the user side and terminate the source side
futs[0].setFinishedIfUndone();
Exception reason = new MultiplicityException("Max number of values received: "+max);
//System.out.println("fut terminate: "+hashCode());
remotes.terminate(reason);
}
}
@Override
public void handleTerminated(Exception reason)
{
//System.out.println("fut terminated: "+hashCode());
super.handleTerminated(reason);
}
@Override
public void handleFinished(Collection