Here is an example of a component.
package net.dpml.composition.testing; import java.util.logging.Logger; public class ExampleComponent implements MyService { private final logger m_logger; public ExampleComponent( Logger logger, Context context ) { m_logger = logger; m_context = context; } public void doMyStuff() { Dimension dimension = m_context.getDimension(); int w = dimension.getWidth(); m_logger.info( "Creating a widget with a width of " + w + "." ); } public interface Context { Dimension getDimension(); } }
The first notable divergence from classic Metro is the usage
of java.util.logging.Logger
. The rational here is
that with the decision to use JDK 1.4 as a base line, the value
of another logger class is questionable - in particular, the usage
of java.util.logging eliminates a framework dependency.
The second point of divergence is the usage of the Context inner
interface in preference to javadoc tag markup. In this case we have
the declaration by the component that it needs an instance of Dimension
.
From the Metro point of view this is equivalent to a non-optional
dependency of the type Dimension
assigned to the key
dimension
.
Another important point in FT is that there is no distinction from the
component implementation point-of-view between context and dependencies
thus eliminating the need for net.dpml.context.Context
and the
net.dpml.service.ServiceManager
framework interfaces. When
combined with the usage of java.util.logging.Logger
we are
starting to see the emergence of a sustainable framework independent
component strategy.
From a formal point-of-view Context operations are expressed using patterns. Allowable method patterns within the Context inner interface are listed below:
[type] get[key](); // required entry [type] get[key]( [default] ); // optional entry
Declaration of an optional context entry would look like the following:
public class ExampleComponent implements MyService { public interface Context { Dimension getDimension( Dimension value ); } ... public void doMyStuff() { Dimesion defaultValue = new DimensionValue( 10, 20 ); Dimension dimension = m_context.getDimension( defaultValue ); ... } }
One concern of the inner-interface approach is the potential level of verbosity this creates. This issue can be handled easily by referencing standard context interfaces. For example, the following one line context inner interface demonstrates the definition of the context via extension of a another context interface.
public interface DimensionalContext { int getHeight( int h ); int getWidth( int w ); } public class ExampleComponent implements MyService { ... public interface Context extends DimensionalContext { } }
Constraints applied to context entry method declarations include the following:
- no exception declarations
- return type may not be void
- return type may not be an array (this may be relaxed in the future)
- zero or one method parameter where the supplied parameter is the default
- a default parameter type must be assignable from the return type
An Ant task has been created that generates a serialized Type for each component. Unlike the classic javadoc tag based approach, the task uses information available in Magic to construct the runtime implementation classloader and uses this to load the component class. Using introspection the information about the component is extracted, validated, and stored in a .type serialized Type holder. This process enables substantial validation of the declaration within inner-interfaces and in particular, the integrity of method declarations, return types, and parameter declarations.
Build time constraints applied during the Context evaluation include:
- context dependencies shall be declared within an inner interface
named
Context
- context return types and parameters will be checked for accessibility within the runtime implementation classloader established by the Magic build system
- type assignability between default parameters and return types will be checked
A component part is somewhat similar to the notion of a block in classic Metro, however under FT a number of significant differences are present. Firstly, a part is stored as a serialized description (a .part file) thereby eliminating the time consuming XML definition loading, parsing and construction of deployment directives. Instead - we load a part and we have the complete directive ready to work with. Secondly - with the removal of the notion of container the definition of a part is a definition of the component deployment scenario.
The following fragment of a build file demonstrates the generation of the component type information and the subsequent usage of that information in the generation of the component part for the component we have described above.
<target name="build" depends="standard.build"> <types xmlns="plugin:dpml/composition/dpml-composition-builder"> <type class="net.dpml.composition.testing.ExampleComponent"/> </types> <component dest="example.part" xmlns="plugin:dpml/composition/dpml-composition-builder" type="net.dpml.composition.testing.ExampleComponent" name="demo"> <context> <value key="dimension" class="net.dpml.composition.testing.DimensionValue"> <param class="int" value="2"/> <param class="int" value="5"/> </value> </context> </component> </target>
The above example starts of with the creation of the <types>
.
The output of each <type/>
element is the serialized type
descriptor collocated with the class (i.e. basically the same as the .xinfo but
in a serialized form and names [classname].type).
Following type creation we move on with the creation of a <component>
part. The <component>
element dest
attribute tells the task where
to store the serialized descriptor (and if not supplied the output defaults to a classic
deliverables artifact filename). The type
attribute is the reference to the
component type we want to use. The <context>
element contains all of
the information describing the fulfillment of the components dependencies. In the above
examples this is limited to the supply of an instance of Dimension. The solution employed
above is to create a simple constructed object using the DimensionValue class by declaring the
classes constructor parameters as nested <param> elements.
It is important to note that the strategy concerning the creation of the Dimension
is totally customizable. You could for example create your own custom part builder and
declare this inside the <context>
element.
A second and equally important aspect of the above is that we have removed any notion
from the component as to how the dependency is resolved (compared to classic Metro where
dependencies were distinct from context entries). To demonstrate this point, we could
replace the above <value>
with an alternative solution using a
component strategy.
<component dest="example.part" xmlns="plugin:dpml/composition/dpml-composition-builder" type="net.dpml.composition.testing.ExampleComponent" name="demo"> <context> <component key="dimension" type="net.dpml.composition.testing.DimensionComponent"/> </context> </component>
In the above examples we have demonstrated alternative approaches to the population of a context. In both cases we have a developer in control (in that it is a developer that is writing the component XML). In effect this is demonstrating "developer" as the point of authority concerning the establishment of the context values. But what if we want our application to take control? This is where parts and parts management come into play.
In classic Metro we use the composition api as the framework for dynamic
management. In Metro FT this is replaced by a Parts
inner interface
and another set of method patterns supporting instance management, deployment and
release.
The following source code is from the class ExampleContainer. It is basically the same as ExampleComponent except that we have included the Dimension as a part. As a part the Dimension context is accessible to the containing component (ExampleContainer) and we can get in there are do things based on current state.
package net.dpml.composition.testing; import java.util.logging.Logger; public class ExampleContainer implements Example { private final Logger m_logger; private final Context m_context; private final Parts m_parts; public ExampleContainer( final Logger logger, final Context context, final Parts parts ) { m_logger = logger; m_context = context; m_parts = parts; } public void doMyStuff() { // // configure the dimension component's context map // Map map = m_parts.getDimensionContextMap(); int width = m_context.getWidth( 9 ); int height = m_context.getHeight( 7 ); map.put( "height", new Integer( height ) ); map.put( "width", new Integer( width ) ); // // get the dimension instance // Dimension dimension = m_parts.getDimension(); int size = dimension.getSize(); m_logger.info( "Creating a widget with a area of " + size ); } public interface Context extends DimensionalContext{} public interface Parts { Map getDimensionContextMap(); Dimension getDimension(); } }
The parts of component are distinct from context in that parts are exclusivly managed by the containing component. In the above example the Dimension component model is declared in XML and the component is reaching into the context model of the Dimension component and modifying height and width values based on it's own context information. After modification of the context the component acquires an instance of Dimension and proceeds with service execution.
The formal patterns associated with a Parts inner interface deal with default and identifiable instances.
Methods dealing with the default instance include:
[type] get[key](); // return the default service instance [manager] get[key]ContextManager(); // return the context manager for the default instance Map get[key]ContextMap(); // return the context map for the default instance Component get[key]Component() // return a model of a Component instance Model get[key]Model() // return the Model of a component type
Methods dealing with the identifiable instance include:
[type] get[key]( [id] ); // return an identified service instance [type] get[key]( [id], [policy] ); // same as above with control over proxy creation [manager] get[key]ContextManager( [id] ); // return the context manager for the identified instance Map get[key]ContextMap( [id] ); // return the context map for the identified instance Component get[key]Component( [id] ) // return a model of an identified Component instance Model get[key]Model( [id] ) // return the Model of an identified component type
In addition to the above the following optional release method signature is supported:
void release[key]( [instance] ) // release of a proxy or implementation instance
In the above examples we are interacting with the context model of the
Dimension component via the java.util.Map
interface. As suggested
by the [manager] get[key]ContextManager( [id] );
a type safe
management strategy is also provided as demonstrated in the following code
fragment:
public void doMyStuff() { DimensionalContext.Manager manager = m_parts.getDimensionContextManager(); manager.setHeight( height ); manager.setWidth( width ); Dimension dimension = m_parts.getDimension(); int size = dimension.getSize(); m_logger.info( "Creating a widget with a area of " + size ); } public interface Parts { DimensionalContext.Manager getDimensionContextManager(); Dimension getDimension(); }
Where DimensionContext and the associated inner Manager are defined as:
public interface DimensionalContext { int getHeight( int h ); int getWidth( int w ); interface Manager extends DimensionalContext { void setWidth( int width ); void setHeight( int height ); } }
In effect the Parts interface and its associated patterns provide a framework for management of internal parts with minimal API intrusion (significantly reducing the OS risk factor).
The following part descriptor demonstrates the creation of a part
containing another part within itself. Nested parts are declared within
the <parts>
element. Support for custom part handlers
is provided at the build level, allowing for example the inclusion of a
completely foreign part as a intrinsic part of the containing part. This
feature will enable 'radical' development of the metro platform while
retaining runtime compatibility with prior releases.
The part descriptor:
<component dest="target/test/acme-example-three.part" xmlns="plugin:dpml/composition/dpml-composition-builder" type="net.dpml.composition.testing.ExampleContainer" name="demo"> <parts> <component key="dimension" type="net.dpml.composition.testing.DimensionComponent"/> </parts> </component>
In addition to the classic declaration of parts - support has been included
for parts-by-reference. The following <component>
delcaration
has a <parts>
element that is using the <part>
element to include a foreign part by reference to a Transit artifact uri.
<x:property key="dpml-composition-testing-acme" feature="uri" type="part" name="acme.uri"/> <component dest="target/test/acme-example-four.part" xmlns="plugin:dpml/composition/dpml-composition-builder" type="net.dpml.composition.testing.ExampleContainer" name="demo"> <parts> <part key="dimension" uri="${acme.uri}"/> </parts> </component>
While the current development effort is looking promising there are number of issues still to addressed. These topics are covered in the following section.