
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.
|
|
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.
|
|
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.
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).
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.
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.
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.
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:
An example of the use of this factory may be found in the static initializer
of LocateRegistrY:
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.
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).
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
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.
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.
[/]
[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]
...
4.1.2
Factories
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 }
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 }
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 }
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",'/');
4.1.3
Putting it all together
Creating a component instance
01 ctx = (Context) Kernel.newConfiguration(C.Class).getValue("a_name",'/');
02 A_Impl = (A) (new AFactory()).newObject(ctx);
|
|
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:
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.
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
The instruction
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:
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.
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 ***
// 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);
</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>
JScheduler scheduler =
(JScheduler) context.getValue("/jonathan/JScheduler/instance",'/');
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 }
<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>