SOFA implementation


Table of Contents

Introduction
SOFA component model
Components
Component Definition Language
Connectors
Behavior protocols
SOFAnode
DCUP
SOFA implementation
SOFA CDL to Java mapping
Runtime structure of a component
Implementation versioning
Connectors
SOFAnode implementation
SOFA net implementation
SOFA Client
Dynamic update (DCUP implementation)
Dynamic update and type compatibility
Developing a SOFA application
How to create SOFA application in several steps
Related work
References
A. Syntactic diagrams of CDL
B. Limitations and missing features of the current implementation
C. Distribution package and installation
D. License

Introduction

Reusing parts of existing software has become one of the major techniques for developing software systems. Many parts of code are repeated in many applications (e.g. user interface toolkit). Their reuse can significantly shorten time and developer effort necessary for application development.

The component-based programming is one of these techniques. Application development is done by assembling small autonomous units of code - components - connected at well-defined points to form the whole application. Reuse of these units is easier than a conventional (non-component based) development of an application.

The goal of the SOFA project[sofa] is to provide a platform for software components. A specific component architecture called DCUP allows dynamic update of SOFA components at runtime. This paper describes the design and implementation of the SOFA platform in the Java language. This implementation allows development and usage of distributed, dynamically updatable component-based applications.

SOFA component model

SOFA (SOFtware Appliances)[sofa] is a project aiming to provide a platform for software components.

Components

In the SOFA component model, applications are viewed as a hierarchy of nested components. Components can be either primitive or composed - a composed component is built of other components, while a primitive component contains no subcomponents. As a result, all functionality is located in the primitive components.

A component is described by its frame and architecture. The frame is a black-box view of the component. It defines provides-interfaces and requires-interfaces of the component. Optionally, it declares properties for parametrizing the component. The frame can be implemented by more than one architecture. The architecture of a composed component describes the structure of the component by instantiating direct subcomponents and specifying the subcomponent's interconnections via interface ties. The architecture reflects a particular grey-box view of the component - it defines first level of nesting in a component hierarchy.

There are four kinds of interface ties: (a) binding of a requires-interface to a provides-interface between two subcomponents, (b) delegating from a provides-interface of component to a subcomponent's provides-interface, (c) subsuming from a subcomponent's requires-interface to a requires-interface of component, (d) exempting an interface of a subcomponent from any ties. A non-exempting tie is realized via a connector (see the section Connectors). For convenience, the simple connectors, expressing procedure (method) calls are implicit so that they do not have to be specified in an architecture specification.

The architecture can also be specified as primitive (i.e. primitive component), which means that there are no subcomponents and its structure/implementation will be provided in an underlying implementation language.

The whole application's hierarchy of components (all levels of nesting) is described by application deployment descriptor.

Component Definition Language

The Component Definition Language (CDL) is used to describe interfaces, frames and architectures of SOFA components. It is based on OMG IDL; the language extends the features of IDL to allow specification of software components.

A sample CDL specification is shown on Figure 1.

Figure 1. A sample declaration in CDL

interface Login {
   CentralPlayerServices login(in string who);
};
frame Client {
   provides:
      Client iClient;
   requires:
      Login iLogin;
      CentralPlayerServices iCPS;
};
architecture CUNI GameGen implements GameGenerator {
   inst GameGeneratorDBServices aGGDBS;
   inst ConfigurationFileParser aConfig;
   inst GameGeneratorFunctionality func;
   bind func:iConfig to aConfig:iConfig;
   bind func:iGGDB to aGGDBS:iGGDB;
   subsume aGGDBS:iDatabase to iDatabase;
};

Descriptions in CDL are compiled and stored in the Type Information Repository (TIR). TIR manages an evolution of component's description and can store several versions of every element defined in CDL. In TIR, each element defined in CDL (interface, frame, architecture,...) is identified by its name specified in CDL and by its specification version. In the CDL descriptions, a developer can specify references to a concrete version of previously compiled types stored in TIR. Type references specified without version are resolved to the currently compiled version of the referenced type (for a single compilation, a developer can specify multiple files of CDL sources; from the CDL compiler view, all definitions come from one source) or to some version stored in TIR. The selected version depends on TIR contents and a profile. A profile is a list of the identifications to be used in the version selection process. Profiles are also stored in TIR; the profile used during the compilation is selected by CDL compiler option. The algorithm for selecting the appropriate version is following: 1. when a version is specified by a version identifier, that version is retrieved, 2. When only the entity name is specified, a profile is active, and the name of the entity exists in the profile, the version identifier stored in the profile is used, 3. otherwise, the most recent version from the main branch is used. Newly defined elements are introduced without version declaration, their version is derrived automatically from TIR contents and selected profile.

