Annotated module classes

HiveMind 2 introduces a complete new way of defining modules. It doesn't requires xml at all, but bases on pure java and annotations.
This approach reduces the complexity of module definitions, allows better support of refactoring and debugging and simply
let the developer use what he is familiar with: java.

The new kind of modules are defined by a simple class whose methods represent services, configurations, contributions etc.
The following example shows how multiple services are defined and wired. The method bodies are used as factory methods for the construction of the service implementations:

@Module( id="calculator" )
public class CalculatorModule extends AbstractAnnotatedModule
{

    @Service( id="Adder" )
    public Adder getAdderService()
    {
        return new AdderImpl();
    }
    
    @Service( id="Subtracter" )
    public Subtracter getSubtractorService()
    {
        return new SubtracterImpl();
    }
    
    @Service( id="Multiplier" )
    public Multiplier getMultiplierService()
    {
        return new MultiplierImpl();
    }

    @Service( id="Divider" )
    public Divider getDividerService()
    {
        return new DividerImpl();
    }

    @Service( id="Calculator" )
    public Divider getCalculatorService()
    {
        CalculatorImpl result = new CalculatorImpl();
        return autowireProperties(result);
    }

}

That example code correponds with this xml module (besides the interceptors).

The calculator service is autowired but alternatively could be wired manually:

@Service( id="Calculator" )
public Divider getCalculatorService()
{
    CalculatorImpl result = new CalculatorImpl();
    result.setAdder(service(Adder.class)); 
    result.setSubtracter(service(Subtracter.class)); 
    return result;
}

How to load an annotated module in a registry

There are different ways to reach this goal:

Build a registry from annotated modules only

If the whole registry is built from annotated modules then the use of an AnnotatedRegistryBuilder is most comfortable:

   AnnotatedRegistryBuilder builder = new AnnotatedRegistryBuilder();
   TypedRegistry registry = builder.constructRegistry(CalculatorModule.class, TestModule.class);
        
   Calculator calculator = registry.getService(Calculator.class);

The AnnotatedRegistryBuilder is a specialization of RegistryBuilder that accepts
a variable list of classes or class names. It returns a special TypedRegistry implementation
that makes use of generics. Note the lack of any cast on the service retrieval!

Build a registry from mixed definition types

This code shows how to mix modules defined by xml with annotated modules:

RegistryDefinition registryDefinition = new RegistryDefinition();

XmlModuleReader reader = new XmlModuleReader(registryDefinition);
reader.readClassPathModule("META-INF/hivemodule.xml");

AnnotatedModuleReader reader = new AnnotatedModuleReader(registryDefinition);
reader.readModule(org.apache.hivemind.SimpleModule.class);

RegistryBuilder builder = new RegistryBuilder(registryDefinition );
Registry registry = builder.constructRegistry();

Autoload an annotated module

Autoloading is triggered by a call to RegistryBuilder#autoDetectModules or RegistryBuilder#constructDefaultRegistry.
If this process is already triggered in an application (for example by using HiveMindServletFilter) then it's possible to prepare a library (jar, war) that
can just be dropped into the classpath whereas include modules are loaded automatically.

Create a class that implements the RegistryProvider interface:

public class MyModuleProvider implements RegistryProvider
{
    public void process(RegistryDefinition registryDefinition, ErrorHandler errorHandler)
    {
        AnnotatedModuleReader reader = new AnnotatedModuleReader(registryDefinition);
        reader.readModule(Module1.class, Module2.class, Module3.class);
    }

}

Add all annotated module classes to the call to readModule,
and finally add the name of the class to the hivemind-providers attribute in the file META-INF/MANIFEST.MF :

Manifest-Version: 1.0
hivemind-providers: foo.MyModuleProvider

In future versions this will be easier and won't require an implementation of RegistryProvider.

Define configurations and contributions

The definition of configurations and contributions is as straightforward as the service definition. A configuration point is defined by the Configuration annotation:

@Module( id="panorama.startup" )
public class PanoramaStartupModule extends AbstractAnnotatedModule
{
    @Service( id="Startup" )
    public Runnable getStartupService()
    {
        TaskExecutor executor = new TaskExecutor();
        // Configuration is used here
        executor.setTasks(configuration("tasks", List.class));
        return executor;
    }
    
    @Configuration( id="tasks" )
    public List<Task> getTasks()
    {
        return new ArrayList<Task>();
    }
}

The method marked with the Configuration method creates empty configuration data in
but it could already provide initial data.
A contribution from a different module references the id of the configuration and gets
a reference to the configuration data handed in.

public class PanoramaMailModule extends AbstractAnnotatedModule
{    
    @Contribution( configurationId="panorama.startup.tasks" )
    public void contributeTaks(List<Task> tasks)
    {
        Task mailStartupTask = new Task();
        mailStartupTask.setExecutable(service("MailStartup", Executable.class));
        tasks.add(mailStartupTask);
    }

}

The module id of an annotated module

The @Module annotation is optional. The id attribute defines the id of the module if the annotation is present.
If not, then the id is built from package and class name.

The Adder service in the followind example can be referenced by "calculator.Adder":

@Module( id="calculator" )
public class CalculatorModule 
{
    @Service( id="Adder" )
    public Adder getAdderService() ...
}

Whereas in the next example it can be referenced by "org.apache.hivemind.Adder":

package org.apache.hivemind;

public class CalculatorModule 
{
    @Service( id="Adder" )
    public Adder getAdderService() ...
}

Obtaining services and configurations

In the examples above other services and configurations are obtained by calls to the service and configuration methods defined
in the ancestor AbstractAnnotatedModule. It is not required to inherit from this class (see #Prerequisites).
If one doesn't want to inherit from this ancestor it is possible to get a reference to the registry injected automatically:

public class TestModule
{
    private TypedRegistry _registry;

    /**
     * This setter is used to inject the registry reference.
     */
    public void setRegistry(TypedRegistry registry)
    {
        _registry = registry;
    }
    
    @Service( id="Adder" )
    public Adder getAdderService() 
    {
        Subtracter subtracter = _registry.getService(Adder.class);
        ...
    }
}

HiveMind checks if a module class implementes a property named "registry" and calls the corresponding
setter after a module instance has been created.

Don't call methods directly

It is not possible to obtain valid configuration or service references by calling the corresponding methods directly.
The instance returned is not controlled by HiveMind. It is no singleton, not intercepted etc.

Prerequisites for a module class

A module class

  • must be public
  • must not be final
  • must not be abstract
  • must have a public constructor without parameters
  • should not execute login in its constructor

A module class need not inherit from AbstractAnnotatedModule above.
It can be any POJO conforming to the rules above. AbstractAnnotatedModule just offers convenience
methods for looking up services and configurations.

  • No labels