AUTHOR : | |||
E. Bruneton | (France Telecom R&D) |
Version | 2.0 |
Released | November 12, 2003 |
This tutorial explains how to configure the Julia framework, and shows the results of several configuration options.
The Julia configuration files must define the "aliases" used in the newFcInstance method, such as "primitiveTemplate", "compositeTemplate", ... (see the Fractal Tutorial). For each such alias, one must define an Interface class generator, a set of control interface types, a set of controller classes implementing these control interfaces, a set of interceptors, and an optimization level (see next section).
A Julia configuration file uses a LISP-like syntax, and contains a set of alias definitions:
(alias-name1 alias-definition1)
(alias-name2 alias-definition2)
...
(alias-nameN alias-definitionN)
where each definition is a LISP-like term that can contain (backward or forward) references to other aliases (be careful to avoid recursive definitions!). A reference to an alias is a term of the form 'alias-name. If an alias name is defined several times, the last definition overrides the previous ones. It is therefore possible to define a "generic" and shared configuration file, that can be extended and partially or completely overriden by additional, user specific configuration files.
# controller descriptor ( # Interface class generator class descriptor |
# control interfaces ( (name signature) ... (name signature) ) |
# controller classes ( class descriptor ... class descriptor ) |
Although the controller classes can be normal Java classes, in the standard Julia configuration file most of the controller classes are generated classes, generated from mixin classes (see the Julia documentation). For example the class that implements the Component interface is defined as follows:
(component-impl (org.objectweb.fractal.julia.asm.MixinClassGenerator ComponentImpl org.objectweb.fractal.julia.BasicControllerMixin org.objectweb.fractal.julia.BasicComponentMixin # to check type related constraints, and for collection interfaces support: org.objectweb.fractal.julia.TypeComponentMixin ) )
This means that the class must be generated by mixing the BasicControllerMixin, the BasicComponentMixin and the TypeComponentMixin classes, in this order, which gives a class equivalent to an hypothetical TypeComponentMixin class subclassing a BasicComponentMixin class, itself subclassing the BasicControllerMixin class. The first name after MixinClassGenerator, ComponentImpl, is the name that is given to this mixed class; this name will appear in the exception stack traces.
( # interceptors [object descriptor] ) |
# optimizations object descriptor optimization level ) |
The optimization level should be equal to none, mergeControllers, mergeControllersAndInterceptors, mergeControllersAndContent or mergeControllersInterceptorsAndContent. In the first case the controller objects will be instantiated separately, as well as the interceptors and the content part of the component. In the second case a class merging the classes of the controller objects will be generated and instantiated, resulting in a single controller object, while the interceptors and the content part will still be instantiated separately. In the third case a merged class will be generated as before, and the interceptor class generator will be used to generate a sub class of this class that includes the controllers and interceptors code. This sub class will then be instantiated, resulting in a single object playing all the controller and interceptor roles at the same time. In the last two cases, a sub class of the content class including the controller - and, in the last case, the interceptor - classes will be generated and instantiated. In the last case the whole component is therefore instantiated as a single Java object.
execute: [java] Server: print method called [java] at ServerImpl.print(ServerImpl.java:31) [java] at org.objectweb.fractal.julia.generated.Cf359f1f4_0.print(INTERCEPTOR[Service]) [java] at org.objectweb.fractal.julia.generated.Ca49e1bb9_0.print(INTERFACE[Service]) [java] at ClientImpl.main(ClientImpl.java:31) [java] at org.objectweb.fractal.julia.generated.Cd9152578_0.main(INTERCEPTOR[Main]) [java] at org.objectweb.fractal.julia.generated.C84d15fbd_0.main(INTERFACE[Main]) [java] at HelloWorld.main(HelloWorld.java:206) [java] Server: begin printing... [java] -> hello world [java] Server: print done.
The exception stack trace is normal: this is because a printStrackTrace() instruction is included in the print method of the ServerImpl class. This stack trace indicates that there are two indirections between the "external client" HelloWorld and the client component ClientImpl, and two indirections between the client component and the server component ServerImpl. These indirections can be explained as follows.
With the default Julia configuration file used in this example, the "primitive" and "composite" aliases are defined by:
(primitive ( 'interface-class-generator ( 'component-itf 'binding-controller-itf 'super-controller-itf 'lifecycle-controller-itf 'name-controller-itf ) ( 'component-impl 'container-binding-controller-impl 'super-controller-impl 'lifecycle-controller-impl 'name-controller-impl ) ( (org.objectweb.fractal.julia.asm.InterceptorClassGenerator org.objectweb.fractal.julia.asm.LifeCycleCodeGenerator ) ) org.objectweb.fractal.julia.asm.MergeClassGenerator 'optimizationLevel ) ) (composite ( 'interface-class-generator ( 'component-itf 'binding-controller-itf 'content-controller-itf 'super-controller-itf 'lifecycle-controller-itf 'name-controller-itf ) ( 'component-impl 'composite-binding-controller-impl 'content-controller-impl 'super-controller-impl 'composite-lifecycle-controller-impl 'name-controller-impl ) ( ) org.objectweb.fractal.julia.asm.MergeClassGenerator 'optimizationLevel ) )
As can be seen, the composite component does not have any interceptor (its life cycle controller is an optimized life cycle controller that does not need interceptor objects, because it delegates the life cycle management operations to the life cycle controller of the sub components). On the other hand, the primitive components have an interceptor used to manage their life cycle. Given the main Julia data structures, and the shortcut algorithm, this configuration explains the previous indirections, as shown in the figure below:
Then, type ant execute in the example/helloworld directory. You should get exactly the same result as in the previous section. In other words, despite the fact that two intermediate composite components have been added, the number of indirections between the encapsulated components has not changed. This is because the composite components do not have interceptors, and therefore several component controllers can be bypassed by the shortcut algorithm. In fact, the actual links between the components are the following:
If the composite components have interceptors, then there are more indirections between components. In order to see this, uncomment the (composite 'composite-section2.2) line in the example/helloworld/julia-tutorial.cfg (the effect of this definition is to add an interceptor at the server and client interfaces of the composite components, in order to trace ingoing and outgoing method calls):
(composite-section2.2 ( 'interface-class-generator ( 'component-itf 'binding-controller-itf 'content-controller-itf 'super-controller-itf 'lifecycle-controller-itf 'name-controller-itf ) ( 'component-impl 'composite-binding-controller-impl 'content-controller-impl 'super-controller-impl 'composite-lifecycle-controller-impl 'name-controller-impl ) ( (org.objectweb.fractal.julia.asm.InterceptorClassGenerator (org.objectweb.fractal.julia.asm.TraceCodeGenerator inout) ) ) org.objectweb.fractal.julia.asm.MergeClassGenerator 'optimizationLevel ) )
This time the result of ant execute is the following:
execute: [java] Entering public abstract void Main.main(java.lang.String[]) [java] Entering public abstract void Main.main(java.lang.String[]) [java] Entering public abstract void Service.print(java.lang.String) [java] Entering public abstract void Service.print(java.lang.String) [java] Server: print method called [java] at ServerImpl.print(ServerImpl.java:31) [java] at org.objectweb.fractal.julia.generated.Cf359f1f4_0.print(INTERCEPTOR[Service]) [java] at org.objectweb.fractal.julia.generated.C6a61e603_0.print(INTERCEPTOR[Service]) [java] at org.objectweb.fractal.julia.generated.C54a577e2_0.print(INTERCEPTOR[Service]) [java] at org.objectweb.fractal.julia.generated.Ca49e1bb9_0.print(INTERFACE[Service]) [java] at ClientImpl.main(ClientImpl.java:31) [java] at org.objectweb.fractal.julia.generated.Cd9152578_0.main(INTERCEPTOR[Main]) [java] at org.objectweb.fractal.julia.generated.Cc2b70e07_0.main(INTERCEPTOR[Main]) [java] at org.objectweb.fractal.julia.generated.Cc2b70e07_0.main(INTERCEPTOR[Main]) [java] at org.objectweb.fractal.julia.generated.C84d15fbd_0.main(INTERFACE[Main]) [java] at HelloWorld.main(HelloWorld.java:206) [java] Server: begin printing... [java] -> hello world [java] Server: print done. [java] Leaving public abstract void Service.print(java.lang.String) [java] Leaving public abstract void Service.print(java.lang.String) [java] Leaving public abstract void Main.main(java.lang.String[]) [java] Leaving public abstract void Main.main(java.lang.String[])
The first four lines are printed, in this order, by the input interceptor of the root composite component, the input interceptor of the composite component around the client component, the output interceptor of the composite component around the client component, and finally the input interceptor of the composite component around the server component. The last four lines are also printed by these interceptors, in the reverse order. As shown by the stack trace, there are now four indirections bewteen the components. Indeed, the actual links between the components are the following:
If you change "inout" to "in" in the "composite-section2.2" definition, then no output interceptor is generated, which removes one trace and one indirection between the client and server component. If you change "inout" to "out", then no input interceptor is generated, which removes three traces and many indirections.
Another solution is to put the life cycle management interceptors in the composite component, instead of in the primitive ones. In this case the minimum number of indirections between two sibling primitive components is only one. But then there are more indirections between components that are not in the same composite, and the primitive components can no longer have their own life cycle controller interface, i.e., they can no longer be started and stopped individually (they can still be dynamically reconfigured but, for that, the whole enclosing component must be stopped first).
The two approaches can be mixed and used at the same time. For example, a composite component without interceptor can contain a primitive component with an interceptor, and a composite component with an interceptor (that itself contains primitive components without interceptors). The figures below summarizes all these possibilities.
Life cycle managed by interceptors in primitive components
Life cyle managed by interceptors in composite components
Mix of the two previous approaches
In order to use interceptors in the composite components but not in the primitive ones, uncomment the (primitive 'primitive-section2.3) and (composite 'composite-section2.3) lines in the julia-tutorial.cfg file (and comment the previously uncommented lines):
(primitive-section2.3 ( 'interface-class-generator ( 'component-itf 'binding-controller-itf 'super-controller-itf 'name-controller-itf ) ( 'component-impl 'container-binding-controller-impl 'super-controller-impl 'name-controller-impl ) ( ) org.objectweb.fractal.julia.asm.MergeClassGenerator 'optimizationLevel ) ) (composite-section2.3 ( 'interface-class-generator ( 'component-itf 'binding-controller-itf 'content-controller-itf 'super-controller-itf 'lifecycle-controller-itf 'name-controller-itf ) ( 'component-impl 'composite-binding-controller-impl 'content-controller-impl 'super-controller-impl 'lifecycle-controller-impl 'name-controller-impl ) ( (org.objectweb.fractal.julia.asm.InterceptorClassGenerator org.objectweb.fractal.julia.asm.LifeCycleCodeGenerator ) ) org.objectweb.fractal.julia.asm.MergeClassGenerator 'optimizationLevel ) )
Edit the execute.properties file in order to change the "run.parameters ..." line to "run.parameters". Then type ant execute. You should get the following result:
execute: [java] Server: print method called [java] at ServerImpl.print(ServerImpl.java:31) [java] at org.objectweb.fractal.julia.generated.Ca49e1bb9_0.print(INTERFACE[Service]) [java] at ClientImpl.main(ClientImpl.java:31) [java] at org.objectweb.fractal.julia.generated.C55d992cb_0.main(INTERCEPTOR[Main]) [java] at org.objectweb.fractal.julia.generated.C84d15fbd_0.main(INTERFACE[Main]) [java] at HelloWorld.main(HelloWorld.java:206) [java] Server: begin printing... [java] -> hello world [java] Server: print done.
As can be seen, there is now only one indirection between the client and server primitive components. Indeed, the actual links between the components are the following:
In order to see this, uncomment the (primitive 'primitive-section2.4) (composite 'composite-section2.4) lines in the julia-tutorial.cfg file (and comment the previously uncommented lines):
(primitive-section2.4 ( 'interface-class-generator ( 'component-itf 'binding-controller-itf 'super-controller-itf 'name-controller-itf ) ( 'component-impl 'static-container-binding-controller-impl 'super-controller-impl 'name-controller-impl ) ( ) org.objectweb.fractal.julia.asm.MergeClassGenerator 'optimizationLevel ) ) (composite-section2.4 'composite-section2.3 )
Then type ant execute. You should get the following result, showing that there is no indirection between the client and server component:
execute: [java] Server: print method called [java] at ServerImpl.print(ServerImpl.java:31) [java] at ClientImpl.main(ClientImpl.java:31) [java] at org.objectweb.fractal.julia.generated.C55d992cb_0.main(INTERCEPTOR[Main]) [java] at org.objectweb.fractal.julia.generated.C84d15fbd_0.main(INTERFACE[Main]) [java] at HelloWorld.main(HelloWorld.java:206) [java] Server: begin printing... [java] -> hello world [java] Server: print done.
Indeed, in this case, the actual links between the components are the following:
With the none option, you will get something like this for the server component:
[java] COMPONENT CREATED [java] INTERFACES: [java] attribute-controller org.objectweb.fractal.julia.generated.C8a201a40_0@1e00c26c [java] component org.objectweb.fractal.julia.generated.C5929b02d_0@bfd7b62c [java] s org.objectweb.fractal.julia.generated.Ca49e1bb9_0@23087a14 [java] lifecycle-controller org.objectweb.fractal.julia.generated.C18c4c884_0@6e4fa7e4 [java] binding-controller org.objectweb.fractal.julia.generated.Ce7005b70_0@6d7aaef0 [java] super-controller org.objectweb.fractal.julia.generated.C7d0bf956_0@442b008 [java] CONTROLLER OBJECTS: [java] org.objectweb.fractal.julia.generated.C3d06d42e_0@37697 [java] org.objectweb.fractal.julia.generated.C3aa6a740_0@5ecdec [java] org.objectweb.fractal.julia.generated.Cda074c35_0@21807c [java] org.objectweb.fractal.julia.generated.Ccac238dc_0@7a7e74 [java] HelloWorld$DebugController@3f74d [java] org.objectweb.fractal.julia.generated.C87ecd5ad_0@6102dc (Interceptor,impl=ServerImpl@2c01f) [java] CONTENT: [java] ServerImpl@2c01f
This shows that there are 6 objects for the server interfaces, 6 objects for the controller part (including one interceptor), and one for the content part. With the mergeControllers option, the five controller objects are merged into a single one:
[java] INTERFACES: [java] attribute-controller org.objectweb.fractal.julia.generated.C8a201a40_0@75ba517c [java] component org.objectweb.fractal.julia.generated.C5929b02d_0@530b523c [java] s org.objectweb.fractal.julia.generated.Ca49e1bb9_0@2a968104 [java] lifecycle-controller org.objectweb.fractal.julia.generated.C18c4c884_0@68774c94 [java] binding-controller org.objectweb.fractal.julia.generated.Ce7005b70_0@13f78a30 [java] super-controller org.objectweb.fractal.julia.generated.C7d0bf956_0@8e81dc68 [java] CONTROLLER OBJECTS: [java] org.objectweb.fractal.julia.generated.C254eb21f_0@21807c [java] org.objectweb.fractal.julia.generated.Ce26412b6_0@7a7e74 (Interceptor,impl=ServerImpl@3f74d) [java] CONTENT: [java] ServerImpl@3f74d
With the mergeControllersAndInterceptors option, the five controller objects and the interceptor object are merged into a single one:
[java] INTERFACES: [java] attribute-controller org.objectweb.fractal.julia.generated.C8a201a40_0@eb345b4b [java] component org.objectweb.fractal.julia.generated.C5929b02d_0@16bdb73b [java] s org.objectweb.fractal.julia.generated.Ca49e1bb9_0@18e45d5 [java] lifecycle-controller org.objectweb.fractal.julia.generated.C18c4c884_0@aa23e489 [java] binding-controller org.objectweb.fractal.julia.generated.Ce7005b70_0@49a4db7c [java] super-controller org.objectweb.fractal.julia.generated.C7d0bf956_0@1a755222 [java] CONTROLLER OBJECTS: [java] org.objectweb.fractal.julia.generated.C91bbe713_0@5ecdec [java] CONTENT: [java] ServerImpl@21807c
Finally, with the mergeControllersInterceptorsAndContent option, the content is also merged into the controller and interceptor objects:
[java] INTERFACES: [java] attribute-controller org.objectweb.fractal.julia.generated.C8a201a40_0@75ba517c [java] component org.objectweb.fractal.julia.generated.C5929b02d_0@530b523c [java] s org.objectweb.fractal.julia.generated.Ca49e1bb9_0@2a968104 [java] lifecycle-controller org.objectweb.fractal.julia.generated.C18c4c884_0@68774c94 [java] binding-controller org.objectweb.fractal.julia.generated.Ce7005b70_0@13f78a30 [java] super-controller org.objectweb.fractal.julia.generated.C7d0bf956_0@8e81dc68 [java] CONTROLLER OBJECTS: [java] org.objectweb.fractal.julia.generated.Ca3313c69_0@21807c [java] CONTENT: [java] org.objectweb.fractal.julia.generated.Ca3313c69_0@21807c
execute: [java] (org.objectweb.fractal.julia.asm.MixinClassGenerator ... [java] (org.objectweb.fractal.julia.asm.MixinClassGenerator ... [java] (org.objectweb.fractal.julia.asm.MixinClassGenerator ... [java] ((org.objectweb.fractal.julia.asm.InterfaceClassGenerator ... [java] ((org.objectweb.fractal.julia.asm.InterfaceClassGenerator ... [java] ((org.objectweb.fractal.julia.asm.InterfaceClassGenerator ... [java] ((org.objectweb.fractal.julia.asm.InterfaceClassGenerator ... ... [java] Server: print method called [java] at ServerImpl.print(ServerImpl.java:31) [java] at org.objectweb.fractal.julia.generated.Cf359f1f4_0.print(INTERCEPTOR[Service]) [java] at org.objectweb.fractal.julia.generated.Ca49e1bb9_0.print(INTERFACE[Service]) [java] at ClientImpl.main(ClientImpl.java:31) [java] at org.objectweb.fractal.julia.generated.Cd9152578_0.main(INTERCEPTOR[Main]) [java] at org.objectweb.fractal.julia.generated.C84d15fbd_0.main(INTERFACE[Main]) [java] at HelloWorld.main(HelloWorld.java:206) [java] Server: begin printing... [java] -> hello world [java] Server: print done.
The first lines describe the classes that are generated on the fly: each line is in fact the class descriptor (see section 1.2) of a class that has just been dynamically generated.
With the "-Djulia.loader.gen.log=err" option the previous trace is sent to the standard error stream. It is also possible to use "out" instead of "err", to sent the trace to the standard output stream. With any other name the trace will be stored an a file of that name. It is also possible to directly store the dynamically generated classes on disk, by using the "-Djulia.loader.gen.dir=directory" option.
The dynamic class generation process heavily relies on the Java Reflection API, which is not available on all Java environments. In these cases Julia can still be used but, in order to do this, all the "dynamically generated" classes must be statically generated and stored on disk (in a directory that is accessible from the class path) before launching an application.
In order to illustrate all the above options, replace "DynamicLoader" with "BasicLoader" in the execute.properties file. The effect of this change is that Julia will use a basic class loader, that loads all classes from the classpath, instead of a class loader that can generate classes on the fly. If you then type ant execute, you should get an exception: this is because the "dynamically generated" classes can neither be generated nor found in the class path.
In order to solve the problem, we must statically generate the previous classes. To do this, first change back "BasicLoader" to "DynamicLoader". Then add the "-Djulia.loader.gen.dir=build" argument to the "run.jvm.parameters" system property, and type ant execute to launch the application. You should get the same trace as above and, in addition, a new org/objectweb/... directory containing the generated classes should have been created in the build directory. If you then type ant execute again, you will see that no class is dynamically generated: instead, the classes previously generated are loaded directly from the class path.
You can now switch to the "BasicLoader": uncomment the (bootstrap 'bootstrap-section2.7) line in the julia-tutorial.cfg file, type ant execute (the effect is to generate and store on disk a new class for the new bootstrap component) and, finally, change "DynamicLoader" with "BasicLoader" in the execute.properties file. If you type ant execute again, this time the application is executed correctly (you can even delete or move to another directory the julia-asm.jar and the julia-mixins.jar files, in the dist/lib directory, the example will still execute normally: this shows that only the julia-runtime.jar is really needed at runtime, when the generated classes are statically generated).
In order to see what kind of classes are dynamically generated, you may reexecute the examples of the previous sections with the "-Djulia.loader.gen.dir=/tmp" option, and by decompiling the generated classes with a decompiler such as Jad. You may also reexecute these examples with the none optimisation level changed to mergeXXX (see section 1.2), in order to see how controller, interceptors and user component classes are merged.