If a profile is not selected for a compilation and the currently compiled elements with same name are already in TIR, the compilation fails and the developer must perform it again with suitable profile selected.

The full description of TIR, profiles and version model of the specifications is provided in [TIR].

Connectors

Components are interconnected by connectors. The goal of connectors is to provide a support for software systems where all application components contain application logic only, while the connectors implement the necessary interaction semantics and cover deployment dependent details. Thus, connectors solve the deployment anomaly problem[con][conp].

In SOFA, connectors are first-class entities like components. Each type of connector implements semantics of specific type of interaction. A connector is described in a similar manner as a component - by connector frame and connector architecture.

The connector frame is represented by a set of role instances. A role is a generic interface of the connector intended to be tied to a component interface. The connector architecture describes connector internals. It can be simple (contains only primitive elements, directly implemented by the underlying environment) or compound (contains only instances of other connectors or components).

SOFA provides three types of predefined connectors (CSProcCall, EventDelivery, DataStream) and user-defined connectors.

Behavior protocols

Communication among SOFA components can be captured formally. Every method call or a return from a method call forms an event. In our communication model, events are atomic (e.g. they cannot be divided into several sub-events) and can be denoted by event tokens. For a method name m, event tokens !m^, ?m^, !m$ and ?m$ stand for emitting a method call, accepting a method call, emitting a return and accepting a return.

A sequence of event tokens denoting events occuring on a component form a trace. Thus, the trace <!m^; ?m$> describes activity of a caller (emitting a method call followed by accepting the return), while the trace <?m^; !m$> denotes what the calle does (accepting a call and emitting the return).

Behavior of a SOFA entity (interface, frame or architecture) is the set of all traces, which can be produced by the entity. In the case of interfaces, restricted form of event tokens is used (only m^ and m$ tokens are possible), because the role of emitor or acceptor of events is implicitly determined by the type of interface (provides or requires).

For example, let an interface I contain methods m, n. If the behavior of I is defined as {<m^; m$; n^; n$>, <m^; m$; n^; n$; n^; n$>}, it means that on the interface I, the method m should be called first, and then n should be called once or twice.

Let a frame F contain an instance i1 of the iterface I (instantiated as a provides interface ) and let the behavior of F be defined as {<?i1.m^; !i1.m$; ?i1.n^; !i1.n$>, <?i1.m^; !i1.m$; ?i1.n^; !i1.n$; ?i1.n^; !i1.n$>}. The meaning is the same as in the previous example, however the description is made from the point of view of the frame, not the interface.

Because behavior described as a (possibly infinite) set of traces is not very human-readable, we use the notation called behavior protocols. A behavior protocol is a regular-like expression on the set of all event tokens, generating the set of traces (behavior). A protocol generating behavior of an interface is called interface protocol, a protocol generating behavior of a frame is called frame protocol. Behavior of I and F from previous examples can be described by the following:

Prot-I = m; (n + (n; n))
Prot-F = ?i1.m; (?i1.n + (?i1.n; ?i1.n))

Here, "+" denotes alternative, ";" denotes sequencing and for a method name m, ?m is an abbreviation for ?m^; !m$.

Interface and frame protocols are written by a programmer into cdl. For example:

interface I {
   void m();
   void n();

   protocol: m; (n + (n; n))
};

frame F {
   provides:
      I i1;
   protocol:
      ?i1.m; (?i1.n + (?i1.n; ?i1.n));
};

There exists third type of protocols: architecture protocol. Architecture protocols are not written by a programmer, instead they are generated by CDL compiler from frame protocols of an architecture's subcomponents.

Having behavior description of SOFA entities, CDL compiler can check semantic consistency of a source code. More precisely, the compiler checks if a frame protocol conforms to the protocols of all frame's interfaces and if an architecture protocol conforms to the protocol of the architecture's frame.

SOFAnode

A single environment for developing, distributing and running SOFA applications is called a SOFAnode. A SOFAnode consists of several logical parts.

A SOFAnode is divided in five logical parts. The heart of a SOFAnode is a Template repository (TR). TR contains an implementation of components as well as their CDL description. The other parts provide the environment for developing of components (the MADE part - the CDL compiler, TIR, the code generator,...), for automatic distribution of components among SOFAnodes (the IN and OUT parts) and the runtime environment for launching component applications (the RUN part). Not all SOFAnode have to consist of all these parts.

One SOFAnode is not tied with one host - it can be distributed across a network.

