Logical StructureLogical Structure
The logical structure of modules
Home > Books > Solutions Developer Guide > Modules > Logical Structure

Rate this page:
Really useful
Satisfactory
Not helpful
Confusing
Incorrect
Unsure
Extra comments:


Module Logical Structure

As described in the module overview a module has an address space export declaration and an internal private address space which contains all resources and code. The logical structure of a module is defined in the module definition file: module.xml which resides at the root of each module. It includes the major sections:

  • Metadata : Information about the module.
  • Export : The exported address space.
  • Rewrite : The mapping between the exported and private address spaces.
  • Mapping : The definition of the private internal address space.
as shown in the skeletal module.xml file:

<module>
  <!-- Metadata: Information about the module -->
  <identity />
  <info />
  <publisher />
  <license />
  <!-- Export: Defines the exported address space -->
  <export>
    <uri />
    <class />
  </export>
  <!-- Rewrite: Maps exported address space to private address space -->
  <rewrite>
    <rule />
  </rewrite>
  <!-- Mapping: Defines the internal private address space -->
  <mapping>
    <ura />
    <import />
    <rewrite />
    <this />
    <super />
  </mapping>
</module>

Module.xml Sections

Metadata

In module.xml there are several elements that contain metadata about the module. These elements include identity, info, publisher and license.
Path Description
/identity The identity section contains two child elements that, when taken together, uniquely identify an instance of the module. You can think of the combination of uri and version as the "primary key" for a module instance.
/identity/uri A unique uri identifying this module. By convention URNs are used. The name should be universally unique using a base name that is controlled by the author or owner of the module.
/identity/version A version number containing integers separated by "." marks. For example 1.0.12 is a valid version number. The version numbers may be extended to any degree of precision.
For example:

<identity>
  <uri>urn:com:myco:accounting</uri>
  <version>1.0.12</version>
</identity>

Identifies a module by the name com:myco:accounting at version number 1.0.12.

Path Description
/info The info section contains human readable information about the module that is used in various reports.
/info/name A human readable name for the module. There is no requirement for this to be unique, but it should be informative.
/info/description A human readable description of the module
/info/type Categorization of the module used with dynamic deployment. The value can be one of application, fulcrum, or library. An application is a module that may be deployed in a fulcrum. A fulcrum module hosts one or more transports. A library module is imported by other modules.
For example:

<info>
  <name>Accounting Program</name>
  <description>Our company's accounting program</description>
  <type>application</type>
</info>

Provides descriptive information about the module.

Path Description
/publisher/name optional publishers name
/publisher/uri optional URI reference to information about the publisher
For example:

<publisher>
  <name>My Company</name>
  <uri>http://www.mycom.com</uri>
</publisher>

Path Description
/license/name optional license name
/license/uri optional URI reference to license information
For example:

<license>
  <name>My Company Commercial License</name>
  <uri>http://www.mycom.com/license/</uri>
</license>

To see how NetKernel displays this module information, look at the Module Report for this installation of NetKernel.

Export

A module may provide services and resources to other modules by declaring an exported URI address space. A module may also export a portion of a its Java class package space.

The exported address space is specified in the export section using a set of URI regular expression patterns. Specifications in the export section are logically combined if they define overlapping address spaces. Inbound requests that match any export expression are captured by the module.

Regular expressions are powerful and their syntax can be confusing. You may want to read the Regular Expression Tutorial for additional information.

Here is an example export section:

<export>
  <uri>
    <match>ffcpl:/mymodule/.*</match>
    <match>active:myOnlyURA.*</match>
  </uri>
  <class>
    <match>org\.ten60\.netkernel\.mymodule\.proxy\..*</match>
  </class>
</export>

Here, the module is exporting two URI patterns. The first: ffcpl:/mymodule/.* matches everything below the /mymodule/ path for the ffcpl scheme. The second: active:myOnlyURA.*, exports the URI of an accessor contained in this module.

This module also exports a public Java class space that includes the package org.ten60.netkernel.mymodule.proxy and its sub-packages. All exported classes are available to the classloader of importing modules. The NetKernel module architecture extends the JAR model and provides private encapsulation of classes with select exporting. When combined with the NetKernel's module version management, Java developers are provided a powerful, safe, and fine-grained class management system.

Rewrite

The rewrite section decouples the module's exported address space and the private address space. Requests accepted into the exported address space are sent through the rewrite rules before being delivered to the private address space which is defined by the mapping rules

         export
            |
            v
         rewrite
            |
            v
         mapping

This decoupling provides a point of architectural flexibility valuable during development and life-long maintenance of an application. The section is aptly named "rewrite" because the destination address in a request can be completely rewritten into a new form.

