Chapter 4
The Configuration Framework

[pdf version]
This document is made available under the terms of the GNU Free Documentation License as published by the Free Software Foundation (http://www.gnu.org/copyleft/fdl.html)

4.1  Principles of the Framework

As defined by IEEE-Std-729-1983, ``Configuration Management is the process of identifying and defining the items in the system, controlling the change of these items throughout their lifecycle, recording and reporting the status of items and change requests, and verifying the completeness and correctness of items''. The Jonathan platform is made up of software components, many of which (object instances) are created dynamically. Configuration management is concerned with the initial configuration of the platform (at bootstrap time) and with its reconfiguration during its lifetime. At this stage, we only consider the initial configuration.

A simple example (borrowed from a presentation by Bruno Dumant (Kelua)) will help us understand the bootstrap configuration problem. Consider a software system made of three components A_Impl, B_Impl and C_Impl, as described on Figure 4.1, both by program skeletons and by a structural graph.

Figures/config-pb.gif
Figure 4.1: The configuration problem

Now suppose we want to use a new implementation of A, A_Impl1. With the above organization, there is no other way than modifying the code of C_Impl to replace A_Impl by A_Impl1. This is quite unsatisfactory since it makes the dependencies between components explicit in the code of the components, while such dependencies should only appear in a global description such as the graph of Figure 4.1.

What would be needed is a separate structural description of the system in terms of its three components, in which the versions to be used would be specified. Changing a component version would then only induce a change in the description, not in the code of the components.

The overall principle of this solution is described on Figure 4.2. Each component instance is generated by a factory, which take as a parameter a description of this instance retrieved in an independent configuration description, using a component name.

Figures/config-princip.gif
Figure 4.2: A solution to the configuration problem: overview

The actual solution adopted in Jonathan (decribed in detail in the following sections) follows this overall pattern, but takes an additional step to cater for complex dependencies. The component name is used to retrieve a context (a description of the various parameters used to create a specific instance). This context, in turn, is used as a parameter by the factory.

The framework for configuration therefore includes two main functions.

A factory (for a class C) is a class that is used to create instances of C and to perform various related administrative functions, such as keeping track of those instances, removing instances, etc. A configuration is a tree-structured database that describes an instance of (part of) the Jonathan platform by a set of typed values that may be retrieved by name. The values describe various characteristics of the platform. For instance, the name /jonathan/tcpip/verbose designates a boolean value that indicates whether the TCP communication primitives should be executed in verbose mode (i.e. should display informative messages during execution). The two notions, factory and configuration, are closely related: a factory takes a configuration as a parameter, to find information needed to create an instance; conversely, a configuration usually contains the names of factories that are used to create various components of the platform described by the configuration.

The next two sections respectively describe the implementation of configurations and factories. The third section gives the global picture of the Jonathan configuration framework.

4.1.1   Contexts and configurations

A context is a form of a naming context: it represents a set of elements, each of which is a typed object, identified by a name. A context is used to retrieve an object by its name. A special case of a context, frequently used, is a tree context, structured as a tree. In Jonathan, a specific implementation of a context is JContextFactory.JContext, and a specific implementation of a tree context is TreeContextFactory.TreeContext.

A configuration is a special form of a tree context, which contains four particular types of elements (Alias, Atom, Assemblage, and Property). An Alias is just an alternative name for an existing element (like a soft link in an Unix file system). An Atom represents a class and contains the package qualified name of that class (e.g.

org.objectweb.jonathan.libs.resources.JSchedulerFactory); it is used to retrieve the specified class, in order to create instances of it. An Assemblage is the association of two names: the name of a factory and the name of a configuration; it is used to create instances using the factory and the configuration. A property is used to register the value of a primitive type; it contains a type and a value.

We illustrate these notions with a fragment of the bootstrap configuration for Jonathan, displayed below in the form of a tree (the actual form is decribed in Section  reftech).

  [/] 
   [jonathan] 
       ... 
       [IPv4ConnectionFactory]                        -- a configuration 
           factory => org.objectweb.jonathan.libs.resources.tcpip.
                       IPv4ConnectionFactoryFactory   -- an atom 
           instance => {factory, .}                   -- an assemblage 
           verbose -> /jonathan/tcpip/verbose         -- an alias 
       ... 
       [JConnectionMgr] 
           factory => org.objectweb.jonathan.libs.resources.tcpip.
                       JConnectionMgrFactory 
           instance => {factory, .} 
           connection_factory -> /jonathan/Ipv4ConnectionFactory/instance] 
       ... 
       [JDomain] 
           [binders] 
               0 -> /david/orbs/iiop/instance 
               org.objectweb.david.libs.binding.orbs.iiop.IIOPORB =>
                         (int.class, 0)               -- a property
       ... 
       [tcpip] 
           verbose => (Boolean.class, false)          -- a property
       ... 
   [david] 
       ... 
       [orbs] 
           ... 
           [iiop] 
               factory => 
                 org.objectweb.david.libs.binding.orbs.iiop.IIOPORBFactory 
               instance => {factory, .} 
               ... 
               [binder_context] 
                   TcpIpConnectionMgr => {/jonathan/JConnectionMgr/factory, .} 
                   connection_factory -> david/GIOP/ConnectionFactory/instance 
                   Scheduler -> /jonathan/Scheduler/instance 
                   direct_local_optimization => (Boolean.class, false) 
                   use_java_serialization => (Boolean.class, false) 
        ... 
    [jeremie] 
       [registry] 
              ...