The Figure 2 shows a sample scheme of several SOFAnodes connected to form a SOFAnet.

Figure 2. Scheme of sample SOFAnodes interconnected to form a SOFAnet

Scheme of sample SOFAnodes interconnected to form a SOFAnet

DCUP

The DCUP architecture is a specific form of SOFA components which enables their safe updating at runtime. It extends the component model with specific implementation objects and by a technique for updating a component at runtime.

A DCUP component can be divided in two parts - permanent and replaceable. The permanent part is not subject of the dynamic update unlike the replaceable part, which is specific for each version of the component (Figure 3).

Figure 3. Permanent and replaceable part of the component

Permanent and replaceable part of the component

The DCUP architecture introduces two control objects: Component manager (CM) and Component builder (CB). CM is the heart of the permanent part and is responsible for controlling the runtime lifecycle of the component. CB is responsible for building component internals (in the case of composed component - subcomponents, in the case of primitive component - implementation objects). Each version of the component has a specific CB.

SOFA implementation

This section describes the Java implementation of the SOFA runtime environment.

SOFA CDL to Java mapping

The SOFA CDL to Java language mapping is based on OMG IDL to Java mapping[IDLJava]. Most of the CDL types are mapped nearly identically as IDL types, mapping of the primitive types is moved to a SOFA-specific package. Component builders and deployment descriptors are generated from the SOFA CDL frames and architectures..

Runtime structure of a component

Since the Java language does not provide a notion of a component, a component is at runtime represented by a collection of Java objects. Each component is comprised of at least of two objects - component manager (CM) and component builder (CB).

CM controls the component lifecycle. It provides methods for registering subcomponents, obtaining references to provisions, and setting requirements. CM is also responsible for managing of the dynamic update of the component. CM is the first instantiated object of a component.

CB is then used for building internal elements of the component. In the case of a composed component, it creates subcomponents and ties, in the case of a primitive component it creates implementation objects.

CM is universal for all components and is parameterized by a component deployment descriptor. CB is specific for each component and is created from a CDL specification. In CB, developer must complete methods initialize (initializing of CB), onArrival (it is called by CM at the time of building the component) and onLeaving (it is called by CM at the time of terminating the component).

A composed component consists of CM and CB only. Primitive components are created by CM and CB and at least by one implementation object, which must implement all interfaces provided by the component.

The application is described by deployment descriptor (DD). Currently, two kinds of DDs are used - component deployment descriptor (CDD) for single component, and application deployment descriptor (ADD) for the whole application. CDD is automatically generated and contains description of one component only. In the generated CDD, developers must fill in the implementation version of the described component and in the case of composed component, select concrete version of subcomponents at the first level of nesting. They can also specify all applicable variants of connectors there.

ADD describes the whole application on all levels of the application's nesting hierarchy. It is created from CDDs of all the application's component; references to subcomponents in particular CDDs are recursively replaced by corresponding CDD. Therefore ADD describes tree of all components of the application. ADD is generated automatically; developers can modify ADD by adding a definition of units, which define locations where components should be launched.

Implementation versioning

One version of the component specification (the frame and the architecture) can be implemented in several ways. A primitive component can be implemented differently, a composed component can be established with different versions of subcomponents. Thus the implementations of component need versioning too.

The identification of component implementation is created from the name of the component, which is the same as the name of its architecture, and implementation version number. This number is specified at the development time by a developer.

The identification of an implementation is used by TR, by the application launcher and at the time of a dynamic update, when an instance of one component implementation is removed and replaced by an instance of another component implementation.

Connectors

The ties among components are elaborated in connectors, so that they encapsulate the whole communication between components. Typically, a connector reflects the underlaying transport technology such as CORBA and RMI. However, connectors are responsible not only for request/response marshalling and unmarshalling, but its functionality can include also synchronization, security, simple interface adaptation, and communication interception (employed e.g. for logging, benchmarking, protocol checking).

Advantageously, a connector is automatically generated using the knowledge of the properties and services offered by the deployment dock and its underlying platform. By making a connector deployment dock - dependent, SOFA avoids using generic code in connector which yields performance and scalability benefits. The information of underlying platform is not known before the components are deployed to particular docks. Technically, connector generation is performed during the deployment phase, immediately preceding an application run.

The current SOFA implementation provides support for the three predefined simple connectors: CSProcCall, EventDelivery, and DataStream. In addition to local communication (in a single address space), CORBA and RMI middleware can be employed. The implementation of these connectors consists of primitive elements, such as role, stub, skeleton, etc. [conb].

SOFAnode implementation

The basic entity of SOFA runtime environment is a deployment dock. The deployment dock is responsible for launching and instantiating of components and for providing services for running components.

