/**
* SAMOA - PROTOCOL FRAMEWORK
* Copyright (C) 2005 Olivier Rütti (EPFL) (olivier.rutti@a3.epfl.ch)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package seqSamoa;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import seqSamoa.exceptions.AlreadyBoundServiceException;
import seqSamoa.exceptions.AlreadyExistingServiceException;
import seqSamoa.exceptions.NotInAComputationException;
import seqSamoa.exceptions.UnboundServiceException;
import uka.transport.Transportable;
import framework.libraries.serialization.TString;
/**
* A class <CODE>Service</CODE> represents a service (Reliable send,
* consensus, ABcast, ..) provided by a {@link seqSamoa.ProtocolModule Protocol}. A
* <CODE>Service</CODE> is bound to an executer which provide the service and
* to many listeners that get the response of th service call. A <CODE>Service
* </CODE> can be subtyped. Imagine a service <t, u> and a service <v, w> such
* that v is a subtype of t and w a subtype of u. Then, we consider service <v,
* w> as a subtype of service <t,u>. However, the class v must define a
* constructor with object of class t as a parameters. An example is available
* for service RPT2PT which is considered as a subtype of UDP. Take also a look
* to protocols HB and Ping in util/groupcomm/fd/ to see how implement protocols
* that can benefit of this feature.
*
* @see ProtocolModule
*/
public class Service<CallParameters, ResponseParameters> {
/* Class representing a service call */
protected class ServiceCall extends AtomicTask {
/* The Service */
protected Service<CallParameters, ResponseParameters> service;
/* The parameters */
protected CallParameters params;
/* The message */
protected Message dmessage;
/* The Service Call or Response */
protected ServiceCallOrResponse cor;
protected ServiceCall(Service<CallParameters, ResponseParameters> service, CallParameters params,
Message dmessage) {
super();
this.service = service;
this.params = params;
this.dmessage = dmessage;
this.cor = ServiceCallOrResponse.createServiceCallOrResponse(service, true);
}
@SuppressWarnings("unchecked")
public void execute(){
// If there is no currentExecuter, throws Exception
if (currentExecuter == null)
throw new UnboundServiceException(this.service);
// Get the class corresponding CallParameters at runtime
Method evaluateMethod = this.service.currentExecuter.getClass().getMethods()[0];
Class classAtRuntime = evaluateMethod.getParameterTypes()[0];
// Transform the parameter if params.class is a super-type of
// CallParameters
if (this.params != null && !this.params.getClass().equals(classAtRuntime)
&& classAtRuntime.getClass().equals(Object.class)) {
try {
// Class[] contains the class of the params
Class[] paramsClass = new Class[1];
paramsClass[0] = this.params.getClass();
// Object[] contains the params
Object[] paramsArray = new Object[1];
paramsArray[0] = this.params;
Constructor constructor = classAtRuntime.getConstructor(paramsClass);
this.params = (CallParameters) constructor.newInstance(paramsArray);
} catch (NoSuchMethodException ex) {
throw new RuntimeException(classAtRuntime
+ " do not define a constructor with"
+ this.params.getClass() + " as a parameters!!!");
} catch (SecurityException ex) {
throw new RuntimeException("Security Exception!!!");
} catch (InstantiationException ex) {
throw new RuntimeException(classAtRuntime
+ " is abstract. Class for parameters"
+ "should not be abstract!!!");
} catch (IllegalAccessException ex) {
throw new RuntimeException(classAtRuntime
+ " does not declare all constructors public");
} catch (IllegalArgumentException ex) {
throw new RuntimeException("Illegal Argument!!!");
} catch (InvocationTargetException ex) {
throw new RuntimeException("The underlying constructor of "
+ classAtRuntime + "throws an exception: "
+ ex.getMessage());
}
}
// Execute interceptors and the executer
if (this.service.boundInterceptors.size() > 0) {
this.currentModule = this.service.boundInterceptors.get(0).parent;
this.service.boundInterceptors.get(0).interceptCall(this.params, this.dmessage);
this.currentModule = null;
} else {
this.currentModule = this.service.currentExecuter.parent;
this.service.currentExecuter.evaluate(this.params, this.dmessage);
this.currentModule = null;
}
}
public ServiceCallOrResponse getCOR() {
return this.cor;
}
public String toString() {
return new String(this.cor + ":"+dmessage);
}
}
/* Class representing a service response */
protected class ServiceResponse extends AtomicTask {
/* The Service */
protected Service<CallParameters, ResponseParameters> service;
/* The Message */
protected Message dmessage;
/* The additional ResponseParameters */
protected ResponseParameters params;
/* The Service Call or Response */
protected ServiceCallOrResponse cor;
protected ServiceResponse(Service<CallParameters, ResponseParameters> service, ResponseParameters infos,
Message dmessage) {
super();
this.service = service;
this.dmessage = dmessage;
this.params = infos;
this.cor = ServiceCallOrResponse.createServiceCallOrResponse(service, false);
}
public void execute(){
int size = this.service.boundInterceptors.size();
if (size > 0) {
this.currentModule = this.service.boundInterceptors.get(size-1).parent;
this.service.boundInterceptors.get(size - 1).interceptResponse(this.params, this.dmessage);
this.currentModule = null;
} else {
TString dest;
Transportable message;
if (this.dmessage == null) {
dest = new TString("NULL");
message = null;
} else {
dest = this.dmessage.dest;
message = this.dmessage.content;
}
if (!dest.equals(new TString("NULL"))) {
if (this.service.allListeners.containsKey(dest)) {
Listener l = this.service.allListeners.get(dest);
this.currentModule = l.parent;
l.evaluate(this.params, message);
} else {
this.service.bufferedMessage.add(this.dmessage);
this.service.bufferedResponseParameters.add(this.params);
}
} else {
Set<TString> allListenersKeys = this.service.allListeners.keySet();
Iterator<TString> it = allListenersKeys.iterator();
while (it.hasNext()) {
TString key = it.next();
Listener l = this.service.allListeners.get(key);
this.currentModule = l.parent;
l.evaluate(this.params, message);
this.currentModule = null;
}
}
}
}
public ServiceCallOrResponse getCOR() {
return this.cor;
}
public String toString() {
return new String(this.cor +":"+dmessage);
}
}
/* Is the corresponding service call critical */
protected final boolean isCallCritical;
/* Is the corresponding service response critical */
protected final boolean isResponseCritical;
/* The name of the service instance */
protected String name;
/* The name of the stack that contains the service */
protected ProtocolStack stack;
/* The current Executer of the service calls */
protected Executer currentExecuter;
/* Map with all Listeners */
protected HashMap<TString, Listener> allListeners;
/* List of all bounded Interceptor */
protected LinkedList<Interceptor> boundInterceptors;
/* List of all buffered Message */
protected LinkedList<Message> bufferedMessage;
/* List of all buffered ResponseParameters */
protected LinkedList<ResponseParameters> bufferedResponseParameters;
/**
* Constructor. By default, both call and responses to/from the service are critical
*
* @param name
* name of the service
* @param stack
* the {@link seqSamoa.ProtocolStack stack} to which the module belongs to
*/
public Service(String name, ProtocolStack stack) throws AlreadyExistingServiceException {
this(name, stack, true, true);
}
/**
* Constructor.
*
* @param name
* name of the service
* @param stack
* the {@link seqSamoa.ProtocolStack stack} to which the module belongs to
*/
public Service(String name, ProtocolStack stack, boolean isCallCritical, boolean isResponseCritical) throws AlreadyExistingServiceException {
super();
this.name = name;
this.stack = stack;
this.isCallCritical = isCallCritical;
this.isResponseCritical = isResponseCritical;
stack.registerService(this);
this.allListeners = new HashMap<TString, Listener>();
this.boundInterceptors = new LinkedList<Interceptor>();
this.bufferedMessage = new LinkedList<Message>();
this.bufferedResponseParameters = new LinkedList<ResponseParameters>();
}
/**
* Return the name of the service
*
* @return name of the service
*/
public String getName() {
return name;
}
/**
* Call the service
*
* @param params
* parameters of the service call
* @param dmessage
* {@link seqSamoa.Message message} with its destination
*
*/
public void call(CallParameters params, Message dmessage) throws NotInAComputationException{
ServiceCall sc = new ServiceCall(this, params, dmessage);
try {
this.stack.scheduler.addInternalTask(sc);
} catch (NotInAComputationException ex) {
throw new NotInAComputationException(new String("Call to service "+sc.service.name));
}
}
/**
* Externally call the service
*
* @param params
* parameters of the service call
* @param dmessage
* {@link seqSamoa.Message message} with its destination
*
* @return
* the id corresponding to the external call
*/
public long externalCall(CallParameters params, Message dmessage) {
ServiceCall sc = new ServiceCall(this, params, dmessage);
return this.stack.scheduler.addExternalTask(sc);
}
/**
* Send the response(s) of the service
*
* @param params
* the parameter of the service response
* @param dmessage
* {@link seqSamoa.Message message} with its destination
*
*/
public void response(ResponseParameters params, Message dmessage) throws NotInAComputationException {
ServiceResponse sr = new ServiceResponse(this, params, dmessage);
try {
this.stack.scheduler.addInternalTask(sr);
} catch (NotInAComputationException ex) {
throw new NotInAComputationException(new String("Response to service "+sr.service.name));
}
}
/**
* Externally send the response of the service
*
* @param params
* the parameter of the service response
* @param dmessage
* {@link seqSamoa.Message message} with its destination
*
* @return
* the id corresponding to the external response
*/
public long externalResponse(ResponseParameters params, Message dmessage) {
ServiceResponse sr = new ServiceResponse(this, params, dmessage);
return this.stack.scheduler.addExternalTask(sr);
}
/**
* Return the service provider
*
* @return the protocol bound to this service
*/
public ProtocolModule getProvider() {
if (currentExecuter != null)
return currentExecuter.parent;
else
return null;
}
/**
* Unlink the {@link seqSamoa.Service.Executer executer} currently linked to the service
*
*/
public void unlinkExecuter() {
currentExecuter = null;
}
// Registrate this Listener and executes all the responses waiting to be
protected void registrateListener(Listener l) {
int i = 0;
while (i < bufferedMessage.size()) {
Message dmessage = bufferedMessage.get(i);
ResponseParameters infos = bufferedResponseParameters.get(i);
if (dmessage.dest.equals(l.key)) {
l.evaluate(infos, dmessage.content);
bufferedResponseParameters.remove(i);
bufferedMessage.remove(i);
} else {
i++;
}
}
}
/**
* A super class for executer, listener and interceptor
*/
public class Handler {
/* protocol containing this handler */
protected ProtocolModule parent;
/*
* The list of calls and reponses that may be called
* upon execution of the handler
*/
protected LinkedList<ServiceCallOrResponse> initiatedCallsAndResponses;
protected Handler(ProtocolModule parent,
LinkedList<ServiceCallOrResponse> initiatedCallsAndResponses) {
super();
this.parent = parent;
this.initiatedCallsAndResponses = initiatedCallsAndResponses;
}
/**
* Return the service to which the Executer, listener or interceptor is bound
*
* @return the service to which the Executer, listener or interceptor is bound
*/
public Service<CallParameters, ResponseParameters> getService() {
return Service.this;
}
}
/**
* A class <CODE>Listener</CODE> describe the behaviour of a {@link seqSamoa.ProtocolModule protocol} when
* he receive a response of a {@link seqSamoa.Service service} call.
*
* @see ProtocolModule
*/
public abstract class Listener extends Handler {
/* The string associated to the Listener */
protected TString key;
/**
* Constructor.
*
* @param parent
* the {@link seqSamoa.ProtocolModule protocol} containing the
* listener
* @param serviceCalls
* the list of {@link simpleSamoa.Service services} that may
* be called while executing the listener
* @param serviceResponses
* the list of {@link simpleSamoa.Service services} that we
* may respond to while executing the listener
*/
public Listener(ProtocolModule parent,
LinkedList<ServiceCallOrResponse> initiatedCallsAndResponses) {
super(parent, initiatedCallsAndResponses);
parent.allListeners.add(this);
key = new TString(parent.name + parent.allListeners.size());
allListeners.put(key, this);
}
/**
* Constructor.
*
* @param parentStack
* the {@link seqSamoa.ProtocolStack stack} containing the
* listener (only if the listener does not belong to a specific {@link seqSamoa.ProtocolModule protocol})
* @param serviceCalls
* the list of {@link simpleSamoa.Service services} that may
* be called while executing the listener
* @param serviceResponses
* the list of {@link simpleSamoa.Service services} that we
* may respond to while executing the listener
*/
public Listener(ProtocolStack parentStack,
LinkedList<ServiceCallOrResponse> initiatedCallsAndResponses) {
super(parentStack.pFake, initiatedCallsAndResponses);
parentStack.registerFinalListener(this);
parent.allListeners.add(this);
key = new TString(parent.name + parent.allListeners.size());
allListeners.put(key, this);
}
/**
* Define the listener behavior
*
* @param params
* the parameter of the {@link seqSamoa.Service service} response
* @param response
* the object of response without destination of the response
*/
public abstract void evaluate(ResponseParameters params,
Transportable response);
}
/**
* A class <CODE>Executer</CODE> describe the behaviour of the service.
* When a service s is called the Executer that is link to s is executed.
*
* @see ProtocolModule
*/
public abstract class Executer extends Handler{
/**
* Constructor.
*
* @param parent
* the {@link seqSamoa.ProtocolModule Protocol} containing the
* executer
* @param serviceCalls
* the list of {@link simpleSamoa.Service services} that may
* be called while executing the executer
* @param serviceResponses
* the list of {@link simpleSamoa.Service services} that we
* may respond to while executing the executer
*/
public Executer(ProtocolModule parent,
LinkedList<ServiceCallOrResponse> initiatedCallsAndResponses) {
super(parent, initiatedCallsAndResponses);
parent.allExecuters.add(this);
}
/**
* Constructor.
*
* @param parentStack
* the {@link seqSamoa.ProtocolStack stack} containing the
* executer
* @param serviceCalls
* the list of {@link simpleSamoa.Service services} that may
* be called while executing the executer
* @param serviceResponses
* the list of {@link simpleSamoa.Service services} that we
* may respond to while executing the executer
*/
public Executer(ProtocolStack parentStack,
LinkedList<ServiceCallOrResponse> initiatedCallsAndResponses) {
super(parentStack.pFake, initiatedCallsAndResponses);
parent.allExecuters.add(this);
}
/**
* Define the executer behavior
*
* @param params
* the parameter of the {@link seqSamoa.Service service} call
* @param dmessage
* the message of the {@link seqSamoa.Service service} call
*/
public abstract void evaluate(CallParameters params, Message dmessage);
/**
* Link this executer to the {@link seqSamoa.Service service}
*/
public void link() throws AlreadyBoundServiceException {
if (currentExecuter == null)
currentExecuter = this;
else
throw new AlreadyBoundServiceException((Service.this));
}
/**
* Unlink this executer from the {@link seqSamoa.Service service}
*/
public void unlink() {
if (currentExecuter == this)
currentExecuter = null;
}
}
/**
* A class <CODE>Interceptor</CODE> allow to intercept the call and the
* response to calls of a {@link seqSamoa.Service service}. A Interceptor effectively intercept calls
* and response only when it is linked.
*
* @see ProtocolModule
*/
public abstract class Interceptor extends Handler {
/**
* Constructor.
*
* @param parent
* the {@link seqSamoa.ProtocolModule Protocol} containing the
* interceptor
* @param serviceCalls
* the list of {@link simpleSamoa.Service services} that may
* be called while executing the interceptor
* @param serviceResponses
* the list of {@link simpleSamoa.Service services} that we
* may respond to while executing the interceptor
*/
public Interceptor(ProtocolModule parent,
LinkedList<ServiceCallOrResponse> initiatedCallsAndResponses) {
super(parent, initiatedCallsAndResponses);
parent.allInterceptors.add(this);
}
/**
* Constructor.
*
* @param parentStack
* the {@link seqSamoa.ProtocolStack stack} containing the
* interceptor
* @param serviceCalls
* the list of {@link simpleSamoa.Service services} that may
* be called while executing the interceptor
* @param serviceResponses
* the list of {@link simpleSamoa.Service services} that we
* may respond to while executing the interceptor
*/
public Interceptor(ProtocolStack parentStack,
LinkedList<ServiceCallOrResponse> initiatedCallsAndResponses) {
super(parentStack.pFake, initiatedCallsAndResponses);
parent.allInterceptors.add(this);
}
/**
* Define the behavior upon call interception
*
* @param params
* the parameter of the {@link seqSamoa.Service service} call
* @param dmessage
* the message of the {@link seqSamoa.Service service} call
*/
public abstract void interceptCall(CallParameters params,
Message dmessage);
/**
* Forward the call to next interceptor or to the executer
*
* @param params
* the parameter of the {@link seqSamoa.Service service} call
* @param dmessage
* the message of the {@link seqSamoa.Service service} call
*/
public void forwardCall(CallParameters params, Message dmessage) {
int nextIndex = boundInterceptors.indexOf(this) + 1;
AtomicTask task = this.parent.stack.scheduler.currentTask();
if (nextIndex < boundInterceptors.size()) {
task.currentModule = boundInterceptors.get(nextIndex).parent;
boundInterceptors.get(nextIndex)
.interceptCall(params, dmessage);
task.currentModule = null;
} else {
task.currentModule = currentExecuter.parent;
currentExecuter.evaluate(params, dmessage);
task.currentModule = null;
}
}
/**
* Define the behavior upon response interception
*
* @param params
* the parameter of the {@link seqSamoa.Service service} response
* @param dmessage
* the message of the {@link seqSamoa.Service service} response
*/
public abstract void interceptResponse(ResponseParameters params,
Message dmessage);
/**
* Forward the response to next interceptor or to the executer
*
* @param params
* the parameter of the {@link seqSamoa.Service service} response
* @param dmessage
* the message of the {@link seqSamoa.Service service} response
*/
public void forwardResponse(ResponseParameters params, Message dmessage) {
int nextIndex = boundInterceptors.indexOf(this) - 1;
AtomicTask task = this.parent.stack.scheduler.currentTask();
if (nextIndex > 0) {
task.currentModule = boundInterceptors.get(nextIndex).parent;
boundInterceptors.get(nextIndex).interceptResponse(params,
dmessage);
task.currentModule = null;
} else {
TString dest;
Transportable message;
if (dmessage == null) {
dest = new TString("NULL");
message = null;
} else {
dest = dmessage.dest;
message = dmessage.content;
}
if (!dest.equals(new TString("NULL"))) {
if (allListeners.containsKey(dest)) {
Listener l = allListeners.get(dest);
task.currentModule = l.parent;
l.evaluate(params, message);
task.currentModule = null;
} else {
bufferedMessage.add(dmessage);
bufferedResponseParameters.add(params);
}
} else {
Set<TString> allListenersKeys = allListeners.keySet();
Iterator<TString> it = allListenersKeys.iterator();
while (it.hasNext()) {
TString key = it.next();
Listener l = allListeners.get(key);
task.currentModule = l.parent;
l.evaluate(params, message);
task.currentModule = null;
}
}
}
}
/**
* Bind this interceptor to the {@link seqSamoa.Service service}. An Interceptor could be link
* only once to a {@link seqSamoa.Service service}.
*/
public void bind() {
if (!boundInterceptors.contains(this))
boundInterceptors.addLast(this);
}
/**
* Unbind this interceptor to the {@link seqSamoa.Service service}.
*/
public void unbind() {
boundInterceptors.remove(this);
}
}
}