A configuration of name name is denoted as [name]; an atom of name name and value val is denoted as name => val; an assemblage of name name, which associates a factory fact and a configuration conf is denoted as name => {fact, conf}; an alias is denoted as name -> target; a property of name name, value val and type t is denoted as name => (t, val).

For instance, the configuration /jonathan/IPv4ConnectionFactory describes a class of that name: instance indicates that instances of this class are created by the factory designated by local name factory using the current configuration; factory in turn points to the qualified class name of the factory; verbose is an alias, whose target designates a boolean with value false.

As another example, consider the configuration /jonathan/JDomain/binders. As described in the binding framework, JDomain is used to marshal and unmarshal identifiers when transmitted on a network. The declaration org.objectweb.david.libs.binding.orbs.iiop.IIOPORB => (int.class, 0) is used when an identifier in the context IIOPORB is marshalled. The associated value, 0, is included in the marshalled identifier. When the marshalled identifier is received, the integer value 0 is used to create the appropriate naming context needed to resolve it: using the declaration 0 -> /david/orbs/iiop/instance, and then the declaration associated with the target of the alias,the appropriate factory (i.e.

org.objectweb.david.libs.binding.orbs.iiop.IIOPORBFactory) may be found and used to create an instance of the context.

4.1.2   Factories

A factory (for a class C) is a class that is used to create instances of C. In Jonathan, factories derive from the GenericFactory abstract class, which in turn implements the Factory interface. Factory defines a method newObject(Context c), which returns a new object, using information found in context c. More precisely, a set of ``components'' (named values) is retrieved from the context, and those components are used for the creation. The definition and use of these components is specific to each factory.

GenericFactory provides a generic mechanism that relies on a pool of objects and allows existing objects to be reused if needed. Before creating an object, the factory looks up the pool for an object created by the same factory with the same context components. If such an object is found, it is reused; if not, a new instance is created and inserted in the pool. In addition to the code that manages object pools, not described here, GenericFactory contains the following code.

 00042 public abstract class GenericFactory implements Factory {
          [...]
 00062    public Object newObject(Context _c) throws JonathanException {
 00063       synchronized (GenericFactory.class) {
          [...]  // the code for object pool management has been omitted
 00082 
 00083          Object[] $found_components = getUsedComponents(_c);
 00084 
          [...]
 00087             $instance = newInstance(_c,$found_components); 
          [...]
 00094          return $instance;
 00095       }
 00096    }
 00097 
          [...]
 00120    abstract protected Object[] getUsedComponents(Context _c);
 00121               // this method is supplied by the specific factory 
 00122 
 00135    abstract protected Object newInstance(Context _c,Object[] _used_components)
 00136       throws JonathanException ;
 00137               // this method is supplied by the specific factory 
          [...]
 00163 }

As an example of a specific factory, let us look at JIOPFactory, a factory that creates instances of JIOP, a binder for binding Jeremie identifiers to remote objects.

 00045 public class JIOPFactory extends GenericFactory {
 00046 
 00047    static final JIOPFactory factory = new JIOPFactory();
 00048 
 00068    final protected Object[] getUsedComponents(Context _c) {
 00069 
 00070       Object[] used_components = { Context.NO_VALUE, Context.NO_VALUE, 
 00071                                    Context.NO_VALUE, Context.NO_VALUE };
 00072       
 00073       if (_c != null) {
 00074          used_components[0] = 
 00075             _c.getValue("ChunkFactory",(char) 0);
 00076          used_components[1] = 
 00077             _c.getValue("MarshallerFactory",(char) 0);
 00078          used_components[2] = 
 00079             _c.getValue("ServicesHandler",(char) 0);
 00080          used_components[3] = 
 00081             _c.getValue("binder_context",(char) 0);
 00082       }
 00083       
 00084       if (! (used_components[0] instanceof ChunkFactory)) {
 00085          throw new InternalException("Component ChunkFactory of type 
                                            ChunkFactory " + 
 00086                                      "required by JIOP.");
 00087       }
 00088       if (! (used_components[1] instanceof MarshallerFactory)) {
 00089          throw new InternalException("Component MarshallerFactory of type 
                                            MarshallerFactory " +
 00090                                      "required by JIOP.");
 00091       }
 00092       if (! (used_components[2] instanceof ServicesHandler)) {
 00093          used_components[2] = null;
 00094       }
 00095       if (! (used_components[3] instanceof Context)) {
 00096          throw new InternalException("Component binder_context of type Context " + 
 00097                                      "required by JIOP.");       
 00098       }
 00099       return used_components;
 00100    }
 00101    
 00112    final protected Object newInstance(Context _c,Object[] _used_components) 
 00113       throws JonathanException {
 00114       return new JIOP(_c,_used_components);
 00115    }
 00116 }