Several deployment docks may be run in the scope of one SOFAnode, each dock may run on a different computer. Deployment docks are identified by the SOFAnode that they are running on and by the dock name. Each dock is registered in a Deployment dock registry. The deployment dock registry is one per SOFAnode and is used for obtaining references to concrete deployment docks (see Figure 4).

Figure 4. Structure of runtime environment

Structure of runtime environment

For the components and the application launcher, services are provided by the deployment dock accessible through the normal SOFA connector.

Launching of application

All components of an application may be launched in one deployment dock or in several docks (it depends on the ADD and splitting of the application into deployment units).

The application launcher obtains a home dock and launches the application top-level component in it. The component builder is then responsible for launching all of its subcomponents. Recursively, CB of each subcomponent is responsible for launching all of its subcomponents - the launching process stops in primitive components. CB then builds ties among already launched subcomponents: first, CB obtains references to provided interfaces of all subcomponents and creates binds among all subcomponents. Second, it informs all subcomponents that they can connect their subsumed interfaces. Finally, it connects its own delegated interfaces and prepares subsumed interfaces.

Runtime identification

To allow several instances of the same component to be run at the same time in one deployment dock, the instances of the component are identified by a unique runtime ID that plays the same role as a process ID (PID) in operating systems.

SOFA net implementation

SOFA nodes (connected via SOFA connectors) create a SOFA net. Every SOFA node in the net consists of the Template Repository and the InOut server, which is responsible for component distribution among the nodes. Both of them are (remotely) accessible through connectors and thus must be registered in the Deployment Dock Registry. Following picture (see Figure 5) shows the layout of TR and InOut within a single SOFA node and their interconnection with other subjects of the same and/or another SOFA node.

Figure 5. SOFA node structure

SOFA node structure
All three kinds of connections depicted in the figure above (between two InOut interfaces of two different SOFA nodes, between an InOut interface and TR and between an InOut interface and a management interface), are implemented via SOFA connectors.

Bundle

Distributed SW is always delivered being wrapped in a bundle. A bundle is a common JAR file usually including all necessary component files along with description of packed components. The manifest file of this JAR file contains the description and the path to components within the JAR file.

Bundles also support distribution of component description only. In such a case, the component binary files are not wrapped into the bundle; however, the component description is included within the manifest file. Further, distribution of components and distribution of component descriptions, may be combined - in one bundle can be stored an arbitrary number of components as well as an arbitrary number of component descriptions. If a component is actually stored in the bundle, its description must be stored there as well.

Template Repository

All components, types and interfaces being installed into a SOFA node, are persistently stored into TR. The current implementation of TR employs the file system of the host (where the SOFA node is running on), to store all necessary component files.

TR offers two operations - one for storing a bundle of components into local TR (storeBundle), second one for retrieving components (packed in a bundle) from local TR (getBundle). Further, it provides a method for listing all installed components (list) and a method for quering the existence of a concrete component (contains).

InOut - component exchange

The InOut server provides two interfaces. It mediates the component exchange between two SOFA nodes and also receives commmands from clients. It requires the interface provided by local TR (to store and retrieve components) and the interface of remote SOFA nodes (to push and pull components).

The interface between two SOFA nodes offers two methods only. One for pushing components to a remote SOFA node (pushBundle) and second one for pulling components from a remote SOFA node (pullBundle).

The InOut server provides an interface containing methods for commanding the SOFA node (used by remote clients). The interface contains three methods for telling the SOFA node to distribute components (distributeComponent), to distribute component descriptions only (distributeOffers) and to distribute the mix of both (distribute). Similar three methods exist for obtaining components, their description or the mix of both (obtainComponents, obtainOffers and obtain). The method list allows the client to get a list of all components installed on the SOFA node.

SOFA Client

The SOFA client allows users to command an arbitrary number of (possibly remote) SOFA nodes. The client employs a SOFA connector to connect to InOut server of the particular SOFA node.

Client configuration file

The list of SOFA references (the string representation of a SOFA connector) of all known SOFA nodes is stored in a configuration file. The format of this file must obey following rules:
  • a text file with one entry written on a single row.
  • each entry consists from a key and a value delimited by the '=' character
  • each key is unique
  • each value contains the SOFA refence to a SOFA node
  • any entry can be disabled by the '#' character written in the begining of the entry
