The Apache Jakarta Project Jakarta HiveMind Project
 
   

Jakarta HiveMind Project Documentation

HiveMind プロジェクト

リファレンス

HiveMind Services

In HiveMind, a service is simply an object that implements a particular interface, the service interface. You supply the service interface (packaged as part of a module). You supply the core implementation of the interface (in the same module, or in a different module). At runtime, HiveMind puts it all together.

HiveMind uses four service models: primitive, singleton, threaded and pooled. In the primitive and singleton models, each service will ultimately be just a single object instance. In the threaded and pooled models, there may be many instances simultaneously, one for each thread.

Unlike EJBs, there's no concept of location transparency: services are always local to the same JVM. Unlike XML-based web services, there's no concept of language transparency: services are always expressed in terms of Java interfaces. Unlike JMX or Jini, there's no concept of hot-loading of of services. HiveMind is kept delibrately simple, yet still very powerful, so that your code is kept simple.

Defining Services

A service definition begins with a Java interface, the service interface. Any interface will do, HiveMind doesn't care, and there's no base HiveMind interface.

A module descriptor may include <service-point> elements to define services. A module may contain any number of services.

Each <service-point> establishes an id for the service and defines the interface for the service. An example is provided later in this document.

HiveMind is responsible for supplying the service implementation as needed; in most cases, the service implementation is an additional Java class which implements the service interface. HiveMind will instantiate the class and configure it as needed. The exact timing is determined from the service's service model:

  • primitive : the service is constructed on first reference
  • singleton : the service is not constructed until a method of the service interface is invoked
  • threaded : invoking a service method constructs and binds an instance of the service to the current thread
  • pooled : as with threaded, but service implementations are stored in a pool when unbound from a thread for future use in other threads.

Additional service models can be defined via the hivemind.ServiceModels configuration point.

HiveMind uses a system of proxies for most of the service models (all except the primitive service model, which primarily exists to bootstrap the core HiveMind services used by other services). Proxies are objects that implement the service interface and take care of details such as constructing the actual implementation of a service on the fly. These lifecycle issues are kept hidden from your code behind the proxies.

A service definition may include service contributions, or may leave that for another module.

Ultimately, a service will consist of a core implementation (a Java object that implements the service interface) and, optionally, any number of interceptors. Interceptors sit between the core implementation and the client, and add functionality to the core implementation such as logging, security, transaction demarkation or performance monitoring. Interceptors are yet more objects that implement the service interface.

Instantiating the core service implementation, configuring it, and wrapping it with any interceptors is referred to as constructing the service. Typically, a service proxy will be created first. The first time that a service method is invoked on the proxy, the service implementation is instantiated and configured, and any interceptors for the service are created.

Extending Services

Any module may contribute to any service extension point. An <implementation> element contains these contributions. Contributions take three forms:

  • Service constructors:
  • <interceptor> to add additional logic to a core implementation
Service Constructors

A service constructor is used to instantiate a Java class as the core implementation instance for the service.

There are two forms of service constructors: instance creators and implementation factories.

An instance creator is represented by a <create-instance> element. It includes a class attribute, the Java class to instantiate.

An implementation factory is represented by a <invoke-factory> element. It includes a service-id attribute, the id of a service implementation factory service (which implements the ServiceImplementationFactory interface). The most common example is the hivemind.BuilderFactory service.

Implementation Factories

An implementation factory is used to create a core implementation for a service at runtime.

Often, the factory will need some additional configuration information. For example, the hivemind.lib.EJBProxyFactory service uses its parameters to identify the JNDI name of the EJB's home interface, as well as the home interface class itself.

Parameters to factory services are the XML elements enclosed by the <invoke-factory> element. Much like a configuration contribution, these parameters are converted from XML into Java objects before being provided to the factory.

The most common service factory is hivemind.BuilderFactory. It is used to construct a service and then set properties of the service implementation object.

Interceptor Contributions

An interceptor contribution is represented by an <interceptor> element. The service-id attribute identifies a service interceptor factory service: a service that implements the ServiceInterceptorFactory interface.

An interceptor factory knows how to create an object that implements an arbitrary interface (the interface being defined by the service extension point), adding new functionality. For example, the hivemind.LoggingInterceptor factory creates an instance that logs entry and exit to each method.

The factory shouldn't care what the service interface itself is ... it should adapt to whatever interface is defined by the service extension point it will create an instance for.

A service extension point may have any number of interceptor contributions. If the order in which interceptors are applied is important, then the optional before and after attributes can be specified.

A Stack of Interceptors

In this example, is was desired that any method logging occur first, before the other interceptors. This ensures that the time taken to log method entry and exit is not included in the performance statistics (gathered by the performance interceptor). To ensure that the logging interceptor is the first, or earliest, interceptor, the special value * (rather than a list of interceptor service ids) is given for its before attribute (within the <interceptor> element). This forces the logging interceptor to the front of the list (however, only a single interceptor may be so designated).

Likewise, the security checks should occur last, after logging and after performance; this is accomplished by setting the after attribute to *. The performance interceptor naturally falls between the two.

This is about as complex as an interceptor stack is likely to grow. However, through the use of explicit dependencies, almost any arraingment of interceptors is possible ... even when different modules contribute the interceptors.

Interceptors implement the toString() method to provide a useful identification for the interceptor, for example:
<Iterceptor: hivemind.LoggingInterceptor for com.myco.MyService(com.myco.MyServiceInterface)>