Note that some of the components are required; if they are not present, the instance cannot be created and an exception is thrown. The constructor of JIOP uses the used_components to initialize its instance variables, as follows:


 00065 public class JIOP implements JRMIBFactory {
 00066 
 00067    IIOPBinder binder;
 00068    JRMIStubFactory stub_factory;
 00069 
 00073    protected JIOP() {
 00074       super();
 00075    }
 00076 
 00077    JIOP(Context c,Object[] used_components) throws JonathanException {
 00078       super();
 00079       initialize(c,used_components);
 00080    }
 00081 
 00089    protected void initialize(Context c,Object[] used_components)
 00090       throws JonathanException {
 00091       Context jiop_context = (Context) used_components[3];
 00092       ChunkFactory chunk_factory = (ChunkFactory) used_components[0];
 00093       MarshallerFactory marshaller_factory = 
                             (MarshallerFactory) used_components[1];
 00094       stub_factory = new StdStubFactory(jiop_context,marshaller_factory);
 00095       ServicesHandler services_handler = (ServicesHandler) used_components[2];
 00096       binder = new IIOPBinder((Context) used_components[3],chunk_factory,
                              marshaller_factory, 
 00097                        services_handler,stub_factory,this,null);
 00098    }
 ...
          [...]               // the methods of JIOP 
 00175 }

An example of the use of this factory may be found in the static initializer of LocateRegistrY:

 00069  $initial_context = (Context) 
 00070      Kernel.newConfiguration(LocateRegistry.class).
                                                getValue("/jeremie/jiop",'/');
 00071  binder = (JIOP)(new JIOPFactory()).newObject($initial_context);
 00072  default_port = 
 00073      $initial_context.getIntValue("/jeremie/registry/default_port",'/'); 

In lines 69 to 71, a new instance of JIOP is created, using a mechanism that is detailed in the next section. In lines 72 and 73, a specific field of LocateRegistry: is initialized using a value retrieved in the context.

4.1.3   Putting it all together

Creating a component instance

The configuration framework provides a uniform mechanism to create an instance of each component of the platform at bootstrap time. As seen in the last example, a component created by a factory is parameterized by an initial context provided as a parameter to the factory. The factory uses this context to retrieve all the information (other components, values, etc.) needed to create the component. The initial context is created using the bootstrap configuration.

We detail this mechanism, using our first introductory example. Recall that class C_Impl ``uses'' an instance A_Impl of class A. This instance is created by the following sequence (the same as that of the last example).

01  ctx = (Context) Kernel.newConfiguration(C.Class).getValue("a_name",'/'); 
02  A_Impl = (A) (new AFactory()).newObject(ctx);                                 

Here we assume that AFactory is a factory for A, and that "a_name" designates the configuration that describes the particular version of A_Impl in the configuration file.

In line 01, a context is created in two steps

  • First a configuration (call it config) is created by Kernel.newConfiguration(C.Class). Note that config is parameterized by C, which allows for a fine tuning of the configuration process by having the ``uses'' relation depend on the ``user'' class as well as on the used class. In the current implementation, the only parameter of the user class is its class loader.

    The Kernel class is an executable form of the bootstrap configuration file. It is automatically created from this file at system generation. This is explained further below.

  • The configuration config is used to create the context for A_Impl by calling config.getValue("a_name",'/').

In line 02, a factory for A is first created. It then generates the new instance of A, using the needed elements contained in the context, as explained in Section 4.1.2. The whole process is summarized on Figure 4.3.

Figures/config-schema.gif
Figure 4.3: Bootstrap configuration in Jonathan

Technical details

