Chapter 10. C3D - from Active Objects to Components

10.1. Reason for this example

This is an example of an application that is refactored to fit the components dogma. The standard C3D example has been taken as a basis, and component wrappers have been created. This way, one can see what is needed to transform an application into component-oriented code.

You may find the code in the examples/components/c3d directory of the proactive source.

10.2. Using working C3D code with components

Informal description of the C3D Components hierarchy

Figure 10.1. Informal description of the C3D Components hierarchy

We consider the working C3D application. It's nice, and has a sleak GUI, but we now want to add component power to it! What we do is shown on the image: add wrappers around the original object classes (C3D*) and instead of linking the classes together by setting fields through the initial methods, do that in the binding methods. In other words, we have to spot exactly where C3DRenderingEngine, C3DUser and C3DDispatcher are used by a class other than itself, and turn these references into component bindings. Of course, we also have to expose the interfaces that we are going to use, hence the Dispatcher, Engine and User interface that have to be implemented.

10.3. How the application is written

First of all, have a look at the doc on C3D to remember how this application is written, in Section 5.2, “C3D: a parallel, distributed and collaborative 3D renderer”. Most important is the class diagram, showing C3DUser, C3DDispatcher and C3DRederingEngine. We decided that the only objects worth wrapping in components were those three. The rest is too small to be worth the hassle.

10.3.1. Creating the interfaces

What we need to do is to extract the interfaces of the Objects, ie find which methods are going to be called on the components. This means find out what methods are called from outside the Active Object. You can do that by searching in the classes where the calls are made on active objects. For this, you have to know in detail which classes are going to be turned into component. If you have a code base which closely follows Object Oriented Programming rules, the interfaces are already there. Indeed, when a class is written, it should always go with one or more interfaces, which present to the world what the class abilities are. In C3D (Active Object version), these interfaces already exist: they are called User, Engine and Dispatcher.

[Note]Note

Tricky part: whatever way you look at components, you'll have to modify the initial code if these interfaces were not created at first go. You have to replace all the class references by their interface, when you use them in other files. For example, if we had not already used interfaces in the C3D Object code, we would have had to replace all occurrences of C3DDispatcher by occurrences of Dispatcher.

Why do we have to do that, replacing classes by interfaces? That's due to the way components work. When the components are going to be bound, you're not binding the classes themselves (ie the container which performs operations), but [proxies to] the interfaces presenting the behaviour available. And these proxies implement the interfaces, and do not extend the classes. What is highlighted here is that components enforce good code design by separating behaviours.

10.3.2. Creating the Component Wrappers

You now have to create a class that englobes the previous Active Objects, and which is a component representing the same functionality. How do you do that? Pretty simple. All you need to do is extend the Active Object class, and add to it the non-functional interfaces which go with the component. You have the binding interfaces to create, which basically say how to put together two Components, tell who is already attached, and how to separate them. These are the lookupFc, listFc, bindFc and unbindFc methods.

This has been done in the *Impl files. Let's consider, for example, the UserImpl class (it is shown below).What you have here are those component methods. Be even more careful with this bindFc method. In fact, it really binds the protected Dispatcher variable c3ddispatcher. This way, the C3DUser code can now use this variable as if it was addressing the real Active Object. Just to be precise, we have to point out that you're going through proxies before reaching the Component, then the Active Object. This is hidden by the ProActive layer, all you should know is you're addressing a Dispatcher, and you're fine! The findDispatcher method has been overridden because component lookup doesn't work like standard Active Object lookup.

       public class UserImpl extends C3DUser implements BindingController, User {
    /** Mandatory ProActive empty no-arg constructor */
    public UserImpl() {
    }

    /** Tells what are the operations to perform before starting the activity of the AO.
     * Registering the component and some empty fields filling in is done here.
     * We also state that if migration asked, procedure  is : saveData, migrate, rebuild */
    public void initActivity(Body body) {
        // Maybe 'binding to dispatcher' has been done before
        if (this.c3ddispatcher == null) {
            logger.error(
                "User component could not find a dispatcher. Performing lookup");

            // ask user through Dialog for userName & host
            NameAndHostDialog userAndHostNameDialog = new NameAndHostDialogForComponent();
            this.c3ddispatcher = userAndHostNameDialog.getValidatedDispatcher();
            setUserName(userAndHostNameDialog.getValidatedUserName());

            if (this.c3ddispatcher == null) {
                logger.error("Could not find a dispatcher. Closing.");
                System.exit(-1);
            }
        }

        if (getUserName() == null) { // just in case it was not yet set.
            setUserName("Bob");
        }

        // Register the User in the Registry. 
        try {
            Fractive.register(Fractive.getComponentRepresentativeOnThis(),
                UrlBuilder.buildUrlFromProperties("localhost", "User"));
        } catch (IOException e) {
            logger.error("Registering 'User' for future lookup failed");
            e.printStackTrace();
        }

        super.initActivity(body);
    }

    /** returns all the possible bindings, here just user2dispatcher .
     * @return the only posible binding "user2dispatcher" */
    public String[] listFc() {
        return new String[] { "user2dispatcher" };
    }

    /** Returns the dispatcher currently bound to the client interface of this component
     * @return null if no component bound, otherwise returns the bound component */
    public Object lookupFc(final String interfaceName) {
        if (interfaceName.equals("user2dispatcher")) {
            return c3ddispatcher;
        }

        return null;
    }

    /** Binds to this UserImpl component the dispatcher which should be used. */
    public void bindFc(final String interfaceName, final Object serverInterface) {
        if (interfaceName.equals("user2dispatcher")) {
            c3ddispatcher = (org.objectweb.proactive.examples.c3d.Dispatcher) serverInterface;

            // Registering back to the dispatcher is done in the go() method 
        }
    }

    /** Detaches the user from its dispatcher.
     * Notice how it has not been called in terminate() ?
     * This is due to the fact that unbinding only sets a reference to null,
     * and does no cleaning up. */
    public void unbindFc(final String interfaceName) {
        if (interfaceName.equals("user2dispatcher")) {
            c3ddispatcher = null;
        }
    }
}

      

