The Apache Jakarta Project Jakarta HiveMind Project
 
   

Overriding a Service

It is not uncommon to want to override an existing service and replace it with a new implementation. This goes beyond simply intercepting the service ... the goal is to replace the original implementation with a new implementation. This occurs frequently in Tapestry where frequently an existing service is replaced with a new implementation that handles application-specific cases (and delegates most cases to the default implementation).

Note
Plans are afoot to refactor Tapestry 3.1 to make considerable use of HiveMind. Many of the ideas represented in HiveMind germinated in earlier Tapestry releases.

HiveMind doesn't have an explicit mechanism for accomplishing this ... that's because its reasonable to replace and wrap existing services just with the mechanisms already available.

Step One: A non-overridable service

To describe this technique, we'll start with a ordinary, every day service. In fact, for discussion purposes, there will be two services: Consumer and Provider. Ultimately, we'll show how to override Provider. Also for discussion purposes, we'll do all of this in a single module, though (of course) you can as easily split it up across many modules.

To begin, we'll define the two services, and set Provider as a property of Consumer:

<module id="ex.override" version="1.0.0">
  <service-point id="Provider" interface="ex.override.Provider">
    <create-instance class="ex.override.impl.ProviderImpl"/>
  </service-point>
  
  <service-point id="Consumer" interface="ex.override.Consumer">
    <invoke-factory>
      <construct class="ex.override.impl.Consumer">
        <set-service property="provider" service-id="Provider"/>
    </invoke-factory>
  </service-point>
</module> 

Step Two: Add some indirection

In this step, we still have just the two services ... Consumer and Provider, but they are linked together less explicitly, by using substitution symbols.

<module id="ex.override" version="1.0.0">
  <service-point id="Provider" interface="ex.override.Provider">
    <create-instance class="ex.override.impl.ProviderImpl"/>
  </service-point>
  
  <contribution configuration-id="hivemind.FactoryDefaults">
    <default symbol="ex.override.Provider" value="ex.override.Provider"/>
  </contribution>
  
  <service-point id="Consumer" interface="ex.override.Consumer">
    <invoke-factory>
      <construct class="ex.override.impl.Consumer">
        <set-service property="provider" service-id="${ex.override.Provider}"/>
    </invoke-factory>
  </service-point>
</module>       

The indirection is in the form of the symbol ex.override.Provider, which evaluates to the service id ex.override.Provider and the end result is the same as step one. We needed to use a fully qualified service id because, ultimately, we don't know in which modules the symbol will be referenced.

Step Three: Override!

The final step is to define a second service and slip it into place. For kicks, the OverrideProvider service will get a reference to the original Provider service.

<module id="ex.override" version="1.0.0">
  <service-point id="Provider" interface="ex.override.Provider">
    <create-instance class="ex.override.impl.ProviderImpl"/>
  </service-point>
  
  <contribution configuration-id="hivemind.FactoryDefaults">
    <default symbol="ex.override.Provider" value="ex.override.Provider"/>
  </contribution>
  
  <service-point id="OverrideProvider" interface="ex.override.Provider">
    <invoke-factory>
      <construct class="ex.override.impl.OverrideProviderImpl">
        <set-service property="defaultProvider" service-id="Provider"/>
      </construct>
    </invoke-factory>
  </service-point>
  
  <!-- ApplicationDefaults overrides FactoryDefaults -->
  
  <contribution id="hivemind.ApplicationDefaults">
    <default symbol="ex.override.Provider" value="ex.override.OverrideProvider"/>
  </contribution>
  
  <!-- Consumer unchanged from step 2 -->
  
  <service-point id="Consumer" interface="ex.override.Consumer">
    <invoke-factory>
      <construct class="ex.override.impl.Consumer">
        <set-service property="provider" service-id="${ex.override.Provider}"/>
    </invoke-factory>
  </service-point>
</module>  

The new service, OverrideProvider, gets a reference to the original service using its real id. It can't use the symbol that the Consumer service uses, because that would end up pointing it at itself. Again, in this example it's all happening in a single module, but it could absolutely be split up, with OverrideProvider and the configuration to hivemind.ApplicationDefaults in an entirely different module.

hivemind.ApplicationDefaults overrides hivemind.FactoryDefaults. This means that the Consumer will be connected to ex.override.OverrideProvider.

Note that the <service-point> for the Consumer doesn't change between steps two and three.

Limitations

The main limitation to this approach is that you can only do it once for a service; there's no way to add an EvenMoreOverridenProvider service that wraps around OverrideProvider (that wraps around Provider). Making multiple contributions to the hivemind.ApplicationDefaults configuration point with the name symbol name will result in a runtime error ... and unpredictable results.

This could be addressed by adding another source to the hivemind.SymbolSources configuration.

To be honest, if this kind of indirection becomes extremely frequent, then HiveMind should change to accomidate the pattern, perhaps adding an <override> element, similar to a <interceptor> element.