The bootstrap configuration (file Kernel.kcf is written in XML. Its format is described in a DTD, configuration.dtd. It is an XML representation of the tree structure described in Section 4.1.1. The Kernel.java file is generated from the bootstrap configuration by the XML2Kernel tool.

Actual configuration generation is done by a set of classes present in the kernel package (KKernel, JContextFactory, JConfigurationFactory).

Retrieving an element by name in a configuration through getValue(name) first finds the element of that name if it exists, then calls getValue() on that element. This method is overloaded in the implementation of each specific element type, and has the following effect:

  • for a property element, return its value;
  • for an atom (class JAtom), load the class described in the atom using the appropriate class loader, and create an instance of that class;
  • for an assemblage (class JAssemblage), find the factory and the context described in the assemblage, then create an instance by calling the factory with the context;
  • for an alias (class JAlias), apply getValue to the target element of the alias.
The net result is that new instances are created through the indirect mechanism of Figure 4.3, without ever using the new method in the body of the classes using the instances.

4.2  The Framework in Use

We illustrate the use of the configuration framework by simple examples.

Example 1: instantiating server and client in a communication protocol.  

This example is taken from a simple client-server application, described in the communication framework tutorial. The main method of the server contains the following sequence.

// Initializing factories
 Context context =  Kernel.newConfiguration(Server.class);
 JScheduler scheduler = 
    (JScheduler) context.getValue("/jonathan/JScheduler/instance",'/');
 JChunkFactory cf = 
    (JChunkFactory) context.getValue("/jonathan/JChunkFactory/instance",'/');
 MarshallerFactory marshaller_factory =
    new CDRMarshallerFactory(cf);
 SharedServerSession.marshaller_factory = marshaller_factory;

// Initializing TCP/IP protocol
 TcpIpConnectionMgr connection_mgr = 
    (TcpIpConnectionMgr) context.getValue("/jonathan/JConnectionMgr/instance",'/');
 TcpIpProtocol protocol = 
    new TcpIpProtocol(context, connection_mgr, scheduler,cf,marshaller_factory);

The corresponding sequence in the main method of the client is identical, except that Client.class is substituted for Server.class in the first instruction.

The first instruction builds a context parameterized by the client or server class (actually by their class loader). The following instructions create the needed resources, according to the descriptions contained in the bootstrap configuration file and designated by identifiers.

Suppose the bootstrap configuration file contains the following sequence within the configuration of name jonathan

      </CONFIGURATION></ELEM>
      <ELEM name="JScheduler"><CONFIGURATION>
         <ELEM name="instance">
            <ASSEMBLAGE factory="/jonathan/JScheduler/factory" configuration="/jonathan/JScheduler"/>
         </ELEM>
         <ELEM name="factory">
            <ATOM class="org.objectweb.jonathan.libs.resources.JSchedulerFactory"/>
         </ELEM>
         <ELEM name="ContextFactory">
            <ALIAS name="/jonathan/JContextFactory/instance"/>
         </ELEM>
      </CONFIGURATION></ELEM>

The instruction

JScheduler scheduler =
    (JScheduler) context.getValue("/jonathan/JScheduler/instance",'/'); 

applies to an element of type ASSEMBLAGE. Therefore its effect is to call the factory org.objectweb.jonathan.libs.resources.JSchedulerFactory using the JScheduler configuration described above. The code of JSchedulerFactory follows the pattern described in Section 4.1.2, and contains the following sequence:

 00061       if (_context != null) {
 00062          used_components[0] = 
 00063             _context.getValue("max_waiting",(char) 0);
 00064          used_components[1] = 
 00065             _context.getValue("verbose",(char) 0);
 00066          used_components[2] = 
 00067             _context.getValue("ContextFactory",(char) 0);
 00068       }
 00069       if (! (used_components[0] instanceof Integer)) {
 00070          used_components[0] = new Integer(5);
 00071       }
 00072       if (! (used_components[1] instanceof Boolean)) {
 00073          used_components[1] = new Boolean(false);
 00074       }
 00075       if (! (used_components[2] instanceof ContextFactory)) {
 00076          throw new InternalException("Component ContextFactory " + 
 00077                                      "of type ContextFactory " + 
 00078                                      "required by JSchedulerFactory.");
 00079       }

We note that the elements of name max_waiting and verbose are not present in the JScheduler configuration; therefore they are assigned a default value (5 and false, respectively). The (mandatory) element of name ContextFactory is present, as an ALIAS for /jonathan/JContextFactory/instance. This latter name designates the following configuration in the bootstrap file.

      <ELEM name="JContextFactory"><CONFIGURATION>
         <ELEM name="instance">
            <ASSEMBLAGE factory="/jonathan/JContextFactory/factory"
               configuration="/jonathan/JContextFactory"/>
         </ELEM>
         <ELEM name="factory">
            <ATOM class="org.objectweb.jonathan.libs.kernel.JContextFactoryFactory"/>
         </ELEM>
      </CONFIGURATION></ELEM>

As a result, the context factory used by JSchedulerFactory is created by JContextFactoryFactory using the JContextFactory context (actually the context is not used in the current implementation of JContextFactoryFactory.

This example shows how the different elements types present in a configuration file are processed.

Example 2: instantiating registry, server and client in Jeremie.  

*** to be provided ***




File translated from TEX by TTH, version 3.05.
On 5 Jun 2002, 16:48.