An example of a configuration file follows:
      local=SOFA.Connector.ECG.SGenerator.Connectors.DeplDockConnector-RMI;//localhost:1099/SOFA/SOFAnode/InOut/Connector/InOut2ClientRMISkel/localInOut-
      local2= SOFA.Connector.ECG.SGenerator.Connectors.DeplDockConnector-RMI;//localhost:1099/SOFA/SOFAnode/InOut/Connector/InOut2ClientRMISkel/local2-
    
The configuration file name is provided to the application when the client is to be started.

Client GUI window

When a client is started, a main dialog appears. It contains two vertical panels and a few buttons below them. In each of the panels is shown a list of all known SOFA nodes (actually these ones which are listed in the configuration file). If the client successfully connects to a SOFA node and there are some components installed on this SOFA node, the proper entry can be explored and the user can see all components installed on the particular SOFA node. Following picture depicts the main window of the client (see Figure 6).

Figure 6. Client main window

Client main window

Users may select a number of installed components (or SOFA nodes, in such a case, it is like the user selected all components on the SOFA node) and a number of SOFA nodes. Pressing buttons below, the user can distribute selected components to the selected SOFA nodes. The text area below shows information and error messages which are generated during the component exchange.

The SOFA node list panel

If the client can not reach a SOFA node listed in the configuration file, a text - not accessible appears next to the SOFA node name. Reached SOFA nodes can be explored by expanding (if there is at least one component installed on it) the appropriate node in the list.

Before the users command the client to distribute any component or its description only, they have to select the components and the SOFA nodes where the components are to be distributed to. The selection is performed by clicking the left mouse button over the component/SOFA node name. Components can be selected in two different ways. Users may either select the component names or the SOFA node name. In the latter case, all components installed on the selected SOFA node are supposed to be distributed. The users can select a group of component/SOFA node names by pressing the keys Shift or Ctrl when they are clicking over the names.

The command buttons

The panel below the lists of known SOFA nodes contains five buttons for distribution components or their description only in both modes (pull/push) and for refreshing the lists of SOFA nodes and their contents.

Note: A successful execution of any distribution operation does not change the content of the SOFA node list panel. The user must press the refresh button (see below) to renew the list content.

Push descriptions. Pressing the button serves for pushing the description of selected components (on the left panel) into the selected SOFA nodes (on the right panel). A confirmation dialog should appear.

Push components. Pressing the button serves for pushing the selected components (on the left panel) into the selected SOFA nodes (on the right panel). A confirmation dialog should appear.

Pull components. Pressing the button serves for pulling the selected components from the right panel into the selected SOFA nodes on the left panel. A confirmation dialog should appear.

Pull descriptions. Pressing the button serves for pulling the description of selected components from the right panel into the selected SOFA nodes on the left panel. A confirmation dialog should appear.

Refresh content. Pressing the button makes the client to (re)connect to all known SOFA nodes and ask for all components stored in there. It refreshes the content of both displayed lists.

Confirmation dialog

Just before the component distribution is going to be performed, the user has to confirm the selected operation. A confirmation dialog appears and the user may either accept or refuse it. See Figure 7 as an example of the confirmation dialog.

Figure 7. Confirmation dialog

Confirmation dialog

Dynamic update (DCUP implementation)

The update of a component is controlled by CM. It provides an interface for performing an update by a new deployment descriptor.

To allow component to reach quiescent state, CM provides a thread registry (ThrReg). ThrReg registers all threads which enter and/or leave the component. An update is allowed only if no thread is executed inside the component and if the component does not wait in a call to another component, i.e. a component is quiescent.

The dynamic update of a component proceeds as follows - first, the flag indicating the update is set. When this flag is set, all incoming calls are blocked. CM then waits for the quiescent state. Next, CM replaces CB and all internals of the component. After the successful update, CM unsets a flag indicating the update and the new version of the component is ready. If any incoming call was blocked, it is unblocked and allowed to enter the component.

The current implementation allows a dynamic update for primitive components that implement the same version of architecture only. Further, updatable components must not start new thread of invocation.

Dynamic update and type compatibility

To allow dynamic update of components, we must deal with several versions of classes with same name but different content. The older versions of SOFA implementation use distinct classloader for each component. However, this approach brings problems with local variant of connectors - they must use Java reflection API, which degrades performance of the system.

Current implementation uses byte-code manipulation to solve this problem. The byte-code of classes that form a component is manipulated in TR - names and references in the classes are augmented by the version identifiers.

ASM tool[ASM] is used for the byte-code manipultaion.

Developing a SOFA application

The development of SOFA components can be divided into several stages. First, the developer describes the interfaces, component frame and architecture in CDL. This description is compiled by the CDL compiler and stored in the Type Information Repository (TIR). The code generator then generates Java types, CB and CDD from the descriptions in TIR.