Example 10.1. The UserImpl class, a component wrapper

10.3.3. Discarding direct reference acknowledgment

If you're out of luck, the code contains instructions to retain references to objects that call methods on the current Object. These methods have a signature ressembling method(..., ActiveObject ao, ...). This is called, in ProActive, with a ProActive.getStubOnThis() (if you don't, and instead use 'this', the code won't work correctly on remote hosts!). If the local object uses this ProActive.getStubOnThis(), you're going to have trouble with components. The problem is that this design does not fit the component paradigm: you should be using declared interfaces bound with the bind methods, not be passing along references to self. So you have to remove these from the code, and make it component-oriented. But remember, you should be using bind methods to attach other components.

[Note]Note

If you really have to keep these ProActive.getStubOnThis() references, you may, because components, (or at least their internals) really are Active Objects. But you should be extra careful. This "Active Object reference passing" should not happen between components, as they are meant to interact through their component interfaces only.

10.4. The C3D ADL

You may be wanting to see how we have bound the components together, now. Since the design is pretty simple, there is not much to it. We have used the fractal ADL, to avoid hard-coding bindings. So all of the information here is in the examples/components/c3d/adl/ directory. There are the components, called '...Impl' (you can see there which interfaces they propose), and a 'userAndComposite.fractal' file, which is where the bindings are made. It includes the use of a Composite component, just for the fun. Specifically, it links one user to a composite made of a dispatcher and two renderers. You may want to explore these files with the Fractal GUI provided with IC2D, it's easier to understand graphically. Here's the code, nevertheless, for you curiosity:

      <?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE definition PUBLIC "-//objectweb.org//DTD Fractal ADL 2.0//EN"
 "classpath://org/objectweb/proactive/core/component/adl/xml/proactive.dtd">

<!-- This is an example of binding a complete application. In the code below, a user component
is attached to a composite, which englobes a dispatcher and 2 renderers. -->

<definition name="org.objectweb.proactive.examples.components.c3d.adl.userAndComposite">

<!-- Creating one user component -->
  <component definition="org.objectweb.proactive.examples.components.c3d.adl.UserImpl" name=
"user"/> 
  <component 
     definition="org.objectweb.proactive.examples.components.c3d.adl.compositeOfDispRend" 
     name="composite"/> 

<!-- binding together the user and the composite -->
  <binding client="user.user2dispatcher" server="composite.dispatch"/>
  <controller desc="composite"/>
  
<!-- coordinates added by the fractal GUI of IC2D. -->
  <coordinates color="-73" y0="0.11" x1="0.30" y1="0.33" name="user" x0="0.03"/>
  <coordinates color="-73" y0="0.18" x1="0.99" y1="0.98" name="composite" x0="0.32">
    <coordinates color="-73" y0="0.53" x1="1.00" y1="0.82" name="engine2" x0="0.57"/>
    <coordinates color="-73" y0="0.10" x1="0.90" y1="0.48" name="engine1" x0="0.63"/>
    <coordinates color="-73" y0="0.10" x1="0.50" y1="0.90" name="dispatcher" x0="0.1"/>
  </coordinates>

</definition>

     

Example 10.2. userAndComposite.fractal, a component ADL file