This string identifies the interceptor service factory (hivemind.LoggingInterceptor), the service extension point (com.myco.MyService) and the service interface (com.myco.MyServiceInterface).

Warning
If toString() is part of the service interface (really, a very rare case), then the interceptor does not override the service implementation's method. However, this is not a recommended practice.
A short example

As an example, let's create an interface with a single method, used to add together two numbers.

package com.myco.mypackage;

public interface Adder
{
  public int add(int arg1, int arg2);
}

We could define many methods, and the methods could throw exceptions. Once more, HiveMind doesn't care.

We need to create a module to contain this service. We'll create a simple HiveMind deployment descriptor. This is an XML file, named hivemodule.xml, that must be included in the module's META-INF directory.

<?xml version="1.0"?>      
<module id="com.myco.mypackage" version="1.0.0">
  <service-point id="Adder" interface="com.myco.mypackage.Adder"/>
</module>

The complete id for this service is com.myco.mypackage.Adder , formed from the module id and the service id. Commonly, the service id will exactly match the complete name of the service interface, but this is not required.

Normally, the <service-point> would contain a <create-instance> or <invoke-factory> element, used to create the core implementation. For this example, we'll create a second module that provides the implementation. First we'll define the implementation class.

package com.myco.mypackage.impl;

import com.myco.mypackage.Adder;

public class AdderImpl implements Adder
{
  public int add(int arg1, int arg2)
  {
    return arg1 + arg2;
  }
}

That's what we meant by a POJO. We'll create a second module to provide this implementation.

<?xml version="1.0"?>
<module id="com.myco.mypackage.impl" version="1.0.0">
  <implementation service-id="com.myco.mypackage.Adder">
    <create-instance class="com.myco.mypackage.impl.AdderImpl"/>
  </implementation>
</module>

The runtime code to access the service is very streamlined:

Registry registry = . . .
Adder service = (Adder) registry.getService("com.myco.mypackage.Adder", Adder.class);  
int sum = service.add(4, 7);

Another module may provide an interceptor:

<?xml version="1.0"?>
<module id="com.myco.anotherpackage version="1.0.0">
  <implementation service-id="com.myco.mypackage.Adder">
    <interceptor service-id="hivemind.LoggingInterceptor">
  </implementation>
</module>

Here the Logging interceptor is applied to the service extension point. The interceptor will be inserted between the client code and the core implementation. The client in the code example won't get an instance of the AdderImpl class, it will get an instance of the interceptor, which internally invokes methods on the AdderImpl instance. Because we code against interfaces instead of implementations, the client code neither knows nor cares about this.

Primitive Service Model

The simplest service model is the primitive service model; in this model the service is constructed on first reference. This is appropriate for services such as service factories and interceptor factories, and for several of the basic services provided in the hivemind module.

Singleton Service Model

Constructing a service can be somewhat expensive; it involves instantiating a core service implementation, configuring its properties (some of which may also be services), and building the stack of interceptors for the service. Although HiveMind encourages you to define your application in terms of a large number of small, simple, testable services, it is also desirable to avoid a cascade of unneccesary object creation due to the dependencies between services.

To resolve this, HiveMind defers the actual creation of services by default. This is controled by the model attribute of the <service-point> element; the default model is singleton.

When a service is first requested a proxy for the service is created. This proxy implements the same service interface as the actual service and, the first time a method of the service interface is invoked, will force the construction of the actual service (with the core service implementation, interceptors, references to other services, and so forth).

In certain cases (including many of the fundamental services provided by HiveMind) this behavior is not desired; in those cases, the primitive service model is specified. In addition, there is rarely a need to defer service implementation or service interceptor factory services.

Threaded Service Model

In general, singleton services (using the singleton or primitive service models) should be sufficient. In some cases, the service may need to keep some specific state. State and multithreading don't mix, so the threaded service model constructs, as needed, a service instance for the current thread. Once constructed, the service instance stays bound to the thread until it is discarded. The particular service implementation is exclusive to the thread and is only accessible from that thread.

The threaded service model uses a special proxy class (fabricated at runtime) to support this behavior; the proxy may be shared between threads but methods invoked on the proxy are redirected to the private service implementation bound to the thread. Binding of a service implementation to a thread occurs automatically, the first time a service method is invoked.

The service instance is discarded when notified to cleanup; this is controlled by the hivemind.ThreadEventNotifier service. If your application has any threaded services, you are responsible for invoking the fireThreadCleanup() method of the service.

A core implementation may implement the Discardable interface. If so, it will receive a notification as the service instance is discarded.

HiveMind includes a servlet filter to take care creating the Registry and managing the ThreadEventNotifier service.

Pooled Service Model

The pooled service model is very similar to the threaded model, in that a service implementation will be exclusively bound to a particular thread (until the thread is cleaned up). Unlike the threaded model, the service is not discarded; instead it is stored into a pool for later reuse with the same or a different thread.

As with the threaded model, all of this binding and unbinding is hidden behind a dynamically fabricated proxy class.

Core service implementations may implement the RegistryShutdownListener interface to receive a callback for final cleanups (as with the singleton and deferred service models).

In addition, a service may implement the PoolManageable interface to receive callbacks specific to the pooled service. The service is notified when it is activated (bound to a thread) and deactivated (unbound from the thread and returned to the pool).