When the types, CB and CDD are generated, the developer must provide an implementation of the component. In the case of a composed component, only CDD must be completed by filling the implementation version number and by specifying concrete versions of the subcomponents; optionally, by specifying concrete values of component properties. In the case of a primitive component, apart from completing CDD, developer must create the implementation of the component.

The complete component is then put to TR. If the component is top-level, i.e. it represents a SOFA application, ADD is made from CDDs of all components used in the application and from a specification file (specfile). The specfile describes the middleware used in connectors as well as the distribution of the components over the deployment docks.

Now is the component ready for instantiating.

How to create SOFA application in several steps

We will describe the process of creating a simple component application. The application will consist of three components - one composed and two primitive ones.

  1. Install the distribution archive - this description assumes that everything is on one machine with the following configuration file:

    POLICY        default
    RMIPORT       2000
    RMIHOST       localhost
    ORBPORT       2001
    ORBHOST       localhost
    TIRSTORAGE    default
    TRURL         http://localhost/TR/
    RMICODEBASE   http://localhost/TR/conn/
    TRDIR         /opt/WWW/TR
    
  2. Run rmiregistry (rmiregistry 2000 - rmiregistry must not have the CLASSPATH environment variable set)

  3. Run orbd (orbd -ORBInitialPort 2001)

  4. Run TIR (tir)

  5. Create the CDL description of components:

    /* logdemo.cdl */
    
    module SOFA {
      module demos {
        module logdemo {
    
          interface LogInterface {
            void log(in string mesg);
          };
    
          frame Tester {
            requires:
              LogInterface Log;
          };
    
          frame Logger {
            provides:
              LogInterface Log;
          };
        };
      };
    };
    
    architecture CUNI ::SOFA::demos::logdemo::Tester implements ::SOFA::demos::logdemo::Tester
    primitive;
    architecture CUNI ::SOFA::demos::logdemo::Logger implements ::SOFA::demos::logdemo::Logger
    primitive;
    
    system architecture CUNI ::SOFA::demos::logdemo::logdemo implements ::SOFA::libs::Application {
      inst ::SOFA::demos::logdemo::Tester tester;
      inst ::SOFA::demos::logdemo::Logger logger;
    
      bind tester:Log to logger:Log using CSProcCall;
    };
    

    There are two primitive components - Tester and Logger. Logger provides the interface LogInterface, Tester requires this interface, in the component logdemo these two components are instantiated and their provision and requirement are bound together.

    Figure 8. Logdemo application

    Logdemo application
  6. Compile the CDL file with the CDL compiler (cdlc logdemo.cdl).

    The CDL compiler stores the CDL descriptions in TIR for later usage.

  7. Generate the Java classes and CDD for each component with the code generator,

    • codegen ::CUNI::SOFA::demos::logdemo::logdemo
    • codegen ::CUNI::SOFA::demos::logdemo::Logger
    • codegen ::CUNI::SOFA::demos::logdemo::Tester

    Code generator generates all types (in this example only the LogInterface type), CB and CDD for each component. For primitive components, it also generates two base classes of implementation. The _BaseComponentImpl class is the base class of the implementation and is not intended for developer changes. The ComponentImpl class have to be changed by developer and it is generated only if it does not exist. It contains four predefined methods - start (called during start of the component), stop (called during destroying of the component), store, restore.

  8. Now you must provide the implementation of the primitive components. Code typeset in bold has to be provided by the developer (code typeset in regular font is generated by the codegen tool and is shown to provide context). First, the implementation of Logger, i.e. implement the log method and set that it can be updatable:

    // LoggerImpl.java
    package SOFA.demos.logdemo;
    
    public class LoggerImpl extends _BaseLoggerImpl {
      ...
    
      public void log(java.lang.String msg) {
        System.out.println("**** LOG: "+msg);
      }
    
      public boolean isUpdatable() {
        return true;
      }
    
    }
    

    Now create the implementation for Tester. The Tester will create a thread and repeatly calls method log on Log requirement.

    // TesterImpl.java
    package SOFA.demos.logdemo;
    
    public class TesterImpl extends _BaseTesterImpl implements Runnable {
    
      Thread thr;
      boolean end;
      boolean stopped;
    
      ...
    
      public void start() throws SOFA.Component.ComponentLifecycleException {
        thr = new Thread(this);
        thr.start();
      }
    
      public void stop() throws SOFA.Component.ComponentLifecycleException {
        setEnd();
        Thread thr = Thread.currentThread();
        while (!isStopped()) {
          try {
            thr.sleep(100);
          } catch (InterruptedException e) {}
        }
      }
    
      ...
    
      public void setEnd() {
        end = true;
      }
    
      public boolean isStopped() {
        return stopped;
      }
    
      public void run() {
        while (!end) {
          LogRequirement.log("Hello world");
          try {
            Thread.currentThread().sleep(1000);
          } catch (Exception e) {
            ;
          }
        }
        stopped = true;
      }
     
    }
    

    Now, the CDDs of all the components have to be completed. For each component, a version identifier has to be selected and entered into the <version> element of the CDD. We recommend using a three-element dot-separated notation (n1.n2.n3). E.g., if 0.0.1 is chosen, the <version> element of the CDD is modified to

    <version>0.0.1</version>

    Also, in the CDD of the logdemo component, references to the Logger and Tester subcomponents have to be entered in the <component_ref> elements, by setting the arch and version tags. Assuming version "0.0.1" was chosen for both subcomponents, the resulting <component_ref> elements are:

    <component_ref inst="tester" arch="::CUNI::SOFA::demos::logdemo::Tester" version="0.0.1"/>
    <component_ref inst="logger" arch="::CUNI::SOFA::demos::logdemo::Logger" version="0.0.1"/>
    
  9. Compile all the implementation classes. The CLASSPATH variable must contain the files sofa-base.jar, sofa-map.jar from the SOFA distribution package and the directory with your implementation classes.

  10. Install all three components to TR directory (e.g. installToTR LoggerCDD.xml SOFA/demos/logdemo/LoggerBuilder.class SOFA/demos/logdemo/_BaseLoggerImpl.class SOFA/demos/logdemo/LoggerImpl.class).

  11. Generate the specfile (genspecfile ::CUNI::SOFA::demos::logdemo::logdemo[0.0.1] logdemo). In the specfile, which is generated by the command, you can change the middleware used in connectors (default is none, i.e. LOCAL connectors) and/or specify, how the application should be distributed over the deployment docks. The following text assumes the defaults, i.e. connectors will be local and all components will be executed in single deployment dock.

  12. Prepare the application for launching (makeAppl logdemo.ssf).

  13. Launch the deployment dock registry(ddockreg) and one deployment dock (ddock NameOfDock).

  14. Now you can launch whole application (launchAppl ::CUNI::SOFA::demos::logdemo::logdemo[0.0.1] NameOfDock). You can see the output of Logger component.

  15. To terminate the application, run killAppl NameOfDock.

  16. To perform a dynamic update, you must first implement a second version of the Logger component. For example, change the string "**** LOG:" to the string "**** NEW LOG:" in the Logger implementation class and also change the implementation version in its CDD.

    // LoggerImpl.java
    
      ...
    
      public void log(java.lang.String msg) {
      
        System.out.println("**** NEW LOG: "+msg);
      
      }
    }
    

    Install this new version to TR (installToTIR LoggerCDD.xml SOFA/demos/logdemo/LoggerBuilder.class SOFA/demos/logdemo/_BaseLoggerImpl.class SOFA/demos/logdemo/LoggerImpl.class).

  17. Launch the logdemo application. Then launch updateTool (updateTool NameOfDock) and update the Logger component.

    > updateTool  NameOfDock
    Obtaining depldock registry...OK
    Obtaining reference to depldock...OK
    Creating connector to depldock...OK
    Choose component for update
    ===========================
    0 ... logger:vishnu1 (::CUNI::SOFA::demos::logdemo::Logger[0.0.1])
    1 ... do nothing
    0
    Choose new component to update
    ==============================
    0 ... ::CUNI::SOFA::demos::logdemo::Logger[0.0.2]
    1 ... ::CUNI::SOFA::demos::logdemo::logdemo[0.0.1]
    2 ... ::CUNI::SOFA::demos::logdemo::Tester[0.0.1]
    3 ... do nothing
    0
    Obtaining and parsing new deployment descriptor...OK
    Updating component...OK
    

