This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

Setting up a Service in HiveMind

Description

<n>This article was originally published on the <a href="http://blogs.encodo.ch/news/view_article.php?id=55"><b>Encodo Blogs</b></a>. Browse on over to see more!</n> <hr> HiveMind is the IOC manager used together with Tapestry; it's in charge of bootstrapping and connecting all of the myriad objects and services available to a Tapestry application. Applications based on Tapestry are encouraged to use it to configure their application- and session-level objects and services as well. Once it works, it works well. Getting it configured in the first place---especially when new to HiveMind---is an exercise in patience. Larger errors are detected at startup, when HiveMind tries to parse the configuration you've entered. Once you've gotten a bit better at this, you're building a configuration that parses correctly, but fails during execution when HiveMind fails to connect one object to another as you expected (usually due to a naming mismatch of some sort or another). <h>Module IDs</h> Every module starts with the <c><module></c> tag, like this: <code> <module id="com.encodo.customer.project" version="1.0.0"> </code> If HiveMind can't locate a class specified in the configuration, it prepends the module id and tries again. A well-chosen module id makes the enclosed specification much cleaner because most of the canonical class name can be left off. <h>Global Services</h> One of the most common concepts to configure in HiveMind is that of a global service. Each service is a POJO and is instantiated under a particular name by HiveMind. Taking it slowly, let's look at the introductory tag of a service declaration: <code> <service-point id="CustomApplicationService" interface="tapestry.CustomApplicationService"> ... </service> </code> This creates a service identified by the id <c>com.encodo.customer.project.CustomApplicationService</c> (expanded to include the module id). The service is understood to have the interface represented by the class, <c>com.encodo.customer.project.tapesty.CustomApplicationService</c> (also expanded to include the module id). <i>Do not confuse the two</i>. Though the service id <i>looks</i> like a class name, it does not have to correspond to an existing class. The outer wrapper identifies the service uniquely and indicates which interface it can be expected to have. It remains to tell HiveMind how to create and configure the object that represents the service. <code> <service-point id="CustomApplicationService" interface="tapestry.CustomApplicationService"> <span class="highlight"><invoke-factory> <create-instance class="tapestry.CustomApplicationService"> </invoke-factory></span> </service> </code> Now HiveMind is happy and will create an instance of the class <c>com.encodo.customer.project.tapestry.CustomApplicationService</c> when requested. <h>Delayed Loading vs. Eager Loading</h> HiveMind services are loaded on-demand, in order to reduce startup time to only that time required to parse configuration files. If you need the service to be started immediately---for example, if it is a periodic task that is never accessed directly by the application---add the service to HiveMind's <c>EagerLoad</c> contribution point. <code> <contribution configuration-id="hivemind.EagerLoad"> <load service-id="CustomApplicationService"> </contribution> </code> <h>Service Interfaces</h> Though recent versions of HiveMind allow a service to be declared without an explicit interface (as done above), HiveMind treats services declared thusly differently. Specifically, it instantiates the service object twice instead of just once. Since this is often an undesired side-effect, it's better to just extract an interface from the class and use that when declaring the service. <code> <span class="highlight">ICustomApplicationService</span>"> <invoke-factory> <create-instance class="tapestry.CustomApplicationService"> </invoke-factory> </service> </code> Multiple interdependent services are configured slightly differently. Instead of simply telling HiveMind to create an instance of an object, you have to use the <c><construct></c> tags, which allow nesting of <c><set-service></c> tags (among others). The example below shows the application service, but now dependent on a <i>fileManagerService</i>. <code> <service-point id="CustomFileManagerService" interface="tapestry.ICustomFileManagerService"> <invoke-factory> <create-instance class="tapestry.CustomFileManagerService"> </invoke-factory> </service> <service-point id="CustomApplicationService" interface="tapestry.ICustomApplicationService"> <invoke-factory> <span class="highlight"><construct class="tapestry.CustomApplicationService"> <set-service property="fileManagerService" service-id="CustomFileManagerService"> </construct></span> </invoke-factory> </service-point> </code> Since both services are defined in the same module, one can refer to the other with its short name, <c>CustomFileMangerService</c> instead of the canonical service id, <c>com.encodo.customer.project.CustomFileMangerService</c> (and, yes, the ids are case-sensitive). The property name must also match the following method in <c>CustomApplicationService</c>. <code> public void setFileManagerService(ICustomApplicationService _service) { fileManagerService = _service; } </code> If it is not available, HiveMind will throw an exception. HiveMind also has no trouble connecting the file manager simultaneously to the application service, like this: <code> <service-point id="CustomFileManagerService" interface="tapestry.ICustomFileManagerService"> <invoke-factory> <construct class="tapestry.CustomFileManagerService"> <span class="highlight"><set-service property="applicationService" service-id="CustomApplicationService"></span> </construct> </invoke-factory> </service> </code> The services will be able to access each other through the proxies created and assigned by HiveMind. <h>Service Factories</h> <div class="caution">All access to HiveMind service properties during service construction are <c>null</c> ... because HiveMind hasn't had the chance to assign them yet.</div> This means that you can't read properties from one service in order to configure the other service and you can't pass the service reference to other objects, because it will always be <c>null</c> in the constructor. One way around this is to use a service factory to create a service. The pattern works as follows: <ol> Create a service factory entry in your hivemind.xml file Add all of the <c><set-property></c> calls to the service factory instead of the service Change the instantiation clause of the service to use the service factory instead of <c><construct></c> or <c><create-instance></c> </ol> Let's make a service factory for the file manager service. <code> <service-point id="CustomFileManagerServiceFactory" interface="org.apache.hivemind.ServiceImplementationFactory"> <invoke-factory> <construct class="tapestry.CustomFileManagerServiceFactory"> <set-service property="applicationService" service-id="CustomApplicationService"> </construct> </invoke-factory> </service-point> </code> As you can see, the application service property is set on the factory now instead of the file manager itself. The file manager can now be created using the factory, like this: <code> <service-point id="CustomFileManagerService" interface="doclib.CustomFileManagerService"> <span class="highlight"><invoke-factory service-id="CustomFileManagerServiceFactory"></span> </service-point> </code> Instantiation of the file manager is delegated to the service factory, which creates the file manager object, assigns the necessary properties (like the application service) and returns it. A possible Java implementation is shown below: <code> public class CustomFileManagerServiceFactory implements ServiceImplementationFactory { private ICustomApplicationService applicationService; public ICustomApplicationService getApplicationService() { return applicationService; } public void setApplicationService(ICustomApplicationService _applicationService) { applicationService = _applicationService; } public Object createCoreServiceImplementation(ServiceImplementationFactoryParameters _factoryParameters) { CustomFileManagerService result = new CustomFileManagerService(); result.setApplicationService(getApplicationService()); return result; } } </code> <c>createCoreServiceImplementation</c> is called after the factory has been created and all other HiveMind properties assigned. Though quite a bit more work---and not at all intuitive---it's at least possible to control configuration at quite a low level ... once one knows how. Even the example as given won't work because of one niggling little detail: HiveMind will parse the configuration without trouble, but will throw an exception when trying to create the service: <div class="error">Unable to construct service com.encodo.customer.project.CustomFileManagerService: Error at context:/WEB-INF/hivemodule.xml, line 50, column 68: Parameters to service implementation factory CustomFileManagerServiceFactory contains no contributions but expects exactly one contribution.</div> The solution is not immediately obvious, but, after digging around on the web and finding <a href="http://permalink.gmane.org/gmane.comp.jakarta.hivemind.devel/694">Bug with parameter-occurs?</a>, it became clear that the default parameter setting was wrong. The fix is to allow the service factory to be instantiated with 0 parameters (instead of requiring 1): <code> <service-point id="CustomFileManagerServiceFactory" interface="org.apache.hivemind.ServiceImplementationFactory"> <span class="highlight">parameters-occurs="0..n"</span> ... </service-point> </code> With this fix in place, HiveMind can create the service factory and the file manager service as expected. <n>Using Java 1.5, Tapestry 4.02, HiveMind 1.1.1</n>