Table of Contents
Introduction
To support modularity and still allow for fault tolerance in the framework, we have split the design of the communication layer into two sublayers. The first of them is the set of required functionalities that are required for fault-tolerant behavior of the framework for communication purposes. We call this sublayer meta-protocol (which is an abstract sublayer, described as an interface called ClientSideProtocolInterface in the framework). The second sublayer, the protocol is any construct that supplies these minimum required functionalities.
The minimum required functionalities for the meta-protocol (on the client side) are:
- Provide the means to connect to the server;
- Provide the ability to disconnect from the server;
- Provide the ability to probe the connection status to the server;
- Provide the means to convert abstract server jobs to protocol specific ones if necessary; and
- Provide the means to submit a job to the server.
Any class that implements these functionalities is a proper client side communication protocol. Note that there is an analogous set of requirements on the server side as well. This will be covered in the client handler tutorial.
What is the server handler?
The server handler sits on the client side and handles the communication between the client and the server. It ensures the proper delivery of the jobs from the client to the server and informs the client (through listeners) in the case of successful delivery of a job to the server. When the server handler informs the client of the delivery, it passes through the server reply as well.
This fault tolerance includes cases where the server is unavailable (due to network problems, downtime, high load, etc). It also includes cases where the telemonitoring node (or is properly closed) in the interim between the request to submit a job and the actual (physical) submission of the job. These jobs are backed up using a backup cabinet and is restored the next time the telemonitoring node is restarted.
How to use the server handler?
In order to initialize a server handler object, you need a client side protocol object. We have implemented an example protocol on top of TLS (or SSL if you choose) that is ready for use called TIProtocol. We will use this protocol for this tutorial (but any protocol/object that implements ClientSideProtocolInterface works). The following is a snippet of code that initializes a protocol object and then uses it to initialize a server handler:
BasicUserIdentifier uid = new BasicUserIdentifier("My user ID"); // User identifier
VersionIdentifier vid = new VersionIdentifier(1,0,0); // Version identifier
TIServerAddress[] serverAddresses = {
new TIServerAddress("server.address.one", portOne),
new TIServerAddress("server.address.two", portTwo) //,
// etc...
}; // Server addresses
TIServerIdentifier si = new TIServerIdentifier(keyStore, "keyStorePassword", serverAddresses); // Server identifier
TIProtocol protocol = new TIProtocol(si, vid, uid);
ServerHandler serverHandler = new ServerHandler(context, protocol, connectionInterval, pathForBackupCabinet); // Create a server handler
where context is the Android context used for the purposes of the server handler (such as scheduling, etc) and connectionInterval is the time interval between consecutive trials of job submissions (thereafter: job sync) in milliseconds, or 0 for immediate job sync (after every request, see next) or -1 for manual job sync (has to be manually invoked as serverHandler.manualOffload() .
A job object jobExample can be submitted to the server handled by the server handler serverHandler by invoking serverHandler.sendJob(jobExample) . This requires preparing a job object from the telemonitoring primitives required. In order to ease the process even more, a couple of methods were implemented to handle submitting data jobs and request jobs. Those methods are respectively:
- serverHandler.sendData(dataId, jobHandler) : prior to using this method, the data encapsulator identified by the identifier dataId has to be registered with the server handler by invoking serverHandler.registerData(encapsulator, isVolatile) where you can pass isVolatile=false if you want the server handler to handle the backup of the data in the encapsulator and isVolatile=true otherwise.
- serverHandler.sendRequest(requestId, requestHandler): Where requestId is the request identifier (of type RequestJobIdentifier)
Why and how to ensure server handlers are unique (SingletonServerHandler)?
Only one ServerHandler instance should exist for each server connection. However, due to Android’s peculiarities, it is possible that multiple instances exist at a given moment even if your application only instantiates it once. To prevent this from happening, we highly recommend creating a singleton class from ServerHandler, which guarantees that only one instance exists.
You can create your own singleton class by extending from ServerHandler, as shown in the code template for a SingletonServerHandler below. A description of how to use it follows.
import android.content.Context;
import edu.berkeley.telemonitoring.client.serverservices.ClientSideProtocolInterface;
import edu.berkeley.telemonitoring.client.serverservices.ServerHandler;
public class SingletonServerHandler extends ServerHandler {
static private SingletonServerHandler mSingletonInstance = null; // The sole instance of this class that can exist
static private int mSingletonTimeInterval = 120 * 1000; // Default of 2 minutes (120,000 milliseconds)
static private ClientSideProtocolInterface mSingletonServerProtocol = null;
/**
* Constructor is private to ensure that it is only ever called internally if no instance
* exists yet.
* @param context
* @param protocol
* @param time
*/
private SingletonServerHandler(Context context, ClientSideProtocolInterface protocol, int time) {
super(context, protocol, time);
}
/**
* Must be called before the first call of getInstance.
* Subsequent calls after the first call of getInstance do nothing.
* @param protocol
*/
static public void setProtocol(ClientSideProtocolInterface protocol) {
if (mSingletonInstance == null) {
mSingletonServerProtocol = protocol;
}
}
/**
* Must be called before the first call of getInstance.
* Can be called later to change the time interval.
* @param time
*/
static public void setTimeInterval(int time) {
mSingletonTimeInterval = time;
if (mSingletonInstance != null) {
mSingletonInstance.setSyncRate(time);
}
}
/**
* Returns the sole instance of SingletonServerHandler.
* Will instantiate a new one if it doesn't yet exist,
* and only if the time interval and protocol have been configured.
* @param context
* @return The singleton instance
*/
static public SingletonServerHandler getInstance(Context context) throws ServerHandlerException {
if (mSingletonInstance == null) {
if (mSingletonServerProtocol != null) {
mSingletonInstance = new SingletonServerHandler(context, mSingletonServerProtocol, mSingletonTimeInterval);
} else {
// Trying to create a new instance without having defined the protocol
throw new ServerHandlerException("Cannot create instance with an undefined server protocol.");
}
}
return mSingletonInstance;
}
}
The basic idea of SingletonServerHandler is that it cannot be directly instantiated and accessed. Instead, it is always accessed through its own getInstance() method. This method is responsible for both instantiating and allowing access to the instance, and makes sure that only one instance exists. Its use will be explained in greater detail below.
How to configure SingletonServerHandler?
Before the very first call to getInstance(), the update period and protocol of the server handler must be configured. They can be set by calling the setSingletonSyncRate() and the setSingletonProtocol() methods, respectively, with their desired values as arguments. If this is not done, an attempt to get an instance of SingletonServerHandler will result in a thrown ServerHandlerException.
If an instance of SingletonServerHandler already exists, then its update period can be reconfigured through the setSingletonSyncRate() method. However, the protocol of the server handler cannot be reconfigured, and any attempt to do so will simply have no effect.
How to use SingletonServerHandler?
Once configured, you can get an instance of SingletonServerHandler by calling its getInstance() method instead of calling its constructor. In fact, the singleton class’s constructor is private to ensure that it is never accidentally called outside of getInstance(), which makes sure that there is never more than one instance. In other words, call getInstance() wherever you would normally call the constructor.
How to deal with multiple server handlers?
For each server handler you need, you would have to create a separate singleton class that extends ServerHandler exactly like the SingletonServerHandler example, but with a different class name. This inconvenience unfortunately arises from limitations in the Java programming language. However, we still highly recommend this as standard practice when using server handlers, as not doing so may lead to unpredictable behavior of your application, especially when data is transmitted from client nodes to servers.
Running Example
(TODO: complete)