Related work

Several systems exist that facilitate building of application of software components.

Microsoft's COM/DCOM[DCOM] and Sun Microsystems EJB[EJB] are two industrially used component systems. Both of them have strong runtime support and they are widely used. However they do not have an formal architecture description and, unlike SOFA, their components do not have an explicit specification of requirements.

Closer to SOFA is OMG's CORBA Component Model[CORBA]. It supports the definition of component's provide and require interfaces, but it omits the formal architecture description. At least two implementations of CCM - OpenCCM and MICO - exist.

Fractal[fractal] is a general software composition framework that supports component-based programming. It supports a definition of provisions and requirements of a component, composed components and a formal architecture description, bindings among components, component sharing, etc. The Fractal itself is an abstract framework and it should be understood as a base of concrete frameworks. A reference implementation of the Fractal in Java - Julia - exists.

References

[sofa] Frantisek Plasil, Dusan Balek, and Radovan Janecek. SOFA/DCUP: Architecture for Component Trading and Dynamic Updating. Proceedings of ICCDS 98, May 4-6, 1998, Annapolis, Maryland, USA. IEEE CS Press. 1998.

[TIR] Vladimir Mencl and Petr Hnetynka. Managing Evolution of Component Specifications using a Federation of Repositories. Tech. Report No. 2001/2. Dep. of SW Engineering, Charles University, Prague. Jun 2001.