Here's what it looks like when you explore it through the IC2D Component explorer

IC2D component explorer with the C3D example

Figure 10.2. IC2D component explorer with the C3D example

10.5. Advanced component highlights

10.5.1. Renaming Virtual Nodes

One feature given by the component architecture is the possiblity to rename Virtual Nodes. Let's see how this can be used:

Suppose you are only dealing with packaged software. That means you may not modify the source code of some part of your application, for instance because it is kindly given to you by some other company, which wants to keep parts of its codebase secret. Let's say that the deployment descriptor you're using does not reference the proper VirtualNodes. How can you still deploy your application in this case? Well, you have to rename those Nodes into the names that are fitting to your application. You should do that after the definition of the interfaces that are defined inside the component. Here's an example of how to do that, renaming the externally provided name 'UserVirtualNode' to the name internally used by UserImpl 'User':

In the main ADL file (userAndComposite.fractal)

  <component ... />
       
    <!-- mapping the node names in the descriptor file to others referenced in the component's
 adl files. -->
    <exportedVirtualNodes>
      <exportedVirtualNode name="UserVirtualNode">
        <composedFrom>
           <composingVirtualNode component="user" name="User"/>
        </composedFrom>
      </exportedVirtualNode>
    </exportedVirtualNodes>
       
    <!-- Creating one user component -->

In the User ADL file (UserImpl.fractal)

<content class="org.objectweb.proactive.examples.components.c3d.UserImpl"/>
       
    <!-- Recalling a renamed Virtual Node -->
    <exportedVirtualNodes>
      <exportedVirtualNode name="User">
        <composedFrom>
          <composingVirtualNode component="this" name="User"/>
        </composedFrom>
      </exportedVirtualNode>
    </exportedVirtualNodes>
       
    <controller desc="primitive"/>

Example 10.3. How to rename Virtual Nodes in ADL files

If you add this code into the adl, you are saying that the VirtualNode called UserVirtualNode (found in the deployment descriptor file the application is using) should be recognized by the application as if it was called User.

[Note]Note

Above has been described the way to rename a VirtualNode; this can be used on packaged software, when the VirtualNodes provided do not fit the VirtualNodes needed by your application.

10.5.2. Component lookup and registration

When running the User Component alone, you are prompted for an address on which to lookup a Dispatcher Component. Then the two components are bound through a lookup mechanism. This is very simple to use. Here's the code to do that:

The component Registration

Fractive.register(Fractive.getComponentRepresentativeOnThis(),
      UrlBuilder.buildUrlFromProperties("localhost", "Dispatcher"));

The Component lookup

ProActiveComponentRepresentative a = Fractive.lookup(
      UrlBuilder.buildUrl(this.hostName, "Dispatcher", protocol, this.portNumber));
      this.c3dDispatcher = (Dispatcher) a.getFcInterface("user2dispatcher");

Example 10.4. Component Lookup and Register

For the registeration, you only need a reference on the component you want to register, and build a url containing the name of the host, containing an alias for the Component.

The Fractive.lookup method uses a Url to find the host which holds the component. This Url contains the machine name of the host, communication protocl and portNumber, but also the lookup name under which the desired Component has been registered under , here "Dispatcher". The last operation consists only in retreiving the correct interface to which to connect to. If the interface is not known at compile-time, it can be discovered at run-time with the getFcInterfaces() method, which lists all the interfaces available.

10.6. How to run this example

There is only one access point for this example in the scripts directory:

scripts/unix/components$ ./c3d.sh
  --- Fractal C3D example ---------------------------------------------
  Parameters : descriptor_file [fractal_ADL_file]
        The first file describes your deployment of computing nodes.
                You may want to try ../../../descriptors/components/C3D_all.xml
        The second file describes your components layout.
                Default is org.objectweb.proactive.examples.components.c3d.adl.userAndComposite
  ---------------------------------------------------------

You have there the way to start this example. If you only want to start the Composite (Dispatcher + Renderer), try this (don't insert the new lines):

scripts/unix/components$ ./c3d.sh  ../../../descriptors/components/C3D_all.xml \
   org.objectweb.proactive.examples.components.c3d.adl.compositeOfDispRend

If you want to start only a User, you will be asked for the address of a Dispatcher to which to connect to:

scripts/unix/components$ ./c3d.sh ../../../descriptors/components/C3D_all.xml \
  org.objectweb.proactive.examples.components.c3d.adl.UserImpl

10.7. Source Code

You may find the code of this application in the following packages:

  • org.objectweb.proactive.examples.c3d, the Active Object version

  • org.objectweb.proactive.examples.components.c3d, the Component version