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

 

Developer's guide

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
  • SynchronyType
    • SYNCHRONOUS
    • ASYNCHRONOUS

 

   

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.