Installation and Setup
Dermi distribution comes in the form of source
code and a precompiled binary JAR package. The downloadable
version consists of a ZIP
file which should be extracted to any directory. Dermi comes
with a set of JUnit tests and samples which can be executed to
test Dermi's functionalities.
To execute these tests and samples, you need at
least Java2 JRE v1.5.0. You will experience
problems if you use prior versions because the annotation functionalities
were not available until JDK 1.5.
Compiling Dermi
If you want to compile the sources, make sure you have
installed Jakarta ANT. You must
change to the ./bin directory in your Dermi installation directory and
execute the following command:
ant
We have used
FreePastry
1.3.2 version as the underlying P2P DHT substrate for Dermi.
Nevertheless, we have modified a little set of FreePastry's files to comply
with Dermi's specifications. These modifications can be found in ./src/rice
directory. All modifications start with a line like
// DERMI ADDITIONS START HERE and end with a line like
// DERMI ADDITIONS END HERE.
IMPORTANT NOTE: As for the current version
of Dermi (v1.2), automatic stub and skeleton generation is deprecated. The
dynamic proxy technique is used. Therefore, remote procedure calls are
dynamically intercepted in runtime by these objects. Subsequently, the older
dermic tool is unavailable from this version (1.1) onwards.
The next step is to edit the dermi-config.xml
file located at the ./bin directory, and change the parameters
concerning bootstrap host, port, protocol to use, etc.
In order to test Dermi's distribution using JUnit, you
may choose two ways. If you want to run the tests on the console, simply
type in the following while being within the ./bin directory:
test
If you prefer running the tests and generate a nice HTML
document containing the output logs (located at ./bin/reports
directory), simply type:
ant test
Running the sample applications
There is a binary precompiled dermi.jar file
contained in the ./dist directory which you should include in the
applications willing to use Dermi.
Dermi contains several sample applications which
demonstrate and test all of the functionalities in the middleware.
The instructions for running the
different included samples are as follows:
- Simple Object (dermi.samples.simple)
This sample includes the simple
object. It demonstrates remote setter and getter methods.
Open a shell and run the server.
- run
dermi.samples.simple.SimpleServer
Open a shell and run the client.
- run
dermi.samples.simple.SimpleClient
- Asynchronous Multicall (dermi.samples.asyncmulticall)
This is the same example as before, but defining the
setAge() method as asynchronous, that is the client does not block
when executing this setter method.
Open a shell and run the server.
- run
dermi.samples.asyncmulticall.SimpleServer
Open a shell and run the client.
- run
dermi.samples.asyncmulticall.SimpleClient
- Naming Object (dermi.samples.naming)
It shows how the decentralized
location service works.
Open a shell and run the server.
It binds a Simple object to the naming service.
- run
dermi.samples.naming.SimpleNamingServer
Open a shell and run the list
client. It should show a list of already bound objects.
- run
dermi.samples.naming.SimpleNamingList
Open a shell and run the client.
It looks up the Simple object and calls several setter/getter
methods.
- run
dermi.samples.naming.SimpleNamingClient
- Listener Objects (dermi.samples.listener)
It demonstrates how to add listener
objects and see how they react to different invocations.
Open a shell and run the server.
- run
dermi.samples.listener.SpriteServer
Open a shell and run one listener
client.
- run
dermi.samples.listener.SpriteClient2
Open a shell and run another
listener client.
- run
dermi.samples.listener.SpriteClient2
Open a shell and run the client
which sends a setX() call to the server. See how listener
clients react to this call.
- run
dermi.samples.listener.SpriteClient1
- Basic types Object (dermi.samples.basictypes)
Primitive Java types can be used as
well, for method's return results and parameters. Our stubs and
skeletons will automatically do the boxing.
Open a shell and run the server.
- run
dermi.samples.basictypes.SpriteServer
Open a shell and run the client.
- run
dermi.samples.basictypes.SpriteClient
- Dynamic class loading (dermi.samples.dynamic)
If the stub class cannot be found on
the client machine, it is automatically sent by the server skeleton.
In this case, the class dermi.samples.dynamic.SimpleStub has
been removed from the classpath, and included in the class
repository directory (which can be modified in dermi-config.xml
file).
Open a shell and run the server.
- runDynamic
dermi.samples.dynamic.SimpleServer
Open a shell and run the client.
You should notice a message telling that the class will be remotely
loaded.
- run
dermi.samples.dynamic.SimpleClient
- Mobile objects (dermi.samples.mobile)
Dermi supports mobile objects. In
this example, we instantiate a Teleport server, which initializes a
Simple object. If we start a SimpleClient, it will invoke Simple's
methods. Subsequently we start a Teleport client, which will send
the Simple Object to the Teleport object. If we start a Simple
client, it will now transparently invoke Teleport's methods. See the
code in TeleportClient.java for more details.
Open a shell and run the Teleport
server.
- run
dermi.samples.mobile.TeleportServer
Open a shell and run the Teleport
client.
- run
dermi.samples.mobile.TeleportClient
Open a shell and run the Simple
client.
- run
dermi.samples.simple.SimpleClient
Now press <RETURN> in the
TeleportClient shell. The Simple server object is moved to the
Teleport server object.
Now just run the Simple client,
and see how it works OK, as if the Simple server hadn't gone.
- run
dermi.samples.simple.SimpleClient
- Exceptions (dermi.samples.exception)
If a method is invoked and throws an
exception, this exception is propagated back to the client object,
so as to notify it about the erroneous condition. In this example,
when calling the merge() method, a MergeException is
thrown and thus sent back to the client.
Open a shell and run the server.
- run
dermi.samples.exception.SimpleServer
Open a shell and run the client.
- run
dermi.samples.exception.SimpleClient
- Pass by reference (dermi.samples.reference)
Other Dermi objects can be passed as
arguments to other Dermi object methods. In this example, we create
two objects: Place and User, and use a User as argument to several
Place's methods.
Open a shell and run the Place
server.
- run
dermi.samples.reference.PlaceServer
Open a shell and run the User
server, which creates several users and makes them join the place.
- run
dermi.samples.reference.UsersServer
Open a shell and run the User
client, which lists the actual users in the place.
- run
dermi.samples.reference.UserClient
- Anycall abstraction (dermi.samples.anycall)
Dermi provides a powerful new
abstraction, namely anycall, which allows calls to be done to
the closest servers in terms of network proximity. In this sample,
we simulate a data unit retrieval process, similar to the one found
in programs like SETI@Home or
United Devices Cancer
Research Project.
Open a shell and run the Seti
server.
- run
dermi.samples.anycall.SetiServer
Open a client and run the Seti
client. You can press <RETURN> and a new data unit will be requested
to the server. The server can spare up to 5 data units. Once they
are finished, it will seek for other servers, and if none of them
has more available data units, the client will be notified by a NotSatisfiedException. Naturally, you can open more shells
starting more Seti servers. You will notice that the client will be
able to request more data units, until reaching the maximum provided
by the servers.
- run
dermi.samples.anycall.SetiClient
- Manycall abstraction (dermi.samples.manycall)
In addition to the anycall
abstraction, the manycall is similar to the anycall,
but referring not to only one server, but to many servers. In this
example, we illustrate how to use it for a ballot involving many
servers. A client wishes to know whether it can perform some task or
not. It will ask a group of servers and collect their opinion. Once
all votes have been accounted for, the result is returned to the
client, which can check whether the required number of votes has
been gathered.
Open a minimum of two shells (you
can open more, or less, if you wish) and start the server.
- run
dermi.samples.manycall.VotingServer
Open the client, which will poll
the existing servers until it gets the required number of votes. If
it cannot gather all of the required votes, a NotSatisfiedException will be thrown.
- run
dermi.samples.manycall.VotingClient
- Direct calls (dermi.samples.direct)
One-to-one calls are made as direct
peer-to-peer calls, without involving the event service. This sample
demonstrates this functionality.
Open a shell and run the server.
- run
dermi.samples.direct.SimpleServer
Open a shell and run the client.
- run
dermi.samples.direct.SimpleClient
- Distributed interception (dermi.samples.interception)
Distributed interception is a facility which allows the
interception of remote procedure calls by objects inserted between end
objects. This way, we can intertwine and invoke interceptor objects
before invoking a method in the destination object.
Open a shell and run the server.
- run
dermi.samples.simple.SimpleServer
Open as many shells as you want, and
run interceptors. These interceptors are simply logging interceptors,
but they change the contents of the messages too. For example, for each
setAge() method called, they add +2 years to the specified age.
- run
dermi.samples.interception.LogInterceptorServer
Open a shell and run the client.
- run dermi.samples.simple.SimpleClient
Note how the age has been increased +2 years for each
open interceptor that you have. If you want to remove an interceptor,
simply enter 'quit' on its console and press <RETURN>.
- Stateful object replicas
(dermi.samples.replicastate)
Dermi allows the creation and management of stateful object
replicas. You can start a group of replicated objects which maintain the
same replicated state information. In this sample, we instantiate a server
with its state replicated among all object replicas. Once a new replica is
started, it queries the group of objects (via anycall) requesting
the shared state.
Open a shell and run the server.
- run
dermi.samples.replicastate.SpriteServer
Open a shell and run the client.
(Server's state will be changed)
- run
dermi.samples.replicastate.SpriteClient
Open a shell and run a new server
replica.
- run
dermi.samples.replicastate.SpriteServer
Open a shell and run a new client. (Note
how both servers return the same state information)
- run
dermi.samples.replicastate.SpriteClient
IMPORTANT NOTE: As for the current version
of Dermi (v1.1), static stub and skeleton generation is deprecated. In this
version you do not need to worry about these classes. Remote procedure calls
are intercepted in runtime using dynamic proxies.
If you wish to code your objects taking advantage of
Dermi services, you should follow these simple steps:
- Code the interface to your object's methods. This
interface should extend the ERemote interface and
follow the next template:
import dermi.*;
import dermi.exception.*;
import dermi.annotation.*;
@DermiRemoteInterface
public interface
AnObject extends ERemote {
@RemoteMethod (granularity =
Granularity.MULTICALL, type = SynchronyType.SYNCHRONOUS)
public void aMethod
(String param1, Integer param2, ...) throws RemoteException;
...
}
Several aspects to comment here:
- Every interface to any Dermi remote object MUST
be declared using the @DermiRemoteInterface annotation.
- Every interface to any Dermi remote object MUST
extend the ERemote interface.
- For each Dermi remote method, an annotation including
the invocation type and the synchrony type must be provided. Current
values for these parameters include the following:
- Granularity
- DIRECTCALL
- MULTICALL
- ANYCALL
- MANYCALL
- LISTENER
- ANYCALL_CONDITION
- MANYCALL_LOCAL_CONDITION
- MANYCALL_GLOBAL_CONDITION
|
|
|
|
For more information, just take a
look at ./src/dermi/samples/simple/Simple.java
file.
- Code implementation class for the
object, i.e. the code to be executed locally in the object, for each
of its previously defined interface methods. The implementation,
should obviously extend the object's interface. In addition, an
empty constructor is required to be provided. You can find an
example at ./src/dermi/samples/simple/SimpleImpl.java.
Nevertheless, a template like the following must be used:
import dermi.*;
import dermi.exception.*;
public interface
AnObjectImpl extends
DermiRemoteObject implements AnObject {
public AnObjectImpl() {
...
}
public AnObjectImpl (Properties
env, ...) throws RemoteException {
super (env);
...
}
}
- Optionally you should code a
class for initializing the object (namely the server object),
and another one for invoking its methods (namely the client
object). You can follow the classes ./src/dermi/samples/simple/SimpleServer.java
and ./src/dermi/samples/simple/SimpleClient.java on how
to bind/lookup objects using the File Registry.
If you wish to register your objects into Dermi's
Decentralized Naming Service, you should follow the next template:
package dermi.samples.listener;
import dermi.*;
import dermi.registry.*;
public class SpriteServer {
public static void main (String[] args) throws Exception {
Properties env =
Registry.getEnvironment ("dermi-config.xml");
SpriteImpl server = new SpriteImpl ("hey
boy, hey girl!", env);
Registry.bind ("p2p://sprite", server);
System.in.read();
server.close();
}
}
Important things to note are the utilization of
Registry.getEnvironment() to load Dermi's configuration properties,
which must be passed to the object implementation instance. Furthermore,
you need to bind the object to the decentralized registry, which is done
by using Registry.bind(). Note that the object's URI is required
to start with "p2p://".
The client's template is the following:
package dermi.samples.listener;
import dermi.*;
import dermi.registry.*;
public class SpriteClient {
public static void main (String[] args) throws Exception {
// Load the registry first
Registry.loadRegistry ("registry-config.xml");
// Look up object in the registry
Sprite client = (Sprite)
Registry.lookup ("p2p://sprite");
client.addSpriteListener (SpriteEvent.class,
new MyListener());
}
}
The client's code is rather simple as well. We need to
load the decentralized registry first, via Registry.loadRegistry().
Finally, we query the registry for the object by executing
Registry.lookup(). Note that we need to load the registry because we
have not instantiated any object. In the server's case we do not need to
do so, as you can see above.
- You now need to compile these
classes, preferrably using ANT.
- Now you've done it! Simply test
the object by invoking the server first, and the client next.
STATEFUL REPLICATED OBJECTS
You can create stateful replicated objects in Dermi. That
is, the object's server state will be replicated among its replicas. When
new replicas of this object are created, the state will be automatically
propagated to these new objects.
To activate such functionality, you Dermi object
implementation must implement the StatefulReplica interface.
package dermi.samples.replicastate;
import dermi.*;
import dermi.exception.*;
import java.io.Serializable;
import java.util.Properties;
public class SpriteImpl extends DermiRemoteObject implements Sprite,
StatefulReplica {
private int x;
// Dermi constructor: REQUIRED
public SpriteImpl() {
}
// Dermi constructor: REQUIRED
public SpriteImpl (int x, Properties env) throws RemoteException {
super (env);
// Default x value
this.x = x;
// Load replica state
super.loadReplicaState();
System.out.println ("[Sprite] object created.");
}
public int getX() throws RemoteException {
System.out.println ("[getX] called: " + x);
return x;
}
public void setX (int x) throws RemoteException {
System.out.println ("[setX] called.");
this.x = x;
}
/**
* Method for loading state into this object
* @param data SpriteValues Object state
*/
public void
loadRemoteObjectState() throws RemoteException {
SpriteData data = (SpriteData) super.loadState();
this.x = data.getX();
System.out.println ("[loadRemoteObjectState] -
new state loaded: " + x);
}
/**
* Method for obtaining this object's state
* @return SpriteData
*/
public Serializable
getRemoteObjectState() {
return new
SpriteValues (x);
}
}
Any object implementing the StatefulReplica
interface must provide the loadRemoteObjectState() and
getRemoteObjectState() methods. The former is called when initializing
the replica to load the received state. It must obtain the state via network
by calling super.loadState() method. This method returns a
Serializable object which will be the replica's state. It must then be
casted to the appropriate defined type (in this case SpriteData).
getRemoteObjectState() is provided in order to get this object's state.
It will be automatically called by Dermi so as to obtain this object's state,
prior to serializing it through the network.
For more information, take a look at
./src/dermi/samples/replicastate/*.java.
HOW TO USE Dermi WITH OTHER AVAILABLE DHTs?
An adaptation layer has been created from version 1.1 of
Dermi, so as it can be easily adapted to different available Key-Based
Routing (KBR) substrates. If you wish to provide Dermi with compatibility
with another overlay network substrate, you should make sure there exists
some kind of topic-based publish/subscribe event service on top of it. In
our case, we have implemented Dermi over Pastry, and Scribe. In order to
make Dermi compatible with another overlay network, you must implement a
series of classes, which are detailed as follows, showing Pastry and
Scribe's example:
Package |
Overlay network class |
Implements |
dermi.core.pastry |
DermiConnection
DermiContent
Id
InterceptorApp
Node
NodeHandle
ScribeClient
Topic |
dermi.core.DermiConnection
dermi.core.DermiContent
dermi.core.Id
dermi.core.InterceptorApp
dermi.core.Node
dermi.core.NodeHandle
dermi.core.Client
dermi.core.Client |
dermi.messaging.pastry |
* |
All messages are specific to the overlay and cannot be made generic |
dermi.registry.pastry |
Naming |
dermi.registry.Naming |
dermi.session.pastry |
Session
NotificationParser |
dermi.Session
dermi.session.DermiApplicationListener |
dermi.util.pastry |
HashGenerator |
Utility class to generate hashes |
To sum up, and looking at the previous example, you should
code all the above specified classes, implementing all required Dermi
interfaces. The package names must follow the same template. For example, in
case we wanted to support Dermi on top of Chord, we would implement the
classes in the packages dermi.core.chord, dermi.messaging.chord,
demi.registry.chord, and dermi.session.chord.
In order to use Dermi with one KBR overlay or another, you
must specify the package name (chord in our case) in the different
configuration files we are using. For instance, dermi-config.xml:
change the lines "<entry key="dermi.underlyingDHT">chord</entry>",
and "<entry key="dermi.sessionClass">dermi.session.pastry.Session</entry>"
accordingly.
|