DPML
DPML Central
HomeUtilitiesStationMetroDepotTransit
Freight Train
Example Component...

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.

Component-driven Context...

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:

  1. no exception declarations
  2. return type may not be void
  3. return type may not be an array (this may be relaxed in the future)
  4. zero or one method parameter where the supplied parameter is the default
  5. 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:

  1. context dependencies shall be declared within an inner interface named Context
  2. context return types and parameters will be checked for accessibility within the runtime implementation classloader established by the Magic build system
  3. type assignability between default parameters and return types will be checked
Creating the component part ...

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 comes into play.