[IDLJava] Object Management Group. IDL to Java Language Mapping. document formal/02-08-05. 2002.

[con] Dusan Balek and Frantisek Plasil. Software Connectors and Their Role in Component Deployment. Proceedings of DAIS'01, Krakow. Kluwer. Sep 2001.

[conp] Dusan Balek. Connectors in Software Architectures. Ph.D. Thesis, Charles university, Prague. 2002.

[conb] Lubomir Bulej and Tomas Bures. A Connector Model Suitable for Automatic Generation of Connectors, Charles university, Prague. Tech. Report. Dep. of SW Engineering, Charles University, Prague. Jan 2003.

[EJB] Enterprise JavaBeans Specification. Version 2.0. Sun Microsystems. 2001.

[CORBA] Object Management Group. The Common Object Request Broker: Architecture and Specification. version 3.0. document formal/02-06-33. 2002.

[DCOM] F. E. Redmond III. DCOM: Microsoft Distributed Component Object Model. IDG Books Worldwide, Inc.,Foster City. 1997.

A. Syntactic diagrams of CDL

The syntactic diagrams of CDL.

B. Limitations and missing features of the current implementation

SOFAnode:

  • Automatic distribution among SOFAnodes is not implemented.

  • TR is not implemented. Instead, an http server is used for obtaining component classes and TIR for obtaining component specifications.

  • Storing and restoring of component state during dynamic update, launching and terminating is not implemented.

Code generator:

  • does not generate CDL unions

  • does not support arrays in bindings in the component builder; the developer has to write the corresponding code by hand

C. Distribution package and installation

The SOFA implementation requires Java2 (jdk1.4), http server and Perl (on Windows platform, we recommended the ActivePerl distribution). The environment can run without an http server, but everything must be run on a single machine then.

The SOFA implementation is distributed as a ZIP archive. The directory structure is as follows: bin with scripts for launching Java programs, conf with configuration files, lib with JAR files and finally storage with TIR content. This archive must be extracted on all machines that will form one SOFAnode.

The distribution also contains the trcnt.zip archive, which contains initial structure of TR. Extract this archive into the directory where the http server has access. The http server need to be run only on one machine of the SOFAnode.

When installing, first edit the conf/main.conf file, where at least the options TRURL, TRDIR, RMICODEBASE must be specified (all options are explained in the file).

Launching scripts are placed in the bin directory. All scripts are written in Perl. The list of scripts is as follows:

  • tir - launching of TIR

  • pverif - standalone behavior protocol verifier

  • tirbrowser - very simple TIR browser

  • tiraddprofile - creates new profile in TIR

  • cdlc - launching of CDL compiler

  • codegen - launching of code generator

  • installToTR - puts the implementation of a component into a directory structure, which serves as TR, the script must have a write access to the http server document hierarchy

  • genspecfile - generates specfile

  • makeAppl - creates ADD and prepares application for launching, the script must have a write access to the http server document hierarchy

  • launchAppl - launches SOFA application

  • killAppl - terminates SOFA application

  • updateTool - tool for dynamic updating of components

All scripts read configuration from the conf/main.conf file. This configuration can be overriden by commandline options. All scripts provide commandline option -h, which prints description with usage of the specific script.

Figure C.1. Example of SOFAnode configuration

Example of SOFAnode configuration

If the deployment dock is running on a different machine than the deployment dock registry, the name of the SOFAnode where the particular dock belongs (option -n) must be specified. The default name of the SOFAnode is the hostname of the machine. On Figure C.1, dock 2 and dock 3 should have SOFAnode name explicitly set to B.

D. License

The SOFA implementation is distributed under the GNU Lesser General Public License (http://www.gnu.org/licenses/lgpl.html)

Parts of the CDL compiler were generated by the JLex (http://www.cs.princeton.edu/~appel/modern/java/JLex/) and CUP (http://www.cs.princeton.edu/~appel/modern/java/CUP/) tools.