Java Kernel - An Example
Here we provide a brief tutorial on how to write components for the Java RUNES middleware through a sample application. Our goal is to write a simple component-based calculator to perform additions and multiplications, as illustrated in the figure. The operations are performed by connecting the component representing the Calculator to two components implementing addition and multiplication, respectively. The receptacle ofCalculator
is meant to be used by another
application component willing to use its services.
Let us start from interfaces. In this example, we just need two interfaces, one for each component we plan to implement. In particular, we need an
IAdder
interface with a single
operation used to add two integers. Then, we need an
IMultiplier
interface with a single operation used
to multiply two integers. Finally, an ICalculator
interface is needed with two operations, one for adding and one for
multiplying two integers. Clearly, the former will be mapped to the
corresponding method in the IAdder
interface,
whereas the latter will be mapped to the corresponding method
in IMultiplier
. The actual code for the three
interfaces is reported next. Note all of them extend the basic RUNES
interface Interface
.
public interface IAdder extends Interface {
   public int add(int x, int y);
}
public interface IMultiplier extends Interface {
   public int multiply(int x, int y);
}
public interface ICalculator extends Interface {
   public int add(int x, int y);
   public int multiply(int x, int y);
}
Note that splitting the calculator into multiple components, where each of them performs a particular operation, allows one to transparently and dynamically substitute the component providing a given operation without the rest of the system being aware of this. Indeed, the calculator component needs to be aware only of the
IAdder
and IMultiplier
interfaces. In other words, the Calculator
component depends
only on the operation signatures in the IAdder
and
IMultiplier
interfaces, not on the way these
operation are realized. Therefore, the components behind these
interfaces can be changed transparently, even at runtime.
After having defined an interface, we have to implement at least a component implementing it. The code of a simple component implementing the
IAdder
interface is shown next. Here, the addition
is performed using the usual Java arithmetic operators. However, once
an interface is defined, the developer can freely decide how to
implement a given operation. Furthermore, notice that every time a
RUNES component is implemented, it has to be written by extending
the BaseComponent
class in order to be recognized
as a RUNES component by the rest of the framework. Apart from this,
the developer writes the actual code for all the methods defined
in the interface(s) the component is implementing. In addition,
the construct()
and destroy()
methods have to be implemented to perform all the necessary operations
at component creation and destruction time, respectively. As in
the SimpleAdder
component there is no setup or
clean-up to perform, these two methods simply print a message on the
standard output.
public class SimpleAdder extends BaseComponent implements IAdder {
   public void construct() throws ComponentException {
     System.out.println("Adder component instantiated");
   }
   public void destroy() throws ComponentException {
     System.out.println("Adder component destroyed");
   }
   public int add(int x, int y) {
     return x+y;
   }
}
An implementation for the Calculator component is reported in the following code fragment. In this case, the
construct()
method is responsible for creating the two receptacles
the component needs. This is accomplished with the
createSingleReceptacle(String interfaceName)
method. Later, a reference to such a receptacle can be retrieved using
the getSingleReceptacleTo(String interfaceName)
method. Then, the receptacle can be used by means of
the getSingleConnectedInterface()
method, that
returns a reference to the Interface connected to this
receptacle. Notice that there are no guarantees that the receptacle is
actually connected to a given interface. For instance, a receptacle
might turn out to be disconnected when the connected Component have
been destroyed. For this reason, every time
a getSingleConnectedInterface()
method is invoked,
one has to explicitly manage the exceptions possibly raised. Moreover,
let us point out that
the getSingleConnectedInterface()
method has to be
invoked systematically every time we want to use a
receptacle. Indeed, if one would simply invoke it at component
creation time and store the reference to the interface internally,
then it might be the case that the reference gets not updated in case
the connected component is destroyed or changed.
public class Calculator extends BaseComponent implements ICalculator {
   public void construct() throws ComponentException {
     createSingleReceptacle("runes.sampleApp.IAdder");
     createSingleReceptacle("runes.sampleApp.IMultiplier");
     System.out.println("Calculator component instantiated");
   }
   public void destroy() throws ComponentException {
     System.out.println("Calculator component destroyed");
   }
   public int add(int x, int y) {
     SingleReceptacle r = getSingleReceptacleTo("runes.sampleApp.IAdder");
     try {
       return ((IAdder) r.getSingleConnectedInterface()).add(x, y);
     } catch(ReceptacleException e) {
       if(e.getKind()==ReceptacleException.NOT_CONNECTED)
         System.err.println("Adder Component not connected!");
       }
     return 0;
   }
   public int multiply(int x, int y) {
     SingleReceptacle r = getSingleReceptacleTo("runes.sampleApp.IMultiplier");
     try {
       return ((IMultiplier) r.getSingleConnectedInterface()).multiply(x, y);
     } catch(ReceptacleException e) {
       if(e.getKind()==ReceptacleException.NOT_CONNECTED)
         System.err.println("Multiplier Component not connected!");
       }
     return 0;
     }
}
Once we created the components, we wire them together to perform some useful computation. To this end, we need to implement a connector component for each of the interfaces we defined. A connector is a particular component meant to represent the connection between two standard components. It can either simply pass the operation to the connected component without further intervention or perform some more complex operation before passing the operation to the connected component, e.g. to monitor communication between the connected parties or to implement a distributed connection among components residing on different hosts. A simple connector for the
IAdder
interface is shown next. Clearly, this is the most simple connector
one could implement, as it simply pass the operation to the component
implementing the IAdder
interface. Notice that, as
the SimpleAdder
component extends
the BaseComponent
class,
the IAdderDefaultSingleConnector
connector extends
the BaseConnector
class. In addition, it also implements the
same interface of the component it is meant to connect.
public class IAdderDefaultSingleConnector extends BaseConnector
implements IAdder {
   public void construct() throws ComponentException {
     System.out.println("IAdderDefaultSingleConnector instantiated");
   }
   public void destroy() throws ComponentException {
   System.out.println("IAdderDefaultSingleConnector destroyed");
   }
   public int add(int x, int y) {
     return ((IAdder)getConnectedInterface()).add(x,y);
   }
}
Having implemented components and connectors, what remains to do is to wire everything together. To this end, the methods of the capsule have to be exploited. First of all, we need to load the necessary component types. For instance, let us consider the calculator component. Its component type can be loaded as follows:
ComponentType calculatorType = capsule.load(new StringPattern( "runes.sampleApp.Calculator"));
With its component type, we can now instantiate a component of type
calculator
as follows:
Component calculator = capsule.instantiate(calculatorType);
In the same way, we can load component types for the
SimpleAdder
and SimpleMultiplier
components and then
instantantiate two of them. Let us call them adder
and multiplier
.
We are now ready to connect components togheter. For instance, to connect the
Calculator
to a SimpleAdder
, we
first retrieve a reference to the interface of
the SimpleAdder
component and to the corresponding
receptacle in Calculator
. Once we obtained these references,
we invoke the bind()
method of the capsule and get
a connector component representing the binding:
Interface adderIf = (Interface) capsule.getAttr(adder, "INTERFACE-runes.sampleApp.IAdder").getValue();
Receptacle calculatorAdderRecpt = (Receptacle) capsule.getAttr(calculator, "RECEPTACLE-runes.sampleApp.IAdder").getValue();
Connector calcToAdder = capsule.bind(adderIf, calculatorAdderRecpt);
In case we later want to destroy this binding, we destroy the connector component representing it as we would destroy any other component, i.e., by means of the
destroy()
method.