Service Lifecycle

As discussed, the service model determines when a service is instantiated. In many cases, the service needs to know when it has been created (to perform any final initializations) or when the Registry has been shut down.

A core service implementation may also implement the RegistryShutdownListener interface. When a Registry is shutdown, the registryDidShutdown() method is invoked on all services (and many other objects, such as proxies). The order in which these notifications occur is not defined. A service may release any resources it may hold at this time. It should not invoke methods on other service interfaces.

The threaded service model does not register services for Registry shutdown notification; regardless of whether the core service implementation implements the RegistryShutdownListener interface or not. Instead, the core service implementation should implement the Discardable interface, to be informed when a service bound to a thread is discarded.

It is preferred that, whenever possible, services use the singleton service model (the default) and not the primitive model. All the service models (except for the primitive service model) expose a proxy object (implementing the service interface) to client code (included other services). These proxies are aware of when the Registry is shutdown and will throw an exception when a service method is invoked on them.

Services and Events

It is fairly common that some services will produce events and other services will consume events. The use of the hivemind.BuilderFactory to construct a service simplifies this, using the < event-listener> element. The BuilderFactory can register a core service implementation (not the service itself!) as a listener of events produced by some other service.

The producing service must include a matched pair of listener registration methods, i.e., both addFooListener() and removeFooListener. Note that only the implementation class must implement the listener interface; the service interface does not have to extend the listener interface. The core service implementation is registered directly with the producer service, bypassing any interceptors or proxies.

Frequently Asked Questions
  • Why do I pass the interface class to getService()?

    This is to add an additional level of error checking and reporting. HiveMind knows, from the module descriptors, the interface provided by the service extension point, but it can't tell if you know that. By passing in the interface you'll cast the returned service to, HiveMind can verify that you won't get a ClassCastException. Instead, it throws an exception with more details (the service extension point id, the actual interface provided, and the interface you passed it).

  • What if no module provides a core implementation of the service?

    HiveMind checks for a service constructor when the registry itself is assembled. If a service extension point has no service constructor, an error is logged (identifying the extension point id). In addition, getService() will throw an ApplicationRuntimeException.

  • What if I need to do some initializations in my service?

    If you have additional initializations that can't occur inside your core service implementations constructor (for instance, if the initializations are based on properties set after the service implementation object is instantiated), then your class should use the hivemind.BuilderFactory to invoke an initializer method.

  • What if I don't invoke Registry.cleanupThread()?

    Then service implementations bound to the current thread stay bound. When the thread is next used to process a request, the same services, in whatever state they were left in, will be used. This may not be desirable in a servlet or Tapestry application, as some state from a client may be left inside the services, and a different client may be associated with the thread in later executions.

  • What if I want my service to be created early, not just when needed?

    Contribute your service into the hivemind.EagerLoad configuration; this will force HiveMind to instantiate the service on startup. This is often used when developing an application, so that configuration errors are caught early; it may also be useful when a service should be instantiated to listen for events from some other service.

Configuration Points

A central concept in HiveMind is configuration extension points. Once you have a set of services, its natural to want to configure those services. In HiveMind, a configuration point contains an unordered list of elements. Each element is contributed by a module ... any module may make contributions to any configuration point.

There is no explicit connection between a service and a configuration point, though it is often the case that a service and a configuration point will be similarily named (or even identically named; services and configuration points are in seperate namespaces). Any relationship between a service and an configuration point is explicit only in code ... the service may be configured with the elements of a configuration point and operate on those elements in some way.

Defining a Configuration Point

A module may include <configuration-point> elements to define new configuration points. A configuration point may specify the expected, or allowed, number of contributions:

  • Zero or one
  • Zero or more (the default)
  • At least one
  • Exactly one

At runtime, the number of actual contributions is checked against the constraint and an error is reported if the number doesn't match.

Defining the Contribution Format

A significant portion of an configuration point is the <schema> element ... this is used to define the format of contributions that may be made inside <contribution> elements. Contributions take the form of XML elements and attributes, the <schema> element identifies which elements and which attributes and provides rules that transform the contributions into Java objects.

This is very important: what gets fed into an configuration point (in the form of contributed <contribution>s) is XML. What comes out on the other side is a list of configured Java objects. Without these XML transformation rules, it would be necessary to write Java code to walk the tree of XML elements and attributes to create the Java objects; instead this is done inside the module deployment descriptor, by specifying a <schema> for the configuration point, and providing rules for processing each contributed element.

If a contribution from an <contribution> is invalid, then a runtime error is logged and the contribution is ignored. The runtime error will identify the exact location (the file, line number and column number) of the contribution so you can go fix it.

The <schema> element contains <element> elements to describe the XML elements that may be contributed. <element>s contain <attribute>s to define the attributes allowed for those elements. <element>s also contain <rules> used to convert the contributed XML into Java objects.

Here's an example from the HiveMind test suite. The Datum object has two properties: key and value.

<configuration-point id="Simple">
  <schema>
    <element name="datum">
      <attribute name="key" required="true"/>
      <attriute name="value" required="true"/>
      
      <conversion class="hivemind.test.config.impl.Datum"/>
    </element>
  </schema>
</configuration-point>

<contribution configuration-id="Simple">
  <datum key="key1" value="value1"/>
  <datum key="key2" value="value2"/>
</contribution>