In the rewrite section rule entries contain a match and a to element. The match element contains a regular expression pattern that is textually matched to the URI address contained in a request. If the regular expression pattern matches an address, then the to element defines how the address is rewritten.

In the following example rewrite section we find a single rule:

<rewrite>
  <rule>
    <match>ffcpl:/mymodule/(.*)</match>
    <to>ffcpl:/$1</to>
  </rule>
</rewrite>

This rule moves the exported address space to the top level of the private address space. An arbitrary number of rewrite rules may be specified and they are evaluated in order, progressing along a chain of rewrites.

The section on internal module patterns demonstrates a number of common ways rewrite rules can be used to map the exported address space to the internal private address space. In particular it shows how URI mapping is used to initiate the execution of runtime processes.

Mapping

The mapping section defines the module's private address space and the linking between specified addresses and accessors. The private address space is empty until entries are placed in the mapping section.

Requests are passed through the mapping rules one by one. The first rule that matches determines the resolution (except for the rewrite rule which changes the request and passes it along). This implies the order of rules is significant. The elements supported are: this, rewrite, import, ura, and super.

Here is the mapping section from the example module definition.

<mapping>
  <ura>
    <match>active:myOnlyURA.*</match>
    <class>org.ten60.netkernel.mymodule.accessor.MyOnlyURAAccessor</class>
  </ura>
  <import>
    <uri>urn:org:ten60:netkernel:ext:xml</uri>
    <version-min>1.0</version-min>
    <version-max>2.0</version-max>
  </import>
  <import>
    <uri>urn:org:ten60:netkernel:ext:layer1</uri>
    <version-min>1.0</version-min>
    <version-max>2.0</version-max>
  </import>
  <import>
    <uri>urn:org:ten60:netkernel:cache:default</uri>
    <version-min>1.0</version-min>
    <version-max>2.0</version-max>
  </import>
  <rewrite>
    <match>ffcpl:/mymodule/(.*)</match>
    <to>ffcpl:/org/ten60/netkernel/mymodule/$1</to>
  </rewrite>
  <this>
    <match>ffcpl:/org/ten60/netkernel/mymodule/.*</match>
  </this>
  <super />
</mapping>
This

this entries define portions of the internal private address space that map to the module's file system with the ffcpl: scheme. Multiple this specifications can be made in the mapping section or multiple match elements can be placed within one this element.

For example, the following two rules declare that a portion of the ffcpl: name space are mapped to the module's file system.

<this>
  <match>ffcpl:/src/.*</match>
  <match>ffcpl:/images/.*</match>
</this>

The example establishes a direct map from the virtual space to the physical space where the directories src and images are located just below the root of the module's directory.

ffcpl:/src/.*   --> /src/...
ffcpl:/images.* --> /images/...

this entries can also override the default resource expiry as specified in the Kernel Parameter Manager. For example:

<this>
  <expiry>
    <match>.*\.png</match>
    <offset>60000</offset>
  </expiry>
</this>
will force all .png resources loaded by this module to have a recheck period of 60 seconds. In general you do not need to worry about overriding the default resource expiry but it may be useful for deployment optimization.

Note: Java class resources do not need to be specified by this. A module's Java classloader can access all internal module resources.

Import

The import element integrates the public address space of the identified module into the private internal address space of the importing module. The import element may include a constraint on the range of module version numbers that are acceptable. If the a module with an acceptable version is not available, then the importing module will not be configured and a failure will be indicated on start-up. Module versioning allows multiple versions of modules and legacy modules to co-exist with newer or future versions without collision.

An import element contains a uri element which specifies the URI of the module you wish to import. Optionally it can use the version-min and version-max elements to constrain the valid versions for importation.

For example, the following import element brings in the address space of the layer1 NFK code as long as it is between version 1.0 and 2.0 inclusive:

<import>
  <uri>urn:org:ten60:netkernel:ext:layer1</uri>
  <version-min>1.0</version-min>
  <version-max>2.0</version-max>
</import>

It is important to note that if the address in a request matches the imported module's exported address space then that request enters the imported module for processing and it is no longer considered to be within the importing module.

URA

The ura element defines the link between a portion of the private address space and an accessor. The ura element consists of a match and an associated class element.

For example, the following ura element uses the regular expression active:myOnlyModule.* and links that portion of the private address space to the myOnlyModule accessor. The class specification is the fully qualified name of the Java class implementing the accessor.

<ura>
  <match>active:myOnlyURA.*</match>
  <class>org.ten60.netkernel.mymodule.accessor.MyOnlyURAAccessor</class>
</ura>

Note: it is important to match everything after the scheme and module name, using '.*', as additional active URI arguments may be passed to your accessor at the end of the address. For example, with the example above, the following request URI addresses will be mapped to the accessor:

active:myOnlyURA
active:myOnlyURA+operator@ffcpl:/myfile.txt

All parameters that are appended to the URI address are available to the accessor's code. For information about writing accessors, please refer to the Extension Developer's Guide.

Rewrite

The rewrite otherwise works identically to the rules documented above in the rewrite section of the module definition.

Best Practices

The evaluation order of the rules defining the private address space are significant. The following tips are offered for their organization:

  • Resources exposed in the exported address space should be placed at the top of the mapping section.
  • Imported modules should be next.
  • Next, include this entries that describe private local resources
  • Last include the super element - if it is appropriate for the module.

Address Resolution

One element in the mapping section not yet discussed is super.

super - part 1

To understand super consider what happens if we generate an internal request for a resource located in another module. We cannot expect a reusable library module to import all modules that might use it. In order to deal with this situation we can hand the request back up to requestor by specifying super. super should always be the last item in the mapping specification since a request hand-off to super will never return to the current module and so any mappings below super can never be reached.

super can be quite hard to comprehend abstractly but is easily understood with an example. However before we can do that we need to put together what we know about modules and see that connected modules constitute an application, we'll return to super below.

Application Composition

It is probably clear by this point that the ability of a module to import other modules allows modules to be composed into applications. An application is a hierarchical set of imported modules.

An application typically has a single root module, which is referred to as the 'Fulcrum'. In order to execute the application it must receive a request. Initiating requests are always generated from a transport, since a transport maps external requests to internal requests. Since applications are invoked from transport generated requests it generally makes sense to put the transport in the Fulcrum module - there are cases where this is not so but it's good rule of thumb to start with. The figure below shows a simple model of an application.

An initiating URI request is initially handled in the transport's home module. If it fails to match, the Module Manager will traverse the mapping of the current module entering it's import modules from above, matching on their publicly exported interface, applying their rewrite rules etc. If an imported module's public interface does not match, the original request is then tried on the next module and so on. A transport initiated request therefore leads the application to traverse the publicly exported interface of a set of modules until it finds a match.

It can be seen that a module should generally only export public interfaces that it can satisfy itself. Of course it is fine for a module to satisfy a resource request by using the resources provided from an imported module, or by requesting resources from above by handing back a request by calling super - we must now return to the super discussion.

super - part 2

Now that we have described the structure of an application we can resume the discussion of super. Below is a simple application configuration. Suppose that the transport issues a request for a DPML idoc to be executed. That idoc contains a single instruction to transform resource1.xml with transform1.xsl and return the result. Let's go through it a step at a time.

  • A request for an idoc is generated in the Fulcrum, active:dpml+operand@operation1.idoc. The Module Manager goes through the Fulcrum module's mappings. It discovers that active:dpml.* is matched by the imported DPML module. The request is issued to the DPML module, the active:dpml... URI initiates an instance of the DPML runtime accessor which begins to execute. Let's skip over that the DPML accessor must request operation1.idoc from somewhere and assume it has got all it needs to start executing the idoc.
  • The idoc contains a single XSLT instruction. The DPML accessor issues a request for the XSLT Accessor to perform the operation with active:xslt+operand@resource1.xml+operator@transform1.xsl. First the module manager looks for a match in the DPML module's mappings. It doesn't find one but it does find super as the last mapping entry. The super declaration causes the Module manager to return to the requestor module, which in this case was the Fulcrum.
  • The module manager attempts to find a match for the request in the Fulcrum's mappings. It finds that the Fulcrum imports the XSLT module and finds a match, active:xslt.* , for the request. Request is handed to the XSLT module, the XSLT accessor is initiated with the request and the transform is processed, again we leave aside how resource1.xml and transform1.xsl are obtained - though it would require a super call back up to the Fulcrum again. The XSLT transform result is returned to the DPML accessor since that was XSLT's requestor. The DPML accessor returns the result to the Fulcrum and thence back to the original transport requestor.

This walk through shows a typical example of the use of super. It allows a module at the same level or lower to make requests for accessors it has no prior-knowledge of. In this case DPML is not aware of any accessors, it's only job is issue requests and process the results of those requests, based upon the structure of the idoc it is asked to execute.

Two important points must be made.

  • super always hands a request back up the callstack. That is a module does not necessarily hand a request back to the module which imports it since it could be imported multiple times and may even be shared between two or more applications.
  • When traversing super the Module Manager will only ever look for a request match once for a given module. super can never result in a circular race condition as once a super hand-off results in a downwards traversal into an import, super will not be called again for that request as the downwards import has stated that it can handle the request.

© 2003-2007, 1060 Research Limited. 1060 registered trademark, NetKernel trademark of 1060 Research Limited.