The <conversion> element creates an instance of the class, and initializes its properties from the attributes of the contributed element (the datum and its key and value attributes). For more complex data, the <map> and <rules> elements add power (and complexity).

This extra work in the module descriptor eliminates a large amount of custom Java code that would otherwise be necessary to walk the XML contributions tree and convert elements and attributes into objects and properties. Yes, you could do this in your own code ... but would you really include all the error checking that HiveMind does? Or the line-precise error reporting? Would you bother to create unit tests for all the failure conditions?

Using HiveMind allows you to write the schema and rules and know that the conversion from XML to Java objects is done uniformly, efficiently and robustly.

The end result of this mechanism is very concise, readable contributions (as shown in the <contribution> in the example).

In addition, it is common for multiple configuration points to share the exact same schema. By assigning an id attribute to a <schema> element, you may reference the same schema for multiple configuration points. For example, the hivemind.FactoryDefaults and hivemind.ApplicationDefaults configuration points use the same schema. The hivemind module deployment descriptor accomplishes this by defining a schema for one configuration point, then referencing it from another:

<schema id="Defaults">
  <element name="default">
  
  . . .
  
  </element>
</schema>

<configuration-point id="FactoryDefaults" schema-id="Defaults"/>

Like service points and configuration points, schemas may be referenced within a single module using an unqualified id, or referenced between modules using a fully qualified id (that is, prefixed with the module's id).

Accessing Configuration Points

Like services, configuration points are meant to be easy to access (the only trick is getting a reference to the registry to start from).

Registry registry = . . .;
List elements = registry.getConfiguration("com.myco.MyConfig");	

int count = elements.size();
for (int i = 0; i < count; i++)
{
  MyElement element = (MyElement) elements.get(i);
  
  . . .
}

Note
Although it is possible to access configurations via the Registry, it is often not a good idea. It is unlikely that you want the information contained in a configuration as an unordered list. A best practice is to always access the configuration through a service, which can organize and validate the data in the configuration.

The list of elements is always returned as an unmodifiable list. An empty list may be returned.

The order of the elements in the list is not defined. If order is important, you should create a new (modifiable) list from the returned list and sort it.

Note that the elements in the list are no longer the XML elements and attributes that were contributed, the rules provided in the configuration point's <schema> are used to convert the contributed XML into Java objects.

Lazy Loading

At application startup, all the module deployment descriptors are located and parsed and in-memory objects created. Validations (such as having the correct number of contributions) occur at this stage.

The list of elements for an configuration point is not created until the first call to Registry.getConfiguration() for that configuration point.

In fact, it is not created even then. When the element list for an configuration point is first accessed, what's returned is not really the list of elements; it's a proxy, a stand-in for the real data. The actual elements are not converted until they are actually needed, in much the same way that the creation of services is deferred.

In general, you will never know (or need to know) this; when you access the size() of the list or get() any of its elements, the conversion of contributions into Java objects will be triggered, and those Java objects will be returned in the list.

If there are minor errors in the contribution, then you may see errors logged; if the <contribution> contributions are singificantly malformed, HiveMind may be unable to recover and will throw a runtime exception.

Substitution Symbols

The information provided by HiveMind module descriptors is entirely static, but in some cases, some aspects of the configuration should be dynamic. For example, a database URL or an e-mail address may not be known until runtime (a sophisticated application may have an installer which collects this information).

HiveMind supports this notion through substitution symbols. These are references to values that are supplied at runtime. Substitution symbols can appear inside literal values ... both as XML attributes, and as character data inside XML elements.

Example:

<contribution configuration-id="com.myco.MyConfig">
  <value> dir/foo.txt </value>
  <value> ${config.dir}/${config.file} </value>
</contribution>

This example contributes two elements to the com.myco.MyConfig configuration point. The first contribution is simply the text dir/foo.txt. In the second contribution, the content contains substitution symbols (which use a syntax derived from the Ant build tool). Symbol substitution occurs before <schema> rules are executed, so the config.dir and config.file symbols will be converted to strings first, then whatever rules are in place to convert the value element into a Java object will be executed.

Note
If you contribute text that includes symbols that you do not want to be expanded then you must add an extra dollar sign to the false symbol. This is to support legacy data that was already using the HiveMind symbol notation for its own, internal purposes. For example, foo $${bar} baz will be expanded into the text foo ${bar} baz.
Symbol Sources

This begs the question: where do symbol values come from? The answser is application dependent. HiveMind itself defines a configuration configuration point for this purpose: hivemind.SymbolSources. Contributions to this configuration point define new objects that can provide values for symbols, and identify the order in which these objects should be consulted.

If at runtime none of the configured SymbolSources provides a value for a given symbol then HiveMind will leave the reference to that symbol as is, including the surrounding ${ and }. Additionally an error will be logged.

Frequently Asked Questions
  • Are the any default implementations of SymbolSource?

    There is now an configuration point for setting factory defaults: hivemind.FactoryDefaults . A second configuration point, for application defaults, overrides the factory defaults: hivemind.ApplicationDefaults.

    SystemPropertiesSymbolSource is a one-line implementation that allows access to system properties as substitution symbols. Note that this configuration is not loaded by default.

    Additional implementations may follow in the future.

  • What's all this about schemas and rules?

    A central goal of HiveMind is to reduce code clutter. If configuration point contributions are just strings (in a .properties file) or just XML, that puts a lot of burden on the developer whose code reads the configuration to then massage it into useful objects. That kind of ad-hoc code is notoriously buggy; in HiveMind it is almost entirely absent. Instead, all the XML parsing occurs inside HiveMind, which uses the schema and rules to validate and convert the XML contributions into Java objects.

    You can omit the schema, in which case the elements are left as XML (instances of Element) and your code is responsible for walking the elements and attributes ... but why bother? Far easier to let HiveMind do the conversions and validations.

  • How do I know if the element list is a proxy or not?

    Basically, you can't, short of performing an instanceof check. There isn't any need to tell the difference between the deferred proxy to the element list and the actual element list; they are both immutable and both behave identically.

HiveDoc

HiveMind には、HiveMind レジストリ(ランタイム時にデプロイされた全てのモジュールからの情報がセットになったもの)を文書化する為のツールが含まれています。ビルド時に、全ての関連したHiveMindモジュールのデプロイ記述子が解析され、その結果が結合されて一つのファイルに纏められます。次に、マスターファイル(このドキュメントにのみ使われるもの)がXSLTを通じてHTMLファイルのセットへと変換されます。最終成果物はJavaDocにとてもよく似ており、全てハイパーリンクがつけられ、全てのサービス・設定ポイント・コントリビューション・そしてスキーマが明白に分かるようになるでしょう。

生成されるドキュメントに結合されるのは、ユーザが指定した説明です。 <attribute>, <configuration-point>, <element>, <module>, <schema> 及び <service-point> 要素には、文字列データの説明を入れる事が出来ます。例えば:

<module id="mymodule" version="1.0.0">
  A module for my application with my services, etc.
</module>

HiveMind フレームワーク及びライブラリ用の HiveDoc はこちらで手に入ります。

Warning
ドキュメントのビルドに関する詳細はまた改めて。
HiveMind Module Descriptor

The purpose of the module descriptor is to provide a runtime and compile-time description of each HiveMind module in terms of service and configuration extension points and contributions to those extension points.

The descriptor is named hivemodule.xml and is stored in the META-INF directory of the module.

The root element of the descriptor is the <module> element.

attribute

<attribute> is used to define an attribute within an <element>. Inside a <contribution>, only known attributes are allowed in elements; unknown attributes will be logged as an error and ignored. In addition, some attributes are required; again, errors occur if the contributed element does not provide a value for the attribute.

Attribute Type Required ? Description
name string yes The name of the attribute.
required boolean no If true, the attribute must be provided in the contributed configuration element. The default is false.
unique boolean no If true, the attribute must contain a unique value with respect to all other contributions to the same configuration point. The default is false.
translator string no The translator configuration that is used to convert the attribute into a useable type. By default, the attribute is treated as a single string.
configuration-point

The <configuration-point> element defines a configuration extension point.

Attribute Type Required ? Description
id string yes The simple id of the service extension point. The fully qualified id for the extension point is created by prefixing with the module's id (and a dot).
occurs unbounded | 0..1 | 1 | 1..n | none no The number of contributions allowed:
  • unbounded (default): any number
  • 0..1: optional
  • 1 : required
  • 1..n: at least one
  • none: none allowed
Note
none doesn't make sense for occurances to a configuration point, but does occasionally make sense for parameters to a factory.
schema-id string no Used to reference a <schema> (in the same module, or a different one) that defines the format of contributions into the configuration point. This may be omitted, in which case the extension point will contain a list of Element .

Contains: <schema>

<configuration-point> only defines a configuration point, it does not supply any data into that point. The <contribution> element is used to provide such data.

contribution

The <contribution> element contributes elements to an existing configuration extension point.

Attribute Type Required ? Description
configuration-id string yes Either the id of an <configuration-point> within the module, or the fully qualified id of an <configuration-point> in another module.

The content of the <contribution> consists of elements. These elements are converted, in accordance with the configuration point's <schema>, into Java objects.

conversion

<conversion> is an alternative to <rules> that is generally simpler and more concise. An <element> should contain <conversion> or <rules> but not both.

<conversion> is geared towards the typical case; a straight-forward mapping of the element to an instance of a Java class, and a mapping of the element's attributes to the properties of the Java object.

Attribute Type Required ? Description
class string yes The fully qualified name of a Java class to instantiate.
parent-method string no The name of a method of the parent object used to add the created object to the parent. The default, addElement, is appropriate for top-level <element>s.

Contains: <map>

Each attribute will be mapped to a property. A limited amount of name mangling occurs: if the attribute name contains dashes, they are removed, and the character following is converted to upper case. So, an attribute named "complex-attribute-name" would be mapped to a property named "complexAttributeName". Only attributes identified with a <attribute> element will be mapped, others will be ignored.

create-instance

<create-instance> is used, within <service-point> and <implementation> to create the core service implementation for a service by instantiating a class. This is appropriate for simple services that require no explicit configuration.

Attribute Type Required ? Description
class class name yes Fully qualified class name to instantiate.
model primitive | singleton | threaded | pooled no The model used to construct and manage the service. singleton is the default.

Additional service models can be defined via the hivemind.ServiceModels configuration point.

element

The <element> element is used to define an element in a the <schema>. <element> may also be nested within another <element>, to indicate an element that may be enclosed within another element.

Attribute Type Required ? Description
name string yes The name of the element.
content-translator string no The translator configuration that is used to convert the element's content into a useable type. By default, the content is treated as a single string.

Contains: <attribute>, <conversion>, <element>, <rules>

Future enhancements to the HiveMind framework will include greater sophistication in defining configuration content.

implementation

The <implementation> element contributes a core implementation or interceptors to a service extension point.

Attribute Type Required ? Description
service-id string yes The id of the service to extend; this may be a fully qualified id, or the local id of a service within the same module.

Contains: <create-instance>, <interceptor>, <invoke-factory>

interceptor

<interceptor> contributes an interceptor factory to a service extension point. An interceptor factory is a service which implements the ServiceInterceptorFactory interface.

When the service is constructed, each invoked interceptor factory will fabricate an interceptor class to provide additional functionality for the service.

Attribute Type Required ? Description
service-id string yes The id of the service that will create the interceptor for the service. This may be the local id of a service defined within the same module, or a fully qualified id.
before string no A list of interceptors whose behavior should come later in execution than this interceptor.
after string no A list of interceptors whose behavior should come earlier in execution than this interceptor.

Like a service implementation factory, a service interceptor factory may need parameters. As with <invoke-factory>, parameters to the interceptor factory are enclosed by the <interceptor> element. The service interceptor factory will decode the parameters using the schema identified by its parameters-schema-id attribute.

Interceptor ordering is based on dependencies; each interceptor can identify, by interceptor service id, other interceptors. Interceptors in the before list are deferred until after this interceptor. Likewise, this interceptor is deferred until after all interceptors in the after list.

Note
The after dependencies will look familar to anyone who has used Ant or any version of Make. before dependencies are simply the opposite.

The value for before or after is a list of service ids seperated by commas. Service ids may be unqualified if they are within the same module. Alternately, the fixed value * may be used instead of a list: this indicates that the interceptor should be ordered absolutely first or absolutely last.

invoke-factory

The <invoke-factory> element is used to provide a service implementation for a service by invoking another service, a factory service.

Attribute Type Required ? Description
service-id string no The id of the factory service. This may be a simple id for a service within the same module, or a fully qualified service id. The default, if not specified, is hivemind.BuilderFactory.
model primitive | singleton | threaded | pooled no The model used to construct and manage the service. singleton is the default.

A service factory defines its parameters in terms of a schema. The content of the <invoke-factory> is converted, in accordance with the factory's schema, and provided to the factory.

Note
Additional service models can be defined via the hivemind.ServiceModels configuration point.
map

The <map> element appears within <conversion> to override the default mapping from an attribute to a property. By default, the property name is expected to match the attribute name (with the name mangling described in the description of <conversion>); the <map> element is used to handle exceptions to the rule.

Attribute Type Required ? Description
attribute string yes The name of the attribute, which should match a name defined by an<attribute> (of the enclosing <element>).
property string yes The corresponding property (of the Java object specified by the enclosing <conversion>)
module

The <module> element is the root element.

Attribute Type Required ? Description
id string yes The id should be a dotted sequence, like a package name. In general, the module id should be the package name.
version version number yes The version of the module as a dotted sequence of three numbers. Example: "1.0.0"

Contains: <contribution>, <configuration-point>, <implementation> , <service-point>, <schema>, <sub-module>

Warning
The version is not currently used, but a later release of HiveMind will include runtime dependency checking based on version number.
parameters-schema

The <parameters-schema> element is identical to the <schema> element, but only appears inside <service-point>, to define the schema for the parameters for a service implementation factory or service interceptor factory.

rules

<rules> is a container for element and attribute parsing rules within an <element>. These rules are responsible for converting the contributed element and its attributes into objects and object properties. The available rules are documented separately .

schema

The <schema> element is used to describe the format of element contributions to an <configuration-point>, or parameters provided to a service or interceptor factory.

Attribute Type Required ? Description
id string yes Assigns a local id to the schema that may be referenced elsewhere.

Contains: <element>

At a future time, the <schema> element will be extended to provide more options, to provide more precise control over the elements that may be provided in an <contribution>. At this time, a <schema> is simply a list of <element> elements.

Warning
When <schema> appears directly within <configuration-point>, or <parameters-schema> appears directly within <service-point>, then the id attribute is not allowed.
service-point

The <service-point> element defines a service extension point.

Attribute Type Required ? Description
id string yes The simple id of the service extension point. The fully qualified id for the extension point is created by prefixing with the module's id (and a dot).
interface class name yes The fully qualified name of the Java interface supplied by this service extension point.
parameters-schema-id string no Used to reference a <schema> (in the same module, or a different one) that defines parameters used by the service. This is used when the service being defined is a ServiceImplementationFactory or a ServiceInterceptorFactory.
parameters-occurs unbounded | 0..1 | 1 | 1..n | none no The number of parameter element contributions allowed:
  • unbounded: any number
  • 0..1: optional
  • 1 (default) : required
  • 1..n: at least one
  • none: none allowed

Contains: <create-instance>, <interceptor>, <invoke-factory> , <parameters-schema>

sub-module

The <sub-module> element is used to identify an additional HiveMind module deployment descriptor. This is used when a single JAR file contains logically distinct packages, each of which should be treated as an individual HiveMind module. This can also be useful as a way to reduce developer conflict against a single, large, central module descriptor by effectively breaking it into smaller pieces. Sub-modules identified in this way must still have their own unique module id.

Attribute Type Required ? Description
descriptor string yes Location of the module descriptor.

The descriptor should be specified as a relative path, either the name of another module descriptor within the same folder, or within a child folder.

Contribution Processing Rules

The concept of performing a rules-directed conversion of elements and attributes into Java objects was pioneered (to my knowledge) in the Jakarta Digester framework (which started inside Tomcat, moved to Struts, and is now available on its own).

The technique is very powerful, even in the limited subset of Digester provided by HiveMind (over time, the number of available rules will increase).

Rules

Rules are attached to <element>s. Each rule object has two methods: the begin() method is invoked when the element is first encountered. The content of the element is then processed recursively (which will involve more rules). Once that completes, the end() method is invoked.

Note: begin() is invoked in the order that rules are defined within the <rules> element. end() is invoked in inverse order. This rarely makes any difference.

Element processing is based on an object stack. Several rules will manipulate the top object on the stack, setting properties based on attributes or content. The <create-object> rule will instantiate a new object at begin() and pop it off the stack at end() .

In several cases, rule descriptions reference the parent and child objects. The top object on the stack is the child, the object beneath that is the parent. The <set-parent> and <invoke-parent> rules are useful for creating hierarchies of objects.

create-object

The <create-object> rule is used to create a new object, which is pushed onto the stack at begin(). The object is popped off the stack at end(). <create-object> is typically paired up with <invoke-parent> to connect the new object (as a child) to a parent object.

Attribute Type Required ? Description
class string yes The complete class name of the object to create. The class must be public, and have a no-arguments public constructor.
custom

The <custom> rule is used to provide a custom implementation of the Rule interface. Note that any such rules must not contain any individual state, as they will be reused, possibly by multiple threads.

Attribute Type Required ? Description
class string yes The complete class name of the class implementing the Rule interface.
invoke-parent

The <invoke-parent> rule is used to connect the child (top object on the stack) to its parent (the next object down). A method of the parent is invoked, passing the child as a parameter. This invocation occurs inside the rule's begin() method; to ensure that the child object is fully configured before being added to the parent place this rule after all properties of the child object have been configured.

Attribute Type Required ? Description
method string yes The name of the method to invoke on the parent object.
depth number no The depth of the parent object within the object stack. The top object (the child) is at depth 0, and default depth of the parent is 1.
Warning
Top level elements should include an <invoke-parent> rule, and specify the method as addElement. This adds the created, configured object to the list of contributed objects for the <contribution> (or for service factories, adds the object as a parameter).
push-attribute

The <push-attribute> rule reads an attribute, converts it with a translator, and pushes the result onto the stack. It will typically be combined with a <invoke-parent> to get the pushed value added to the configuration point elements (or to some parent object).

Attribute Type Required ? Description
attribute string yes The name of the attribute to read.
read-attribute

The <read-attribute> rule reads an attribute from the current element, optionally translates it (from a string to some other type), and then assigns the value to a property of the top object on the object stack.

Attribute Type Required ? Description
property string yes The name of the property of the top object on the stack to update.
attribute string yes The name of the attribute to read.
skip-if-null boolean no If "true" (the default), then an omitted attribute will be ignored. If "false", the property will be updated regardless.
translator string no A translator that overrides the attribute's translator.
read-content

The <read-content> rule is similar to <read-attribute>, except it concerns the content of the current element (the text wrapped by its start and end tags).

Attribute Type Required ? Description
property string yes The name of the property of the top object on the stack to update.
set-module

<set-module> is used to set a property of the top object on the stack to the module which made the contribution. This is often used when some other attribute of the contribution is the name of a service or configuration extension point (but it is advantageous to defer access to the service or configuration). The module can be used to resolve names of services or configurations that are local to the contributing module.

Attribute Type Required ? Description
property string yes The name of the property of the top object to update with the contributing module.
set-parent

The <set-parent> rule is used to set a property of the child object to parent object. This allows for backwards connections from child objects to parent objects.

Attribute Type Required ? Description
property string yes The name of the property of the child object to set.
set-property

The <set-property> rule is used to set a property of the top object to a preset value.

Attribute Type Required ? Description
property string yes The name of the property of the child object to set.
value string yes The value to set the proeprty to. The is interpreted as with the smart translator, meaning that conversion to normal Java types (boolean, int, etc.) will work as expected.
Translators

Commonly, it is necessary to perform some translation or transformation of string attribute value to convert the value into some other type, such as boolean, integer or date. This can be accomplished by specifying a translator in the <attribute> element (it also applies to element content, with the content-translator attribute of the <element> element).

A translator is an object implementing the Translator interface. The translator value specified in a rule may be either the complete class name of a class implementing the interface, or one of a number of builtin values.

Translators configurations consist of a translator name, and an optional initalizer string. The initializer string is separated from the translator id by a comma, ex: int,min=0 (where min=0 is the initializer string). Initializer strings are generally in the format of key=value[,key=value]* ... but each Translator is free to interpret the initializer string its own way.

The following sections describe the basic translators provided with the framework. You can add additional translators by contributing to the hivemind.Translators configuration point.

bean

The bean translator expects its input to bean in the form service-id:locator. The service-id references a service implementing BeanFactory.

Note
This translator is contributed by the hivemind.lib module.
boolean

The boolean translator converts an input string into a boolean value. "true" is translated to true, and "false" to false.

A default value is used when the input is blank. Normally, this default is false, but the "default" key in the initializer can override this (i.e., boolean,default=true).

class

The class translator converts a class name into a Class object. The value must be a fully qualified class name. A null input value returns null.

Note
This translator is hard-coded, and does not appear in the hivemind.Translators configuration point.
configuration

The configuration translator converts an input value into a configuration point id, then obtains the elements for that configuration point as a List. The id may be fully qualified, or a local id within the contributing module.

A blank input value returns null.

double

The double translator converts the input into an double precision floating point value. It recognizes three initializer values:

  • default: the default value (normally 0) to use when the input is blank
  • min: a minimum acceptible value
  • max: a maximum acceptible value
enumeration

The enumeration translator converts input strings into enumerated values. Enumeration requires an initializer string, with a special format:
enumeration,class-name,input=field-name[,input=field-name]*

That is, the initializer begins with the name of the class containing some number of public static fields. Input values are mapped against field names. Example:
enumeration,java.lang.Boolean,yes=TRUE,no=FALSE

If the input is null or empty, then the translator returns null.

id-list

Translates a comma-seperated list of ids into a comma-seperated list of fully qualified ids (qualified against the contributing module). Alternately, passes the value * through as-is. Id lists are typically used to establish ordering of items within a list, as with <interceptor>.

instance

The object translator converts a fully qualified class name into an object instance. The class must implement a public no-arguments constructor.

int

The int translator converts the input into an integer value. It recognizes three initializer values:

  • default: the default value (normally 0) to use when the input is blank
  • min: a minimum acceptible value
  • max: a maximum acceptible value
Note
This translator is hard-coded, and does not appear in the hivemind.Translators configuration point.
long

The long translator converts the input into an long integer (64 bit) value. It recognizes three initializer values:

  • default: the default value (normally 0) to use when the input is blank
  • min: a minimum acceptible value
  • max: a maximum acceptible value
object

The object translator is allows the caller to provide an object value in a multitude of ways. The object translator inverts the normal roles; the caller has all the power in determining how to interpret the value, and the schema takes whatever value shows up. The object translator is driven by the hivemind.ObjectProviders configuration.

qualified-id

Translates an id into a fully qualified id (qualified against the contributing module's id).

resource

The resource translator is used to find a resource packaged with (or near) the module's deployment descriptor. The input value is the relative path to a file. The translator converts the input value to a Resource for that file.

If the file doesn't exist, then an error is logged. If a localization of the file exists, then the Resource for that localization is returned.

service

The service translator is used to lookup a service in the registry. The input value is either a local service id from the contributing module, or a fully qualified service id.

Note
This translator is hard-coded, and does not appear in the hivemind.Translators configuration point.
service-point

The service translator is used to lookup a service point (not a service) in the registry. The input value is either a local service id from the contributing module, or a fully qualified service id.

smart

The smart translator attempts an automatic conversion from a string value (the attribute value or element content) to a particular type. It determines the type from the property to which the value will be assigned. Smart translator makes use of the JavaBeans's PropertyEditor class for the conversion, which allows easy this translator to be used with most common primitive types, such as int, short and boolean. See the SmartTranslator documentation for more details.

In general, the smart translator is the useful for most ordinary Java type properties, unless you want to specify range constraints.

It recognizes one initializer value:

  • default: the default value to use when the input is blank
Note
This translator is hard-coded, and does not appear in the hivemind.Translators configuration point.
ライブラリ:依存関係

HiveMind は、他のオープンソースのフレームワークと様々な依存関係を持っています。HiveMind 用の Ant ビルドファイルが、ibiblio 上のMavenレポジトリから自動的に依存関係にあるファイルを自動的にダウンロードします。

ファイル名 名称 特記事項
commons-logging-1.0.3.jar Commons-Logging
easymock-1.1.jar EasyMock testing framework HiveMindTestCase によってのみ利用されます。独自のテストに基づいて存在します
jboss-j2ee-3.2.1.jar JBoss J2EE Server HiveMind ライブラリのいくつかのサービスで使われます。JBossそのものとの依存関係はなく、ただjavax.ejbパッケージを使うのみです。
javassist-2.6.jar Javassist bytecode library
oro-2.0.6.jar ORO Regular Expressions
spring-full-1.0.1.jar Spring hivemind.lib.SpringLookupFactory サービスで使われます。

典型的には、HiveMind ライブラリ、Javassist、ORO、及び Commons-logging があれば十分です。貴方のEJB コンテナが javax.ejb クラス群を提供してくれるでしょう。明らかに、貴方は Spring を使う場合には Spring を、HiveMindTestCaseベースクラスを使ったテストを書く際には EasyMock を、夫々含める必要があります。

多くの場合、HiveMind は"handy"バージョンに対してビルドが行われています:十中八九、既存の環境に合うように依存関係にあるもの(jars)の「バージョン」に変更を加える事が出来ます。ただ、いくつかテストを書いておくのを忘れないでくださいネ!

Note
HiveMind は、JDK 1.3 以上で使われる事をはっきりと念頭においてデザインされています。JDK 1.3 に含まれない 1.4 の機能は一切使っておりません。

History of Changes

RSS

Version 1.0 (Sep 22 2004)
  • fix Ensure that the logging interceptor will work properly when wrapping around JDK dynamic proxies. (HLS) Fixes HIVEMIND-55.
Version 1.0-rc-2 (Sep 11 2004)
  • add Add method getSymbolValue() to RegistryInfrastructure and Module (HLS)