Add Functionality To Oracle Coherence Services

This post is going to be about how to add functionality to Oracle Coherence services using the cache configuration and my new favourite Coherence 12.1.2 feature – custom namespaces. I had been playing around with this while on holiday in Turkey – well, there is only so much laying on the beach you can put up with, then with a 10 hour delay at Istanbul airport (thank you Pegasus Airlines) I had plenty of time to write it all up. This is quite a long post but if you want a decent example of what you can do with a custom namespace it should be useful.

There are various bits of functionality in a Coherence Service that cannot be configured using the cache configuration file, for example it is possible to add a ServiceListener to a Service, but there is not a way to do this in configuration. A service listener might be useful if you have application logic that relies on knowing that a particular service is up and running; we could poll for the state but a listener that is called back on is always preferable to polling. In the project I am working on for my current client we rely on knowing services have started and partitions have been allocated before we allow other processing to start.

Now I realise that for most people this might not be a big deal, after all you can quite easily add something like a service listener in code and I have done exactly that in the past. What I usually end up with though is a custom main class that either extends or calls DefaultCacheServer to start my cluster and then does any custom configuration. This is fine, but there are occasions where I do not really want to override the main class as I want to stick with DefaultCacheServer or whatever other main class Coherence might use, for example I might be writing a new GAR package that runs inside WebLogic.

As usual all the code from this blog is in my Blog GitHub repository under the coherence-twelve-one-two module.

Adding a Service Listener

OK, to continue the example mentioned above, say I want to add a ServiceListener to one of my services. If this was a built in Coherence feature it might look something like the in the configuration…

  1.    
  2. <distributed-scheme>
  3.     <scheme-name>my-distributed-scheme</scheme-name>
  4.     <service-name>DistributedCache</service-name>
  5.     <backing-map-scheme>
  6.         <local-scheme/>
  7.     </backing-map-scheme>
  8.     <autostart>true</autostart>
  9.     <service-listeners>
  10.         <service-listener>
  11.             <instance>
  12.                 <class-name>com.thegridman.coherence.service.LoggingServiceListener</class-name>
  13.             </instance>
  14.         </service-listener>
  15.     </service-listeners>
  16. </distributed-scheme>
    
<distributed-scheme>
    <scheme-name>my-distributed-scheme</scheme-name>
    <service-name>DistributedCache</service-name>
    <backing-map-scheme>
        <local-scheme/>
    </backing-map-scheme>
    <autostart>true</autostart>
    <service-listeners>
        <service-listener>
            <instance>
                <class-name>com.thegridman.coherence.service.LoggingServiceListener</class-name>
            </instance>
        </service-listener>
    </service-listeners>
</distributed-scheme>

You can see we have added a new <service-listeners> element at the end of the <distributed-scheme> which in turn contains one or more <service-listener> elements. Inside the <service-listener> element is the standard Coherence <instance> element which creates the actual ServiceListener instance.

Obviously Coherence does not really allow this configuration so we need to add it using the custom namespace functionality. There are two ways we could do this: Option 1, we override the default processing for the default namespace which will allow us to write the configuration exactly as it is above. Option 2, we introduce a new namespace handler and add the service listener using a custom namespace and element name prefix.

I have chosen to go with option 2, the reason being that this will allow me to also have a custom XSD file that goes along with my namespace handler which will validate the XML, both at runtime and also in my IDE at development time. I also think it is just better to do this sort of thing as a custom namespace as it makes it obvious in the configuration file what is standard Coherence and what is extra that we have added. So our configuration might now look something like this, with our custom namespace handler declared with a prefix of ex and a class name of com.thegridman.coherence.config.ExtensionsNamespaceHandler

  1. <cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2.              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
  3.              xmlns:ex="class://com.thegridman.coherence.config.ExtensionsNamespaceHandler"
  4.              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
  5.                  class://com.thegridman.coherence.config.ExtensionsNamespaceHandler coherence-extended-config.xsd">
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xmlns:ex="class://com.thegridman.coherence.config.ExtensionsNamespaceHandler"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
                  class://com.thegridman.coherence.config.ExtensionsNamespaceHandler coherence-extended-config.xsd">

…and our <distributed-scheme>now looks like this, with the service listener elements using the new namespace prefix.

  1. <distributed-scheme>
  2.     <scheme-name>my-distributed-scheme</scheme-name>
  3.     <service-name>DistributedCache</service-name>
  4.     <backing-map-scheme>
  5.         <local-scheme/>
  6.     </backing-map-scheme>
  7.     <autostart>true</autostart>
  8.     <ex:service-listeners>
  9.         <ex:service-listener>
  10.             <instance>
  11.                 <class-name>com.thegridman.coherence.service.LoggingServiceListener</class-name>
  12.             </instance>
  13.         </ex:service-listener>
  14.     </ex:service-listeners>
  15. </distributed-scheme>
<distributed-scheme>
    <scheme-name>my-distributed-scheme</scheme-name>
    <service-name>DistributedCache</service-name>
    <backing-map-scheme>
        <local-scheme/>
    </backing-map-scheme>
    <autostart>true</autostart>
    <ex:service-listeners>
        <ex:service-listener>
            <instance>
                <class-name>com.thegridman.coherence.service.LoggingServiceListener</class-name>
            </instance>
        </ex:service-listener>
    </ex:service-listeners>
</distributed-scheme>

So how do we make this all work and what parts do we need? To answer that we will have a quick look at how service configuration is currently processed.

Default Service Configuration & Building

All of the standard Coherence cache configuration is processed by a built in namespace handler called CacheConfigNamespaceHandler. One of the things that this namespace handler does is register an ElementProcessor for each of the XML elements that define services, for example <distributed-scheme>, <replicated-scheme>, <invocation-scheme> etc…

Default Service Config Processing

The diagram above shows a simplified view of the classes involved (for clarity, and to make the diagram a manageable size, I have not included all of the possible services). You can see that the ElementProcessor that processes the XML elements is the same for each service, it just hands off the actual building of the service to a ServiceBuilder instance that is specific to the service being configured and built. For the <distributed-scheme> element the builder is the DistributedScheme class and there is a corresponding class for each service type.

Extending Service Configuration & Building

In the current release of Coherence (12.1.2) the above classes are not really very extensible. As far as I can tell there are no plug in points that allow additional processing to be easily added. If we just coded the standard namespace handler, element processors and builders for the new XML elements we want to add to create a service listener, we would find that our custom element processors do not get called. Even though the Coherence cache configuration XSD file allows XML from other namespaces to be added inside the <distributed-scheme> element it is just ignored when the services are created.

The only way to have our XML (and any other custom namespace XML) processed is to override a few of the classes above.

Extended Service Config Processing

The diagram above shows another simplified view of the classes we will need to create to be able to extend the configuration of the various services. For simplicity it only shows the classes for processing the <distributed-scheme> element but all the other services would work the same way.

Working across from the left, the first class we need will be the custom namespace handler ExtensionsNamespaceHandler. As well as registering the ElementProcessor instances for any new XML elements we add, such as the <service-listeners> element it will also override the registered ElementProcessor for the the <distributed-scheme> element in the default namespace handler CacheConfigNamespaceHandler.

The next new class is the ExtendedServiceBuilderProcessor class. This class will extend the regular ServiceBuilderProcessor class and be responsible for making sure any XML from addition namespaces is processed for the service.

The ExtendedDisributedScheme class extends the DistributedScheme class and will build the service and apply any extensions to the service.

The ExtendedDisributedScheme class will apply the extensions by making use of the ServiceExtension classes that will be created by the ExtendedServiceBuilderProcessor class, by processing any foreign namespace XML, and then passed to the ExtendedDisributedScheme instance. The reason the code for extending the service, for example adding service listeners, will be in ServiceExtension classes is that this code is common to all the services we might want to extend, so rather than duplicating the code in each service specific scheme class we extend it is better in a helper classes.

Coding The Extension Classes

So lets actually do some code and write the classes we need. I will start with the classes above, as these are all pretty generic, and then add in the classes for the service listener later.

The ExtensionsNamespaceHandler Class

All custom namespaces will need a namespace handler, logically enough ours will be called ExtensionsNamespaceHandler. As I said above, this class will be responsible for registering the ElementProcessor instances of our custom XML elements and also for overriding the default processors in the default CacheConfigNamespaceHandler for the services we are extending.

How do we override the default processors? All the element processors for the standard cache configuration are registered in the CacheConfigNamespaceHandler so to override them we need to get the instance of this handler and re-register our own ElementProcessor classes. Each namespace handler class has an onStartNamespace method that Coherence will call at the start of processing the XML configuration file, so this is where we can add the code in our custom namespace handler to do the overrides.

The onStartNamespace method is passed four parameters, the first of which is the ProcessingContext and it is this context that can give us the instance of the CacheConfigNamespaceHandler. The processing context has a method context.getNamespaceHandler(URI) that will return the namespace handler instance for a given namespace URI. The namespace URI for the default Coherence configuration is class://com.tangosol.coherence.config.xml.CacheConfigNamespaceHandler, that is class:// followed by the fully qualified class name of the CacheConfigNamespaceHandler class. Once we have the instance of the CacheConfigNamespaceHandler class we can call the registerProcessor on it to register our own ElementProcessor classes to override the defaults.

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.config.xml.AbstractNamespaceHandler;
  4. import com.tangosol.config.xml.ProcessingContext;
  5. import com.tangosol.run.xml.XmlElement;
  6.  
  7. import java.net.URI;
  8.  
  9. /**
  10.  * @author Jonathan Knight
  11.  */
  12. public class ExtensionsNamespaceHandler extends AbstractNamespaceHandler
  13. {
  14.     public ExtensionsNamespaceHandler()
  15.     {
  16.     }
  17.  
  18.     @SuppressWarnings("unchecked")
  19.     @Override
  20.     public void onStartNamespace(ProcessingContext context, XmlElement element, String prefix, URI uri)
  21.     {
  22.         URI coherenceURI = URI.create("class://" + CacheConfigNamespaceHandler.class.getCanonicalName());
  23.         AbstractNamespaceHandler coherenceNamespaceHandler
  24.                 = (AbstractNamespaceHandler) context.getNamespaceHandler(coherenceURI);
  25.  
  26.         coherenceNamespaceHandler.registerProcessor(
  27.                 "distributed-scheme",
  28.                 new ExtendedServiceBuilderProcessor(ExtendedDistributedScheme.class));
  29.         coherenceNamespaceHandler.registerProcessor(
  30.                 "invocation-scheme",
  31.                 new ExtendedServiceBuilderProcessor(ExtendedInvocationScheme.class));
  32.         coherenceNamespaceHandler.registerProcessor(
  33.                 "optimistic-scheme",
  34.                 new ExtendedServiceBuilderProcessor(ExtendedOptimisticScheme.class));
  35.         coherenceNamespaceHandler.registerProcessor(
  36.                 "proxy-scheme",
  37.                 new ExtendedServiceBuilderProcessor(ExtendedProxyScheme.class));
  38.         coherenceNamespaceHandler.registerProcessor(
  39.                 "remote-cache-scheme",
  40.                 new ExtendedServiceBuilderProcessor(ExtendedRemoteCacheScheme.class));
  41.         coherenceNamespaceHandler.registerProcessor(
  42.                 "remote-invocation-scheme",
  43.                 new ExtendedServiceBuilderProcessor(ExtendedRemoteInvocationScheme.class));
  44.         coherenceNamespaceHandler.registerProcessor(
  45.                 "replicated-scheme",
  46.                 new ExtendedServiceBuilderProcessor(ExtendedReplicatedScheme.class));
  47.         coherenceNamespaceHandler.registerProcessor(
  48.                 "transactional-scheme",
  49.                 new ExtendedServiceBuilderProcessor(ExtendedTransactionalScheme.class));
  50.     }
  51.  
  52. }
package com.thegridman.coherence.config;

import com.tangosol.config.xml.AbstractNamespaceHandler;
import com.tangosol.config.xml.ProcessingContext;
import com.tangosol.run.xml.XmlElement;

import java.net.URI;

/**
 * @author Jonathan Knight
 */
public class ExtensionsNamespaceHandler extends AbstractNamespaceHandler
{
    public ExtensionsNamespaceHandler()
    {
    }

    @SuppressWarnings("unchecked")
    @Override
    public void onStartNamespace(ProcessingContext context, XmlElement element, String prefix, URI uri)
    {
        URI coherenceURI = URI.create("class://" + CacheConfigNamespaceHandler.class.getCanonicalName());
        AbstractNamespaceHandler coherenceNamespaceHandler
                = (AbstractNamespaceHandler) context.getNamespaceHandler(coherenceURI);

        coherenceNamespaceHandler.registerProcessor(
                "distributed-scheme",
                new ExtendedServiceBuilderProcessor(ExtendedDistributedScheme.class));
        coherenceNamespaceHandler.registerProcessor(
                "invocation-scheme",
                new ExtendedServiceBuilderProcessor(ExtendedInvocationScheme.class));
        coherenceNamespaceHandler.registerProcessor(
                "optimistic-scheme",
                new ExtendedServiceBuilderProcessor(ExtendedOptimisticScheme.class));
        coherenceNamespaceHandler.registerProcessor(
                "proxy-scheme",
                new ExtendedServiceBuilderProcessor(ExtendedProxyScheme.class));
        coherenceNamespaceHandler.registerProcessor(
                "remote-cache-scheme",
                new ExtendedServiceBuilderProcessor(ExtendedRemoteCacheScheme.class));
        coherenceNamespaceHandler.registerProcessor(
                "remote-invocation-scheme",
                new ExtendedServiceBuilderProcessor(ExtendedRemoteInvocationScheme.class));
        coherenceNamespaceHandler.registerProcessor(
                "replicated-scheme",
                new ExtendedServiceBuilderProcessor(ExtendedReplicatedScheme.class));
        coherenceNamespaceHandler.registerProcessor(
                "transactional-scheme",
                new ExtendedServiceBuilderProcessor(ExtendedTransactionalScheme.class));
    }

}

The code above is for our ExtensionsNamespaceHandlerclass. You can see the onStartNamespace where we obtain the CacheConfigNamespaceHandler instance. We then call its registerProcessor to register our overrides for the different elements.

Right now we have not written any custom XML elements that we need to register but we will add the <service-listeners> element later.

The ExtendedServiceBuilderProcessor Class

The next class we need to write is the ExtendedServiceBuilderProcessor class. This will be responsible for creating our ServiceExtension classes from the XML configuration and passing them to the service scheme class for use when the service is built. This class is not specific to any particular service type, just like the default ServiceBuilderProcessor class that it extends so it will be used for all of our services. The purpose of us overriding the class is so that we can make sure that any XML from custom namespaces get correctly processed when the configuration file is processed and when services are created so we need to override the process method.

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.coherence.config.builder.ServiceBuilder;
  4. import com.tangosol.coherence.config.xml.processor.ServiceBuilderProcessor;
  5. import com.tangosol.config.ConfigurationException;
  6. import com.tangosol.config.xml.ProcessingContext;
  7. import com.tangosol.run.xml.XmlElement;
  8.  
  9. import java.util.ArrayList;
  10. import java.util.List;
  11. import java.util.Map;
  12.  
  13. /**
  14.  * @author Jonathan Knight
  15.  */
  16. public class ExtendedServiceBuilderProcessor extends ServiceBuilderProcessor
  17. {
  18.     public ExtendedServiceBuilderProcessor(Class clzToRealize)
  19.     {
  20.         super(clzToRealize);
  21.     }
  22.  
  23.     @SuppressWarnings("unchecked")
  24.     @Override
  25.     public T process(ProcessingContext context, XmlElement element) throws ConfigurationException
  26.     {
  27.         T builder = super.process(context, element);
  28.         if (!(builder instanceof ExtendedServiceScheme))
  29.         {
  30.             return builder;
  31.         }
  32.        
  33.         Map map = context.processForeignElementsOf(element);
  34.         List extensions = new ArrayList<>(map.size());
  35.         for (Object value : map.values())
  36.         {
  37.             if (value instanceof ServiceExtension)
  38.             {
  39.                 extensions.add((ServiceExtension) value);
  40.             }
  41.         }
  42.         ((ExtendedServiceScheme)builder).setExtensions(extensions);
  43.  
  44.         return builder;
  45.     }
  46.  
  47. }
package com.thegridman.coherence.config;

import com.tangosol.coherence.config.builder.ServiceBuilder;
import com.tangosol.coherence.config.xml.processor.ServiceBuilderProcessor;
import com.tangosol.config.ConfigurationException;
import com.tangosol.config.xml.ProcessingContext;
import com.tangosol.run.xml.XmlElement;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author Jonathan Knight
 */
public class ExtendedServiceBuilderProcessor extends ServiceBuilderProcessor
{
    public ExtendedServiceBuilderProcessor(Class clzToRealize)
    {
        super(clzToRealize);
    }

    @SuppressWarnings("unchecked")
    @Override
    public T process(ProcessingContext context, XmlElement element) throws ConfigurationException
    {
        T builder = super.process(context, element);
        if (!(builder instanceof ExtendedServiceScheme))
        {
            return builder;
        }
        
        Map map = context.processForeignElementsOf(element);
        List extensions = new ArrayList<>(map.size());
        for (Object value : map.values())
        {
            if (value instanceof ServiceExtension)
            {
                extensions.add((ServiceExtension) value);
            }
        }
        ((ExtendedServiceScheme)builder).setExtensions(extensions);

        return builder;
    }

}

You can see in the code for the ExtendedServiceBuilderProcessor that we have extended the default ServiceBuilderProcessor class and overridden the process method. In the process method the first thing we do is to call process on the super class, which will return a configured instance of the service scheme class that will build the service. The type of the class returned is the same as the clzToRealize constructor parameter. Next we check to see if the builder returned is an instance of our new ExtendedServiceScheme interface; if it is not we just return the builder. If the builder is an instance of ExtendedServiceScheme then is means we need to process any foreign namespace XML to find any ServiceExtension instances and pass them to the builder.

The ExtendedServiceScheme Interface

The ExtendedServiceScheme interface is a simple interface with one method. All of the classes that we write to extend the built in service builder scheme classes will implement this interface so that any ServiceExtension instances can be passed to them.

  1. package com.thegridman.coherence.config;
  2.  
  3. import java.util.List;
  4.  
  5. /**
  6.  * @author Jonathan Knight
  7.  */
  8. public interface ExtendedServiceScheme
  9. {
  10.     void setExtensions(List extensions);
  11. }
package com.thegridman.coherence.config;

import java.util.List;

/**
 * @author Jonathan Knight
 */
public interface ExtendedServiceScheme
{
    void setExtensions(List extensions);
}

The ServiceExtension Interface

The ServiceExtension interface will be implemented by any class we write that knows how to apply extensions to a service. In our first example we are going to write an implementation that adds ServiceListener instances to a service.

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.config.expression.ParameterResolver;
  4. import com.tangosol.net.Cluster;
  5. import com.tangosol.net.Service;
  6.  
  7. /**
  8.  * @author Jonathan Knight
  9.  */
  10. public interface ServiceExtension
  11. {
  12.     void extendService(Service service, ParameterResolver resolver, ClassLoader loader, Cluster cluster);
  13. }
package com.thegridman.coherence.config;

import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.net.Cluster;
import com.tangosol.net.Service;

/**
 * @author Jonathan Knight
 */
public interface ServiceExtension
{
    void extendService(Service service, ParameterResolver resolver, ClassLoader loader, Cluster cluster);
}

The interface has a single method that is passed the service to have the extensions applied.

The ExtendedDistributedScheme Class

 The next class we need is the builder for the DistributedCacheService which is an extension of the built in DistributedScheme class. Sticking to our theme for class names we will call it ExtendedDistributedScheme.

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.coherence.config.scheme.DistributedScheme;
  4. import com.tangosol.config.expression.ParameterResolver;
  5. import com.tangosol.net.Cluster;
  6. import com.tangosol.net.Service;
  7.  
  8. import java.util.List;
  9.  
  10. /**
  11.  * @author Jonathan Knight
  12.  */
  13. public class ExtendedDistributedScheme extends DistributedScheme implements ExtendedServiceScheme
  14. {
  15.     private List<ServiceExtension> extensions;
  16.  
  17.     public ExtendedDistributedScheme()
  18.     {
  19.     }
  20.  
  21.     @Override
  22.     public void setExtensions(List<ServiceExtension> extensions)
  23.     {
  24.         this.extensions = extensions;
  25.     }
  26.  
  27.     @Override
  28.     public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster)
  29.     {
  30.         Service service = super.realizeService(resolver, loader, cluster);
  31.         if (service.isRunning())
  32.         {
  33.             return service;
  34.         }
  35.  
  36.         if (extensions != null)
  37.         {
  38.             for (ServiceExtension extension : extensions)
  39.             {
  40.                 extension.extendService(service, resolver, loader, cluster);
  41.             }
  42.         }
  43.         return service;
  44.     }
  45. }
package com.thegridman.coherence.config;

import com.tangosol.coherence.config.scheme.DistributedScheme;
import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.net.Cluster;
import com.tangosol.net.Service;

import java.util.List;

/**
 * @author Jonathan Knight
 */
public class ExtendedDistributedScheme extends DistributedScheme implements ExtendedServiceScheme
{
    private List<ServiceExtension> extensions;

    public ExtendedDistributedScheme()
    {
    }

    @Override
    public void setExtensions(List<ServiceExtension> extensions)
    {
        this.extensions = extensions;
    }

    @Override
    public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster)
    {
        Service service = super.realizeService(resolver, loader, cluster);
        if (service.isRunning())
        {
            return service;
        }

        if (extensions != null)
        {
            for (ServiceExtension extension : extensions)
            {
                extension.extendService(service, resolver, loader, cluster);
            }
        }
        return service;
    }
}

This class has to implement ExtendedServiceScheme so that it can be passed the list of ServiceExtension instances by the ExtendedServiceBuilder that we coded above.

As this class is an instance of ServiceBuilder the work of creating the Service is done in the realizeService method, so this is the method we need to override. In our realizeService method we first call the super class’s realizeService method to actually build the service. If the service returned by the super class is already running then there is nothing for us to do as the service has already been configured by a previous call to the builder so we just return the service.

If the service has is not running then we need to apply any ServiceExtension instances if any have been added to this scheme. We just iterate over the list of ServiceExtension instances calling the extendService method on each one.

The Other Extended Scheme Classes

All of the other services that we want to extend will follow the same pattern as the ExtendedDistributedScheme above. So for a replicated scheme we extend ReplicatedScheme with a class called ExtendedReplicatedScheme and the code in it will be identical to the code we wrote in our ExtendedDistributedScheme class. The same for all the other service schemes InvocationScheme, OptimisticScheme, ProxyScheme, RemoteCacheScheme, RemoteInvocationScheme and TransactionalScheme.

That is all of the generic classes for extending services and these will now work for any service extensions that we want to add.

Adding the ServiceListener Extensions

Now we have our service extension framework we can use it for our original requirement of adding ServiceListener instances in the cache configuration file. If we look again at the XML configuration we want to use we will be able to see what classes we need to create.

  1. <distributed-scheme>
  2.     <scheme-name>my-distributed-scheme</scheme-name>
  3.     <service-name>DistributedCache</service-name>
  4.     <backing-map-scheme>
  5.         <local-scheme/>
  6.     </backing-map-scheme>
  7.     <autostart>true</autostart>
  8.     <ex:service-listeners>
  9.         <ex:service-listener>
  10.             <instance>
  11.                 <class-name>com.thegridman.coherence.service.LoggingServiceListener</class-name>
  12.             </instance>
  13.         </ex:service-listener>
  14.     </ex:service-listeners>
  15. </distributed-scheme>
<distributed-scheme>
    <scheme-name>my-distributed-scheme</scheme-name>
    <service-name>DistributedCache</service-name>
    <backing-map-scheme>
        <local-scheme/>
    </backing-map-scheme>
    <autostart>true</autostart>
    <ex:service-listeners>
        <ex:service-listener>
            <instance>
                <class-name>com.thegridman.coherence.service.LoggingServiceListener</class-name>
            </instance>
        </ex:service-listener>
    </ex:service-listeners>
</distributed-scheme>

We already have our ExtensionsNamespaceHandler so we will need ElementProcessor classes for the <service-listeners> and <service-listener> elements. We are actually going to implement these as more than two classes as we want to get some reuse for later examples in the post.

The ServiceListenerBuilder Class

The <service-listener> element does not contain any other XML that we need to process in an ElementProcessor. The only XML that the <service-listener> element will contain is an <instance> element which is processed by the Coherence namespace handler. This means that to handle the <service-listener> element we are going to use a built in ElementProcessor called CustomizableBuilderProcessor which takes the class name of our builder class as its constructor parameter. Our builder looks like this…

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.coherence.config.ParameterList;
  4. import com.tangosol.coherence.config.builder.BuilderCustomization;
  5. import com.tangosol.coherence.config.builder.ParameterizedBuilder;
  6. import com.tangosol.coherence.config.builder.ParameterizedBuilderHelper;
  7. import com.tangosol.config.expression.ParameterResolver;
  8. import com.tangosol.util.ServiceListener;
  9.  
  10. /**
  11.  * @author Jonathan Knight
  12.  */
  13. public class ServiceListenerBuilder
  14.         implements ParameterizedBuilder<ServiceListener>, BuilderCustomization<ServiceListener>
  15. {
  16.     private ParameterizedBuilder<ServiceListener> builder;
  17.  
  18.     public ServiceListenerBuilder()
  19.     {
  20.     }
  21.  
  22.     @Override
  23.     public ServiceListener realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters)
  24.     {
  25.         loader = loader == null ? getClass().getClassLoader() : loader;
  26.  
  27.         if (!ParameterizedBuilderHelper.realizes(builder, ServiceListener.class, resolver, loader))
  28.         {
  29.           throw new IllegalArgumentException(
  30.                   "Unable to build an ServiceListener based on the specified class: "
  31.                   + String.valueOf(builder));
  32.         }
  33.  
  34.         return builder.realize(resolver, loader, listParameters);
  35.     }
  36.  
  37.     @Override
  38.     public ParameterizedBuilder<ServiceListener> getCustomBuilder()
  39.     {
  40.         return this.builder;
  41.     }
  42.  
  43.     @Override
  44.     public void setCustomBuilder(ParameterizedBuilder<ServiceListener> builder)
  45.     {
  46.         this.builder = builder;
  47.     }
  48. }
package com.thegridman.coherence.config;

import com.tangosol.coherence.config.ParameterList;
import com.tangosol.coherence.config.builder.BuilderCustomization;
import com.tangosol.coherence.config.builder.ParameterizedBuilder;
import com.tangosol.coherence.config.builder.ParameterizedBuilderHelper;
import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.util.ServiceListener;

/**
 * @author Jonathan Knight
 */
public class ServiceListenerBuilder
        implements ParameterizedBuilder<ServiceListener>, BuilderCustomization<ServiceListener>
{
    private ParameterizedBuilder<ServiceListener> builder;

    public ServiceListenerBuilder()
    {
    }

    @Override
    public ServiceListener realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters)
    {
        loader = loader == null ? getClass().getClassLoader() : loader;

        if (!ParameterizedBuilderHelper.realizes(builder, ServiceListener.class, resolver, loader))
        {
          throw new IllegalArgumentException(
                  "Unable to build an ServiceListener based on the specified class: "
                  + String.valueOf(builder));
        }

        return builder.realize(resolver, loader, listParameters);
    }

    @Override
    public ParameterizedBuilder<ServiceListener> getCustomBuilder()
    {
        return this.builder;
    }

    @Override
    public void setCustomBuilder(ParameterizedBuilder<ServiceListener> builder)
    {
        this.builder = builder;
    }
}

The class is pretty straight forward and implements two built in interfaces. If you look at the XML configuration you will see that the actual ServiceListener implementation is defined by an instance element (or in theory could be defined by a similar element from another custom namespace that will build a ServiceListener). All our ServiceListenerBuilder needs to do is handle the inner builder and use it to get the listener. The Coherence configuration framework will call the setCustomBuilder method on our ServiceListenerBuilder class and pass to it the inner builder defined by the the <instance> element. The realize method of our ServiceListenerBuilder checks to make sure the inner builder actually builds a ServiceListener and if not throws an exception. If it does, then the inner builder’s realize method is called to get the ServiceListener which is then returned.

The <service-listeners> ElementProcessor Code

The code to process the <service-listeners> element is also quite simple. In effect all it has to do is process the set of inner <service-listener> elements in its process method and return a class that implements ServiceExtension which will be passed to our extended service scheme classes as we explained above.

This pattern of code where we have an ElementProcessor that just handles a list of inner elements who’s ElementProcessor just returns a builder of a particular type is quite common, so we will first write a generic class to handle this processing then we will extend it to work for handling a ServiceListener.

The ListOfInstancesProcessor Class

As our ElementProcessor will handle a list of instances of a specific type I have called it ListOfInstancesProcessor. The code is below…

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.config.ConfigurationException;
  4. import com.tangosol.config.xml.ElementProcessor;
  5. import com.tangosol.config.xml.ProcessingContext;
  6. import com.tangosol.run.xml.XmlElement;
  7.  
  8. import java.util.ArrayList;
  9. import java.util.List;
  10.  
  11. /**
  12.  * @author Jonathan Knight
  13.  */
  14. public class ListOfInstancesProcessor<R extends ListOfInstancesProcessor.ListOfInstancesScheme,T>
  15. implements ElementProcessor<R>
  16. {
  17.     private final Class<R> classToRealize;
  18.     private final Class<T> type;
  19.  
  20.     public ListOfInstancesProcessor(Class<R> classToRealize, Class<T> type)
  21.     {
  22.         this.classToRealize = classToRealize;
  23.         this.type = type;
  24.     }
  25.  
  26.     @SuppressWarnings("unchecked")
  27.     @Override
  28.     public R process(ProcessingContext context, XmlElement element) throws ConfigurationException
  29.     {
  30.         List<XmlElement> children = (List<XmlElement>) element.getElementList();
  31.         ArrayList<T> instances = new ArrayList<>(children.size());
  32.         for (XmlElement child : children)
  33.         {
  34.             try
  35.             {
  36.                 T value = (T) context.processElement(child);
  37.                 if (!type.isAssignableFrom(value.getClass()))
  38.                 {
  39.                     throw new ClassCastException();
  40.                 }
  41.                 instances.add(value);
  42.             }
  43.             catch (ClassCastException e)
  44.             {
  45.                 throw new ConfigurationException("Element " + child.getName()
  46.                                                  + " does not produce an instance of "
  47.                                                  + type,
  48.                                                  "Check the configuration", e);
  49.             }
  50.         }
  51.  
  52.         R scheme;
  53.         try
  54.         {
  55.             scheme = classToRealize.newInstance();
  56.             scheme.setInstances(instances);
  57.         }
  58.         catch (Exception e)
  59.         {
  60.             throw new ConfigurationException("Error creating class " + classToRealize,
  61.                                              "Make sure class " + classToRealize +
  62.                                              " is a valid class with public no-arg constructor", e);
  63.         }
  64.         return scheme;
  65.     }
  66.  
  67.     public static interface ListOfInstancesScheme<T>
  68.     {
  69.         void setInstances(List<T> instances);
  70.     }
  71. }
package com.thegridman.coherence.config;

import com.tangosol.config.ConfigurationException;
import com.tangosol.config.xml.ElementProcessor;
import com.tangosol.config.xml.ProcessingContext;
import com.tangosol.run.xml.XmlElement;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Jonathan Knight
 */
public class ListOfInstancesProcessor<R extends ListOfInstancesProcessor.ListOfInstancesScheme,T> 
implements ElementProcessor<R>
{
    private final Class<R> classToRealize;
    private final Class<T> type;

    public ListOfInstancesProcessor(Class<R> classToRealize, Class<T> type)
    {
        this.classToRealize = classToRealize;
        this.type = type;
    }

    @SuppressWarnings("unchecked")
    @Override
    public R process(ProcessingContext context, XmlElement element) throws ConfigurationException
    {
        List<XmlElement> children = (List<XmlElement>) element.getElementList();
        ArrayList<T> instances = new ArrayList<>(children.size());
        for (XmlElement child : children)
        {
            try
            {
                T value = (T) context.processElement(child);
                if (!type.isAssignableFrom(value.getClass()))
                {
                    throw new ClassCastException();
                }
                instances.add(value);
            }
            catch (ClassCastException e)
            {
                throw new ConfigurationException("Element " + child.getName()
                                                 + " does not produce an instance of "
                                                 + type,
                                                 "Check the configuration", e);
            }
        }

        R scheme;
        try
        {
            scheme = classToRealize.newInstance();
            scheme.setInstances(instances);
        }
        catch (Exception e)
        {
            throw new ConfigurationException("Error creating class " + classToRealize,
                                             "Make sure class " + classToRealize +
                                             " is a valid class with public no-arg constructor", e);
        }
        return scheme;
    }

    public static interface ListOfInstancesScheme<T>
    {
        void setInstances(List<T> instances);
    }
}

The constructor takes two Class parameters. The first is the Class of the value that will be returned from the process method. The second is the class of the value that will be returned from processing the child XML elements of the element passed to this ElementProcessor's process method.

The process method is where all the work is done. First we iterate over each child XmlElement of the XmlElement passed to the process method. For each child element we process it and get the value produced (usually as a result of the child elements ElementProcessor being called). We check to make sure that the returned value is of the expected type (the second constructor parameter) and if not we throw an exception. If the type is correct we add the value to a list of instances created from the child elements. When all the child elements have been processed we create an instance of the class specified as the return type of this ListOfInstancesProcessor (the first constructor parameter) which must have a public no-args constructor and implement the ListOfInstancesProcessor.ListOfInstancesScheme interface. We then pass the list of instances to this class’s setInstances method and return.

The ServiceListenersScheme Class

Now we have a generic class for dealing with a list of child elements we can write the class that will handle the specifics of managing a list of ServiceListener instances. We will call this class ServiceListenersScheme and it will implement the ServiceExtension class as it needs to be able to apply the ServiceListener instances to a Service and it will implement the ListOfInstancesProcessor.ListOfInstancesScheme interface we just described above as it will be used by the ListOfInstancesProcessor.

The code for our ServiceListenersScheme class is shown below…

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.config.expression.ParameterResolver;
  4. import com.tangosol.net.Cluster;
  5. import com.tangosol.net.Service;
  6. import com.tangosol.util.ServiceListener;
  7.  
  8. import java.util.List;
  9.  
  10. /**
  11.  * @author Jonathan Knight
  12.  */
  13. public class ServiceListenersScheme
  14.         implements ServiceExtension, ListOfInstancesProcessor.ListOfInstancesScheme<ServiceListenerBuilder>
  15. {
  16.     private List<ServiceListenerBuilder> instances;
  17.  
  18.     public ServiceListenersScheme()
  19.     {
  20.     }
  21.  
  22.     @Override
  23.     public void setInstances(List<ServiceListenerBuilder> instances)
  24.     {
  25.         this.instances = instances;
  26.     }
  27.  
  28.     @Override
  29.     public void extendService(Service service, ParameterResolver resolver, ClassLoader loader, Cluster cluster)
  30.     {
  31.         if (instances == null || instances.isEmpty())
  32.         {
  33.             return;
  34.         }
  35.  
  36.         for (ServiceListenerBuilder builder : instances)
  37.         {
  38.             ServiceListener listener = builder.realize(resolver, loader, null);
  39.             service.addServiceListener(listener);
  40.         }
  41.     }
  42. }
package com.thegridman.coherence.config;

import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.net.Cluster;
import com.tangosol.net.Service;
import com.tangosol.util.ServiceListener;

import java.util.List;

/**
 * @author Jonathan Knight
 */
public class ServiceListenersScheme
        implements ServiceExtension, ListOfInstancesProcessor.ListOfInstancesScheme<ServiceListenerBuilder>
{
    private List<ServiceListenerBuilder> instances;

    public ServiceListenersScheme()
    {
    }

    @Override
    public void setInstances(List<ServiceListenerBuilder> instances)
    {
        this.instances = instances;
    }

    @Override
    public void extendService(Service service, ParameterResolver resolver, ClassLoader loader, Cluster cluster)
    {
        if (instances == null || instances.isEmpty())
        {
            return;
        }

        for (ServiceListenerBuilder builder : instances)
        {
            ServiceListener listener = builder.realize(resolver, loader, null);
            service.addServiceListener(listener);
        }
    }
}

The setInstances method is the implementation of the ListOfInstancesProcessor.ListOfInstancesScheme interface and will be passed the list of ServiceListenerBuilder instances processed by the ListOfInstancesProcessor.

The extendService method is the implementation of the ServiceExtension interface and will add the ServiceListener instances to the Service. All the method does is loop over the list of ServiceListener builder instances and calls the realize method of each to obtain an instance of a ServiceListener. It then adds each of these listeners to the service.

Registering the ElementProcessors

Now that we have all of our classes the last thing to do is register the new ElementProcessors in our custom namespace handler so that the XML elements are correctly processed. We do this in the constructor of the ExtensionsNamespaceHandler class like this…

  1. public ExtensionsNamespaceHandler()
  2. {
  3.     registerProcessor("service-listener",
  4.             new CustomizableBuilderProcessor<>(ServiceListenerBuilder.class));
  5.     registerProcessor("service-listeners",
  6.             new ListOfInstancesProcessor<>(ServiceListenersScheme.class, ServiceListenerBuilder.class));
  7. }
public ExtensionsNamespaceHandler()
{
    registerProcessor("service-listener", 
            new CustomizableBuilderProcessor<>(ServiceListenerBuilder.class));
    registerProcessor("service-listeners", 
            new ListOfInstancesProcessor<>(ServiceListenersScheme.class, ServiceListenerBuilder.class));
}

And that is all we need to do; we can now add ServiceListener instances to a service in the cache configuration file.

The Example ServiceListener Classes

Two final classes to finish off the example are the ServiceListener implementations. We have one class, MuliplexingServiceListener that multiplexes the three methods of the ServiceListener interface down to a single onEvent method. The second class we create is called LoggingServiceListener which extends MuliplexingServiceListener and in its onEvent method just prints the event to the Coherence log.

The MuliplexingServiceListener Class
  1. package com.thegridman.coherence.service;
  2.  
  3. import com.tangosol.util.Base;
  4. import com.tangosol.util.ServiceEvent;
  5. import com.tangosol.util.ServiceListener;
  6.  
  7. /**
  8.  * @author Jonathan Knight
  9.  */
  10. public abstract class MuliplexingServiceListener extends Base implements ServiceListener
  11. {
  12.     protected MuliplexingServiceListener()
  13.     {
  14.     }
  15.  
  16.     @Override
  17.     public void serviceStarted(ServiceEvent serviceEvent)
  18.     {
  19.         onEvent(serviceEvent);
  20.     }
  21.  
  22.     @Override
  23.     public void serviceStarting(ServiceEvent serviceEvent)
  24.     {
  25.         onEvent(serviceEvent);
  26.     }
  27.  
  28.     @Override
  29.     public void serviceStopping(ServiceEvent serviceEvent)
  30.     {
  31.         onEvent(serviceEvent);
  32.     }
  33.  
  34.     @Override
  35.     public void serviceStopped(ServiceEvent serviceEvent)
  36.     {
  37.         onEvent(serviceEvent);
  38.     }
  39.  
  40.     protected abstract void onEvent(ServiceEvent serviceEvent);
  41. }
package com.thegridman.coherence.service;

import com.tangosol.util.Base;
import com.tangosol.util.ServiceEvent;
import com.tangosol.util.ServiceListener;

/**
 * @author Jonathan Knight
 */
public abstract class MuliplexingServiceListener extends Base implements ServiceListener
{
    protected MuliplexingServiceListener()
    {
    }

    @Override
    public void serviceStarted(ServiceEvent serviceEvent)
    {
        onEvent(serviceEvent);
    }

    @Override
    public void serviceStarting(ServiceEvent serviceEvent)
    {
        onEvent(serviceEvent);
    }

    @Override
    public void serviceStopping(ServiceEvent serviceEvent)
    {
        onEvent(serviceEvent);
    }

    @Override
    public void serviceStopped(ServiceEvent serviceEvent)
    {
        onEvent(serviceEvent);
    }

    protected abstract void onEvent(ServiceEvent serviceEvent);
}
The LoggingServiceListener Class
  1. package com.thegridman.coherence.service;
  2.  
  3. import com.tangosol.util.ServiceEvent;
  4.  
  5. /**
  6.  * @author Jonathan Knight
  7.  */
  8. public class LoggingServiceListener extends MuliplexingServiceListener
  9. {
  10.     @Override
  11.     protected void onEvent(ServiceEvent serviceEvent)
  12.     {
  13.         log(serviceEvent);
  14.     }
  15. }
package com.thegridman.coherence.service;

import com.tangosol.util.ServiceEvent;

/**
 * @author Jonathan Knight
 */
public class LoggingServiceListener extends MuliplexingServiceListener
{
    @Override
    protected void onEvent(ServiceEvent serviceEvent)
    {
        log(serviceEvent);
    }
}

Running An Example

If we now run an instance of DefaultCacheServer with the XML configuration below…

  1. <?xml version="1.0"?>
  2. <cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
  4.              xmlns:ex="class://com.thegridman.coherence.config.ExtensionsNamespaceHandler"
  5.              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
  6.                                  class://com.thegridman.coherence.config.ExtensionsNamespaceHandler coherence-extended-config.xsd">
  7.  
  8.     <defaults>
  9.         <serializer>pof</serializer>
  10.     </defaults>
  11.  
  12.     <caching-scheme-mapping>
  13.         <cache-mapping>
  14.             <cache-name>dist-*</cache-name>
  15.             <scheme-name>my-distributed-scheme</scheme-name>
  16.         </cache-mapping>
  17.     </caching-scheme-mapping>
  18.  
  19.     <caching-schemes>
  20.  
  21.     <distributed-scheme>
  22.         <scheme-name>my-distributed-scheme</scheme-name>
  23.         <service-name>DistributedCache</service-name>
  24.         <backing-map-scheme>
  25.             <local-scheme/>
  26.         </backing-map-scheme>
  27.         <autostart>true</autostart>
  28.         <ex:service-listeners>
  29.             <ex:service-listener>
  30.                 <instance>
  31.                     <class-name>com.thegridman.coherence.service.LoggingServiceListener</class-name>
  32.                 </instance>
  33.             </ex:service-listener>
  34.         </ex:service-listeners>
  35.     </distributed-scheme>
  36.  
  37.     </caching-schemes>
  38.  
  39. </cache-config>
<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xmlns:ex="class://com.thegridman.coherence.config.ExtensionsNamespaceHandler"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
                                  class://com.thegridman.coherence.config.ExtensionsNamespaceHandler coherence-extended-config.xsd">

    <defaults>
        <serializer>pof</serializer>
    </defaults>

    <caching-scheme-mapping>
        <cache-mapping>
            <cache-name>dist-*</cache-name>
            <scheme-name>my-distributed-scheme</scheme-name>
        </cache-mapping>
    </caching-scheme-mapping>

    <caching-schemes>

    <distributed-scheme>
        <scheme-name>my-distributed-scheme</scheme-name>
        <service-name>DistributedCache</service-name>
        <backing-map-scheme>
            <local-scheme/>
        </backing-map-scheme>
        <autostart>true</autostart>
        <ex:service-listeners>
            <ex:service-listener>
                <instance>
                    <class-name>com.thegridman.coherence.service.LoggingServiceListener</class-name>
                </instance>
            </ex:service-listener>
        </ex:service-listeners>
    </distributed-scheme>

    </caching-schemes>

</cache-config>

…when the service is started we will see something like the following message in the Coherence log output

2013-09-15 12:49:17.238/3.426 Oracle Coherence GE 12.1.2.0.0  (thread=DistributedCache:EventDispatcher, member=1):
ServiceEvent{STARTED com.tangosol.coherence.component.util.safeService.safeCacheService.SafeDistributedCacheService}

so we can see that our LoggingServiceListener did indeed get added to the DistributedCache service and received the Started ServiceEvent.

The Service User Context

I suspect this is quite an under used feature of Coherence Services, but it is possible to add an application defined user context value to a service. This can be any object you like and is entirely under your control. It is not clustered at all, so for example, if you add a value as the user context to a Distributed Cache Service on one node of the cluster that value is not replicated to other instances of the same service on other cluster members. The service has two methods…

  1. public Object getUserContext();
  2.  
  3. public void setUserContext(Object o);
public Object getUserContext();

public void setUserContext(Object o);

If you’ve been paying attention I suspect you can see where I am going next, yes, I want to add a user context value (or values) to a Coherence Service by putting them in the cache configuration file using a custom namespace as we did above with service listeners. Now that we have a framework for extending the service configuration we should be able to reuse a lot of the code we wrote above.

In order to make the user context value useful we may want to be able to add multiple application specific values as context values. One way to do this would be to use a Map as the user context value then we could add named values to the map and get them out again. Instead of a Map though I am going to use one of the classes from Coherence called a ResourceRegistry to which you add named resources of particular types.

Here is an example of the type of configuration that I want to be able to add to the cache configuration file…

  1. <distributed-scheme>
  2.     <scheme-name>my-distributed-scheme</scheme-name>
  3.     <service-name>DistributedCache</service-name>
  4.     <thread-count>5</thread-count>
  5.     <backing-map-scheme>
  6.         <local-scheme/>
  7.     </backing-map-scheme>
  8.     <autostart>true</autostart>
  9.     <ex:user-context-resources>
  10.         <ex:user-context-resource>
  11.             <ex:resource-name>test-resource</ex:resource-name>
  12.             <ex:resource-type>com.thegridman.coherence.config.MyTestResource</ex:resource-type>
  13.             <instance>
  14.                 <class-name>com.thegridman.coherence.config.MyTestResource</class-name>
  15.             </instance>
  16.         </ex:user-context-resource>
  17.     </ex:user-context-resources>
  18. </distributed-scheme>
<distributed-scheme>
    <scheme-name>my-distributed-scheme</scheme-name>
    <service-name>DistributedCache</service-name>
    <thread-count>5</thread-count>
    <backing-map-scheme>
        <local-scheme/>
    </backing-map-scheme>
    <autostart>true</autostart>
    <ex:user-context-resources>
        <ex:user-context-resource>
            <ex:resource-name>test-resource</ex:resource-name>
            <ex:resource-type>com.thegridman.coherence.config.MyTestResource</ex:resource-type>
            <instance>
                <class-name>com.thegridman.coherence.config.MyTestResource</class-name>
            </instance>
        </ex:user-context-resource>
    </ex:user-context-resources>
</distributed-scheme>

You can see that there is a custom <user-context-resources> element that can contain multiple <user-context-resource> elements (although the XML above only contains a single <user-context-resource> element). Inside the <user-context-resource> element are the following…

  • The <resource-name> element that contains the name of the resource.
  • The <resource-type> element that contains the class name of the resource (which may be different to the actual class of the resource as it may be say an interface class that the resource implements.
  • An <instance> element that is a standard Coherence element for defining instances of classes, in this case a simple test class.

Sticking to the patterns we used for service listeners above we will need the following classes:

  • A NamedResource class that will hold the name, type and resource value to be added to the ResourceRegistry user context.
  • A NamedResourceBuilder class that knows how to build a NamedResource.
  • A NamedResourceProcessor class to process the <user-context-resource> XML element and return a NamedResourceBuilder.
  • A NamedResourceScheme class that will be our ServiceExtension implementation that knows how to add the various named resources to a Service’s user context.

The NamedResource Class

The NamedResource class is just a holder class for a resource that will be added to a Service’s user context. As we are using a ResourceRegistry as the user context, each resource we add needs to have a name and a type as well as a value.

  1. package com.thegridman.coherence.config;
  2.  
  3. /**
  4.  * @author Jonathan Knight
  5.  */
  6. public class NamedResource
  7. {
  8.     private String name;
  9.     private Class resourceClass;
  10.     private Object resource;
  11.  
  12.     public NamedResource(String name, Class resourceClass, Object resource)
  13.     {
  14.         this.name = name;
  15.         this.resource = resource;
  16.         this.resourceClass = (resourceClass != null) ? resourceClass : resource.getClass();
  17.     }
  18.  
  19.     public String getName()
  20.     {
  21.         return name;
  22.     }
  23.  
  24.     public Class getResourceClass()
  25.     {
  26.         return resourceClass;
  27.     }
  28.  
  29.     public Object getResource()
  30.     {
  31.         return resource;
  32.     }
  33. }
package com.thegridman.coherence.config;

/**
 * @author Jonathan Knight
 */
public class NamedResource
{
    private String name;
    private Class resourceClass;
    private Object resource;

    public NamedResource(String name, Class resourceClass, Object resource)
    {
        this.name = name;
        this.resource = resource;
        this.resourceClass = (resourceClass != null) ? resourceClass : resource.getClass();
    }

    public String getName()
    {
        return name;
    }

    public Class getResourceClass()
    {
        return resourceClass;
    }

    public Object getResource()
    {
        return resource;
    }
}

The NamedResource is just a simple holder for the three attributes we require.

The NamedResourceBuilder Class

We next need a builder class that can build a NamedResource from the elements of the XML configuration; this will be a simple implementation of the built in ParameterizedBuilder class.

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.coherence.config.ParameterList;
  4. import com.tangosol.coherence.config.builder.ParameterizedBuilder;
  5. import com.tangosol.coherence.config.builder.ParameterizedBuilderHelper;
  6. import com.tangosol.config.annotation.Injectable;
  7. import com.tangosol.config.expression.Expression;
  8. import com.tangosol.config.expression.Parameter;
  9. import com.tangosol.config.expression.ParameterResolver;
  10. import com.tangosol.config.expression.Value;
  11. import com.tangosol.util.Base;
  12.  
  13. /**
  14.  * @author Jonathan Knight
  15.  */
  16. public class NamedResourceBuilder implements ParameterizedBuilder<NamedResource>
  17. {
  18.     private String resourceName;
  19.  
  20.     private String resourceClassName;
  21.  
  22.     private Expression resourceExpression;
  23.  
  24.     public String getResourceName()
  25.     {
  26.         return resourceName;
  27.     }
  28.  
  29.     @Injectable
  30.     public void setResourceName(String resourceName)
  31.     {
  32.         this.resourceName = resourceName;
  33.     }
  34.  
  35.     public String getResourceType()
  36.     {
  37.         return resourceClassName;
  38.     }
  39.  
  40.     @Injectable
  41.     public void setResourceType(String resourceClassName)
  42.     {
  43.         this.resourceClassName = resourceClassName;
  44.     }
  45.  
  46.     public Expression getResourceExpression()
  47.     {
  48.         return resourceExpression;
  49.     }
  50.  
  51.     public void setResourceExpression(Expression resourceExpression)
  52.     {
  53.         this.resourceExpression = resourceExpression;
  54.     }
  55.  
  56.     @Override
  57.     public NamedResource realize(ParameterResolver resolver, ClassLoader loader, ParameterList parameters)
  58.     {
  59.         try
  60.         {
  61.             Class<?> resourceClass = null;
  62.  
  63.             if (resourceClassName != null && !resourceClassName.isEmpty())
  64.             {
  65.                 resourceClass = loader.loadClass(resourceClassName);
  66.             }
  67.  
  68.             Object resource = null;
  69.             if (resourceExpression != null)
  70.             {
  71.                 resource = resourceExpression.evaluate(resolver);
  72.             }
  73.  
  74.             if (resource instanceof Value)
  75.             {
  76.                 resource = ((Value)resource).get();
  77.             }
  78.             else if (resource instanceof ParameterizedBuilder)
  79.             {
  80.                 ParameterizedBuilder builder = (ParameterizedBuilder) resource;
  81.                 if (resourceClass != null
  82.                     && !ParameterizedBuilderHelper.realizes(builder, resourceClass, resolver, loader))
  83.                 {
  84.                   throw new IllegalArgumentException("The specified resource class [" +
  85.                                                      resourceClass + "] is not built by: "
  86.                                                      + String.valueOf(builder));
  87.                 }
  88.                 resource = builder.realize(resolver, loader, parameters);
  89.             }
  90.  
  91.             return new NamedResource(resourceName, resourceClass, resource);
  92.         }
  93.         catch (ClassNotFoundException e)
  94.         {
  95.             throw Base.ensureRuntimeException(e);
  96.         }
  97.     }
  98. }
package com.thegridman.coherence.config;

import com.tangosol.coherence.config.ParameterList;
import com.tangosol.coherence.config.builder.ParameterizedBuilder;
import com.tangosol.coherence.config.builder.ParameterizedBuilderHelper;
import com.tangosol.config.annotation.Injectable;
import com.tangosol.config.expression.Expression;
import com.tangosol.config.expression.Parameter;
import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.config.expression.Value;
import com.tangosol.util.Base;

/**
 * @author Jonathan Knight
 */
public class NamedResourceBuilder implements ParameterizedBuilder<NamedResource>
{
    private String resourceName;

    private String resourceClassName;

    private Expression resourceExpression;

    public String getResourceName()
    {
        return resourceName;
    }

    @Injectable
    public void setResourceName(String resourceName)
    {
        this.resourceName = resourceName;
    }

    public String getResourceType()
    {
        return resourceClassName;
    }

    @Injectable
    public void setResourceType(String resourceClassName)
    {
        this.resourceClassName = resourceClassName;
    }

    public Expression getResourceExpression()
    {
        return resourceExpression;
    }

    public void setResourceExpression(Expression resourceExpression)
    {
        this.resourceExpression = resourceExpression;
    }

    @Override
    public NamedResource realize(ParameterResolver resolver, ClassLoader loader, ParameterList parameters)
    {
        try
        {
            Class<?> resourceClass = null;

            if (resourceClassName != null && !resourceClassName.isEmpty())
            {
                resourceClass = loader.loadClass(resourceClassName);
            }

            Object resource = null;
            if (resourceExpression != null)
            {
                resource = resourceExpression.evaluate(resolver);
            }

            if (resource instanceof Value)
            {
                resource = ((Value)resource).get();
            }
            else if (resource instanceof ParameterizedBuilder)
            {
                ParameterizedBuilder builder = (ParameterizedBuilder) resource;
                if (resourceClass != null
                    && !ParameterizedBuilderHelper.realizes(builder, resourceClass, resolver, loader))
                {
                  throw new IllegalArgumentException("The specified resource class [" +
                                                     resourceClass + "] is not built by: "
                                                     + String.valueOf(builder));
                }
                resource = builder.realize(resolver, loader, parameters);
            }

            return new NamedResource(resourceName, resourceClass, resource);
        }
        catch (ClassNotFoundException e)
        {
            throw Base.ensureRuntimeException(e);
        }
    }
}

There are three fields in the builder to hold the three things we need to build a NamedResource, each of them has a setter method annotated with @Injectable so that Coherence will inject the required values when processing the XML configuration.

  • First is the resourceName field with the setter method setResourceName which will be injected from the contents of the <resource-name> XML element.
  • Next is the resourceClassName field with the setter method setResourceType which will be injected from the con tents of the <resource-type> XML element.
  • Finally is the resourceExpression field with the setter method setresourceExpression which will not be injected automatically but will be set by the NamedResourceProcessor using whatever is the value of the last child XML element within the <user-context-resource> element.

You will notice in the realize method that if after evaluating the resourceExpression the value of the resource is an instance of either a ParameterizedBuilder or a Value then the resource value is further evaluated.

If the last XML element in the <user-context-resource> is an <instance> element (as in our example XML) then the resource will evaluate to a ParamterizedBuilder. We then need to call the realize method on this builder to actually build the resource.

By also allowing the resource expression to evaluate to a Value we can do this in the XML configuration…

  1. <ex:user-context-resource>
  2.     <ex:resource-name>replicated-test-cache</ex:resource-name>
  3.     <ex:resource-type>com.tangosol.net.NamedCache</ex:resource-type>
  4.     <init-param>
  5.         <param-name>{cache-ref}</param-name>
  6.         <param-value>replicated-test</param-value>
  7.     </init-param>
  8. </ex:user-context-resource>
<ex:user-context-resource>
    <ex:resource-name>replicated-test-cache</ex:resource-name>
    <ex:resource-type>com.tangosol.net.NamedCache</ex:resource-type>
    <init-param>
        <param-name>{cache-ref}</param-name>
        <param-value>replicated-test</param-value>
    </init-param>
</ex:user-context-resource>

…that is, we can use an <init-param> element to define the resource. Doing this allows us to use a cache reference as our resource, as in the example above where we use a reference to the replicated-test cache, or any other valid <init-param> values such as a scheme-reference to reference another scheme or service, or we could just have a literal value.

The NamedResourceProcessor Class

The NamedResourceProcessor class is the ElementProcessor for the <user-context-resource> element.

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.config.ConfigurationException;
  4. import com.tangosol.config.expression.Expression;
  5. import com.tangosol.config.expression.LiteralExpression;
  6. import com.tangosol.config.xml.ElementProcessor;
  7. import com.tangosol.config.xml.ProcessingContext;
  8. import com.tangosol.run.xml.XmlElement;
  9.  
  10. import java.util.Map;
  11.  
  12. /**
  13.  * @author Jonathan Knight
  14.  */
  15. public class NamedResourceProcessor implements ElementProcessor<NamedResourceBuilder>
  16. {
  17.  
  18.     @SuppressWarnings("unchecked")
  19.     @Override
  20.     public NamedResourceBuilder process(ProcessingContext context, XmlElement element) throws ConfigurationException
  21.     {
  22.         NamedResourceBuilder builder = new NamedResourceBuilder();
  23.         context.inject(builder, element);
  24.  
  25.         Map<String,?> map = context.processRemainingElementsOf(element);
  26.         if (map.size() > 0)
  27.         {
  28.             Object value = map.values().iterator().next();
  29.             Expression expression = (value instanceof Expression)
  30.                     ? (Expression) value : new LiteralExpression(value);
  31.             builder.setResourceExpression(expression);
  32.         }
  33.  
  34.         return builder;
  35.     }
  36. }
package com.thegridman.coherence.config;

import com.tangosol.config.ConfigurationException;
import com.tangosol.config.expression.Expression;
import com.tangosol.config.expression.LiteralExpression;
import com.tangosol.config.xml.ElementProcessor;
import com.tangosol.config.xml.ProcessingContext;
import com.tangosol.run.xml.XmlElement;

import java.util.Map;

/**
 * @author Jonathan Knight
 */
public class NamedResourceProcessor implements ElementProcessor<NamedResourceBuilder>
{

    @SuppressWarnings("unchecked")
    @Override
    public NamedResourceBuilder process(ProcessingContext context, XmlElement element) throws ConfigurationException
    {
        NamedResourceBuilder builder = new NamedResourceBuilder();
        context.inject(builder, element);

        Map<String,?> map = context.processRemainingElementsOf(element);
        if (map.size() > 0)
        {
            Object value = map.values().iterator().next();
            Expression expression = (value instanceof Expression) 
                    ? (Expression) value : new LiteralExpression(value);
            builder.setResourceExpression(expression);
        }

        return builder;
    }
}

This is a simple ElementProcessor implementation. In the process method we first create an instance of NamedResourceBuilder and then we inject the values from the XML configuration into the builder (this would be the resource name and resource type). Then we get the ProcessingContext to process any remaining XML elements, which will be returned in a Map. We are only interested in the values of the map and there should only be a single value. This will be our resource value Expression so we set this into the builder. Finally we return the NamedResourceBuilder.

The NamedResourceScheme Class

The last class we require is the NamedResourceScheme class. Just like the ServiceListenersScheme class we wryte earlier, this class implements the ServiceExtension interface so that it can extend a Service by setting the user context and also it implements the ListOfInstancesProcessor.ListOfInstancesScheme interface so that it can be passed the list of NamedResourceBuilder instances created from the <user-context-resource> elements.

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.tangosol.coherence.config.builder.ParameterizedBuilder;
  4. import com.tangosol.config.expression.ParameterResolver;
  5. import com.tangosol.net.Cluster;
  6. import com.tangosol.net.Service;
  7. import com.tangosol.util.ResourceRegistry;
  8. import com.tangosol.util.SimpleResourceRegistry;
  9.  
  10. import java.util.List;
  11.  
  12. /**
  13.  * @author Jonathan Knight
  14.  */
  15. public class NamedResourcesScheme
  16.         implements ServiceExtension, ListOfInstancesProcessor.ListOfInstancesScheme<NamedResourceBuilder>
  17. {
  18.     private List<NamedResourceBuilder> instances;
  19.  
  20.     public NamedResourcesScheme()
  21.     {
  22.     }
  23.  
  24.     @Override
  25.     public void setInstances(List<NamedResourceBuilder> instances)
  26.     {
  27.         this.instances = instances;
  28.     }
  29.  
  30.     @Override
  31.     public void extendService(Service service, ParameterResolver resolver, ClassLoader loader, Cluster cluster)
  32.     {
  33.         if (instances == null || instances.isEmpty())
  34.         {
  35.             return;
  36.         }
  37.  
  38.         Object userContext = service.getUserContext();
  39.         ResourceRegistry registry;
  40.         if (userContext != null && userContext instanceof ResourceRegistry)
  41.         {
  42.             registry = (ResourceRegistry) userContext;
  43.         }
  44.         else
  45.         {
  46.             registry = new SimpleResourceRegistry();
  47.         }
  48.  
  49.         for (ParameterizedBuilder<NamedResource> builder : instances)
  50.         {
  51.             NamedResource resource = builder.realize(resolver, loader, null);
  52.             registry.registerResource(resource.getResourceClass(), resource.getName(), resource.getResource());
  53.         }
  54.  
  55.         service.setUserContext(registry);
  56.     }
  57. }
package com.thegridman.coherence.config;

import com.tangosol.coherence.config.builder.ParameterizedBuilder;
import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.net.Cluster;
import com.tangosol.net.Service;
import com.tangosol.util.ResourceRegistry;
import com.tangosol.util.SimpleResourceRegistry;

import java.util.List;

/**
 * @author Jonathan Knight
 */
public class NamedResourcesScheme
        implements ServiceExtension, ListOfInstancesProcessor.ListOfInstancesScheme<NamedResourceBuilder>
{
    private List<NamedResourceBuilder> instances;

    public NamedResourcesScheme()
    {
    }

    @Override
    public void setInstances(List<NamedResourceBuilder> instances)
    {
        this.instances = instances;
    }

    @Override
    public void extendService(Service service, ParameterResolver resolver, ClassLoader loader, Cluster cluster)
    {
        if (instances == null || instances.isEmpty())
        {
            return;
        }

        Object userContext = service.getUserContext();
        ResourceRegistry registry;
        if (userContext != null && userContext instanceof ResourceRegistry)
        {
            registry = (ResourceRegistry) userContext;
        }
        else
        {
            registry = new SimpleResourceRegistry();
        }

        for (ParameterizedBuilder<NamedResource> builder : instances)
        {
            NamedResource resource = builder.realize(resolver, loader, null);
            registry.registerResource(resource.getResourceClass(), resource.getName(), resource.getResource());
        }

        service.setUserContext(registry);
    }
}

The setInstances method is the implementation of the ListOfInstancesProcessor.ListOfInstancesScheme interface. This method will be passed the list of NamedResourceBuilder instances to use to build the resources.

The extendService method is the implementation of the ServiceExtension interface. If there are no NamedResourceBuilder instances this method just returns straight away. If there are instances then first the code checks to see if the user context of the Service is already a ResourceRegistry and if it is then the same registry is re-used otherwise a new registry is created. The NamedResouceBuilder instances are then iterated over to build each NamedResource. The resource is then added to the registry for the service.

Registering the User Context Element Processors

Now we have all of the classes built we just need to register the ElementProcessor instances for our custom namespace elements in our namespace handler as we did for the ServiceListener elements. Our ExtensionsNamespaceHandler Class’s constructor now looks like this…

  1. public ExtensionsNamespaceHandler()
  2. {
  3.     registerProcessor("service-listener", new CustomizableBuilderProcessor<>(ServiceListenerBuilder.class));
  4.     registerProcessor("service-listeners",
  5.                       new ListOfInstancesProcessor<>(ServiceListenersScheme.class, ServiceListenerBuilder.class));
  6.     registerProcessor("user-context-resource", new NamedResourceProcessor());
  7.     registerProcessor("user-context-resources",
  8.                       new ListOfInstancesProcessor<>(NamedResourcesScheme.class, NamedResourceBuilder.class));
  9. }
public ExtensionsNamespaceHandler()
{
    registerProcessor("service-listener", new CustomizableBuilderProcessor<>(ServiceListenerBuilder.class));
    registerProcessor("service-listeners", 
                      new ListOfInstancesProcessor<>(ServiceListenersScheme.class, ServiceListenerBuilder.class));
    registerProcessor("user-context-resource", new NamedResourceProcessor());
    registerProcessor("user-context-resources", 
                      new ListOfInstancesProcessor<>(NamedResourcesScheme.class, NamedResourceBuilder.class));
}

…with the addition of the two new processors for the <user-context-resource> and <user-context-resources> elements.

A Configurable Service User Context Example

Now everything is done we can finish with a full blown example of how we can set a whole range of values into the user context for a service.

Here is an example cache configuration file called usercontext-extensions-cache-config.xml

  1. <?xml version="1.0"?>
  2. <cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
  4.              xmlns:ex="class://com.thegridman.coherence.config.ExtensionsNamespaceHandler"
  5.              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
  6.                                  class://com.thegridman.coherence.config.ExtensionsNamespaceHandler coherence-extended-config.xsd">
  7.  
  8.     <defaults>
  9.         <serializer>pof</serializer>
  10.     </defaults>
  11.  
  12.     <caching-scheme-mapping>
  13.         <cache-mapping>
  14.             <cache-name>dist-*</cache-name>
  15.             <scheme-name>my-distributed-scheme</scheme-name>
  16.         </cache-mapping>
  17.  
  18.         <cache-mapping>
  19.             <cache-name>replicated-*</cache-name>
  20.             <scheme-name>my-replicated-scheme</scheme-name>
  21.         </cache-mapping>
  22.     </caching-scheme-mapping>
  23.  
  24.     <caching-schemes>
  25.  
  26.         <distributed-scheme>
  27.             <scheme-name>my-distributed-scheme</scheme-name>
  28.             <service-name>DistributedCache</service-name>
  29.             <thread-count>5</thread-count>
  30.             <backing-map-scheme>
  31.                 <local-scheme/>
  32.             </backing-map-scheme>
  33.             <autostart>true</autostart>
  34.             <ex:user-context-resources>
  35.                 <ex:user-context-resource>
  36.                     <ex:resource-name>test-resource</ex:resource-name>
  37.                     <ex:resource-type>com.thegridman.coherence.config.MyTestResource</ex:resource-type>
  38.                     <instance>
  39.                         <class-name>com.thegridman.coherence.config.MyTestResource</class-name>
  40.                         <init-params>
  41.                             <init-param>
  42.                                 <param-type>String</param-type>
  43.                                 <param-value>from instance element</param-value>
  44.                             </init-param>
  45.                         </init-params>
  46.                     </instance>
  47.                 </ex:user-context-resource>
  48.                 <ex:user-context-resource>
  49.                     <ex:resource-name>replicated-test-cache</ex:resource-name>
  50.                     <ex:resource-type>com.tangosol.net.NamedCache</ex:resource-type>
  51.                     <init-param>
  52.                         <param-type>{cache-ref}</param-type>
  53.                         <param-value>replicated-test</param-value>
  54.                     </init-param>
  55.                 </ex:user-context-resource>
  56.                 <ex:user-context-resource>
  57.                     <ex:resource-name>another-test</ex:resource-name>
  58.                     <ex:resource-type>com.thegridman.coherence.config.MyTestResource</ex:resource-type>
  59.                     <init-param>
  60.                         <param-type>{scheme-ref}</param-type>
  61.                         <param-value>test-scheme</param-value>
  62.                     </init-param>
  63.                 </ex:user-context-resource>
  64.                 <ex:user-context-resource>
  65.                     <ex:resource-name>invocation</ex:resource-name>
  66.                     <ex:resource-type>com.tangosol.net.InvocationService</ex:resource-type>
  67.                     <init-param>
  68.                         <param-type>{scheme-ref}</param-type>
  69.                         <param-value>my-invocation-scheme</param-value>
  70.                     </init-param>
  71.                 </ex:user-context-resource>
  72.             </ex:user-context-resources>
  73.         </distributed-scheme>
  74.  
  75.         <class-scheme>
  76.             <scheme-name>test-scheme</scheme-name>
  77.             <class-name>com.thegridman.coherence.config.MyTestResource</class-name>
  78.             <init-params>
  79.                 <init-param>
  80.                     <param-type>String</param-type>
  81.                     <param-value>from class scheme</param-value>
  82.                 </init-param>
  83.             </init-params>
  84.         </class-scheme>
  85.  
  86.         <replicated-scheme>
  87.             <scheme-name>my-replicated-scheme</scheme-name>
  88.             <service-name>ReplicatedService</service-name>
  89.             <backing-map-scheme>
  90.                 <local-scheme/>
  91.             </backing-map-scheme>
  92.         </replicated-scheme>
  93.  
  94.         <invocation-scheme>
  95.             <scheme-name>my-invocation-scheme</scheme-name>
  96.             <service-name>MyInvocationService</service-name>
  97.             <thread-count>5</thread-count>
  98.             <autostart>true</autostart>
  99.         </invocation-scheme>
  100.     </caching-schemes>
  101.  
  102. </cache-config>
<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
              xmlns:ex="class://com.thegridman.coherence.config.ExtensionsNamespaceHandler"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
                                  class://com.thegridman.coherence.config.ExtensionsNamespaceHandler coherence-extended-config.xsd">

    <defaults>
        <serializer>pof</serializer>
    </defaults>

    <caching-scheme-mapping>
        <cache-mapping>
            <cache-name>dist-*</cache-name>
            <scheme-name>my-distributed-scheme</scheme-name>
        </cache-mapping>

        <cache-mapping>
            <cache-name>replicated-*</cache-name>
            <scheme-name>my-replicated-scheme</scheme-name>
        </cache-mapping>
    </caching-scheme-mapping>

    <caching-schemes>

        <distributed-scheme>
            <scheme-name>my-distributed-scheme</scheme-name>
            <service-name>DistributedCache</service-name>
            <thread-count>5</thread-count>
            <backing-map-scheme>
                <local-scheme/>
            </backing-map-scheme>
            <autostart>true</autostart>
            <ex:user-context-resources>
                <ex:user-context-resource>
                    <ex:resource-name>test-resource</ex:resource-name>
                    <ex:resource-type>com.thegridman.coherence.config.MyTestResource</ex:resource-type>
                    <instance>
                        <class-name>com.thegridman.coherence.config.MyTestResource</class-name>
                        <init-params>
                            <init-param>
                                <param-type>String</param-type>
                                <param-value>from instance element</param-value>
                            </init-param>
                        </init-params>
                    </instance>
                </ex:user-context-resource>
                <ex:user-context-resource>
                    <ex:resource-name>replicated-test-cache</ex:resource-name>
                    <ex:resource-type>com.tangosol.net.NamedCache</ex:resource-type>
                    <init-param>
                        <param-type>{cache-ref}</param-type>
                        <param-value>replicated-test</param-value>
                    </init-param>
                </ex:user-context-resource>
                <ex:user-context-resource>
                    <ex:resource-name>another-test</ex:resource-name>
                    <ex:resource-type>com.thegridman.coherence.config.MyTestResource</ex:resource-type>
                    <init-param>
                        <param-type>{scheme-ref}</param-type>
                        <param-value>test-scheme</param-value>
                    </init-param>
                </ex:user-context-resource>
                <ex:user-context-resource>
                    <ex:resource-name>invocation</ex:resource-name>
                    <ex:resource-type>com.tangosol.net.InvocationService</ex:resource-type>
                    <init-param>
                        <param-type>{scheme-ref}</param-type>
                        <param-value>my-invocation-scheme</param-value>
                    </init-param>
                </ex:user-context-resource>
            </ex:user-context-resources>
        </distributed-scheme>

        <class-scheme>
            <scheme-name>test-scheme</scheme-name>
            <class-name>com.thegridman.coherence.config.MyTestResource</class-name>
            <init-params>
                <init-param>
                    <param-type>String</param-type>
                    <param-value>from class scheme</param-value>
                </init-param>
            </init-params>
        </class-scheme>

        <replicated-scheme>
            <scheme-name>my-replicated-scheme</scheme-name>
            <service-name>ReplicatedService</service-name>
            <backing-map-scheme>
                <local-scheme/>
            </backing-map-scheme>
        </replicated-scheme>

        <invocation-scheme>
            <scheme-name>my-invocation-scheme</scheme-name>
            <service-name>MyInvocationService</service-name>
            <thread-count>5</thread-count>
            <autostart>true</autostart>
        </invocation-scheme>
    </caching-schemes>

</cache-config>

You can see that we have added a number of user context resources to the DistributedCache service.

  • First we have a resource created from an <instance> element with the name test-resource and the type com.thegridman.coherence.config.MyTestResource that takes a single String constructor argument.
  • Second we have a resource created from an <init-param> element that is named replicated-test-cachethat will be a reference to a cache called replicated-test.
  • Third we have another resource created from an <init-param> element that is named another-test and is a reference to a class-scheme.
  • Finally we have yet another resource from an <init-param> element that is named invocation and is a scheme reference to an InvocationService scheme.

If we then create a unit test class that uses the cache configuration file we can prove that all the user context resources are created correctly.

  1. package com.thegridman.coherence.config;
  2.  
  3. import com.oracle.tools.junit.AbstractTest;
  4. import com.tangosol.net.CacheFactory;
  5. import com.tangosol.net.ConfigurableCacheFactory;
  6. import com.tangosol.net.InvocationService;
  7. import com.tangosol.net.NamedCache;
  8. import com.tangosol.net.Service;
  9. import com.tangosol.util.ResourceRegistry;
  10. import org.junit.BeforeClass;
  11. import org.junit.Test;
  12.  
  13. import static org.hamcrest.CoreMatchers.is;
  14. import static org.hamcrest.CoreMatchers.notNullValue;
  15. import static org.junit.Assert.assertThat;
  16.  
  17. /**
  18.  * @author Jonathan Knight
  19.  */
  20. public class UserContextServiceExtensionsAcceptanceTest extends AbstractTest
  21. {
  22.     private static ConfigurableCacheFactory cacheFactory;
  23.  
  24.     @BeforeClass
  25.     public static void createCacheFactory()
  26.     {
  27.         System.setProperty("tangosol.coherence.localhost", "Jonathans-MacBook-Pro.local");
  28.         System.setProperty("tangosol.coherence.cacheconfig", "usercontext-extensions-cache-config.xml");
  29.  
  30.         cacheFactory = CacheFactory.getCacheFactoryBuilder().getConfigurableCacheFactory(null);
  31.     }
  32.  
  33.     @Test
  34.     public void shouldContainTestResourceFromInstance() throws Exception
  35.     {
  36.         Service service = cacheFactory.ensureService("DistributedCache");
  37.         ResourceRegistry registry = (ResourceRegistry) service.getUserContext();
  38.  
  39.         MyTestResource resource = registry.getResource(MyTestResource.class, "test-resource");
  40.         assertThat(resource, is(notNullValue()));
  41.         assertThat(resource.getParamValue(), is("from instance element"));
  42.     }
  43.  
  44.     @Test
  45.     public void shouldContainTestResourceFromSchemeRef() throws Exception
  46.     {
  47.         Service service = cacheFactory.ensureService("DistributedCache");
  48.         ResourceRegistry registry = (ResourceRegistry) service.getUserContext();
  49.  
  50.         MyTestResource resource = registry.getResource(MyTestResource.class, "another-test");
  51.         assertThat(resource, is(notNullValue()));
  52.         assertThat(resource.getParamValue(), is("from class scheme"));
  53.     }
  54.  
  55.     @Test
  56.     public void shouldContainCacheReference() throws Exception
  57.     {
  58.         Service service = cacheFactory.ensureService("DistributedCache");
  59.         ResourceRegistry registry = (ResourceRegistry) service.getUserContext();
  60.  
  61.         NamedCache replicatedCache = registry.getResource(NamedCache.class, "replicated-test-cache");
  62.         assertThat(replicatedCache, is(notNullValue()));
  63.         assertThat(replicatedCache.getCacheName(), is("replicated-test"));
  64.     }
  65.  
  66.     @Test
  67.     public void shouldContainInvocationService() throws Exception
  68.     {
  69.         Service service = cacheFactory.ensureService("DistributedCache");
  70.         ResourceRegistry registry = (ResourceRegistry) service.getUserContext();
  71.  
  72.         InvocationService invocationService = registry.getResource(InvocationService.class, "invocation");
  73.         assertThat(invocationService, is(notNullValue()));
  74.         assertThat(invocationService.getInfo().getServiceName(), is("MyInvocationService"));
  75.     }
  76.  
  77. }
package com.thegridman.coherence.config;

import com.oracle.tools.junit.AbstractTest;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.ConfigurableCacheFactory;
import com.tangosol.net.InvocationService;
import com.tangosol.net.NamedCache;
import com.tangosol.net.Service;
import com.tangosol.util.ResourceRegistry;
import org.junit.BeforeClass;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;

/**
 * @author Jonathan Knight
 */
public class UserContextServiceExtensionsAcceptanceTest extends AbstractTest
{
    private static ConfigurableCacheFactory cacheFactory;

    @BeforeClass
    public static void createCacheFactory()
    {
        System.setProperty("tangosol.coherence.localhost", "Jonathans-MacBook-Pro.local");
        System.setProperty("tangosol.coherence.cacheconfig", "usercontext-extensions-cache-config.xml");

        cacheFactory = CacheFactory.getCacheFactoryBuilder().getConfigurableCacheFactory(null);
    }

    @Test
    public void shouldContainTestResourceFromInstance() throws Exception
    {
        Service service = cacheFactory.ensureService("DistributedCache");
        ResourceRegistry registry = (ResourceRegistry) service.getUserContext();

        MyTestResource resource = registry.getResource(MyTestResource.class, "test-resource");
        assertThat(resource, is(notNullValue()));
        assertThat(resource.getParamValue(), is("from instance element"));
    }

    @Test
    public void shouldContainTestResourceFromSchemeRef() throws Exception
    {
        Service service = cacheFactory.ensureService("DistributedCache");
        ResourceRegistry registry = (ResourceRegistry) service.getUserContext();

        MyTestResource resource = registry.getResource(MyTestResource.class, "another-test");
        assertThat(resource, is(notNullValue()));
        assertThat(resource.getParamValue(), is("from class scheme"));
    }

    @Test
    public void shouldContainCacheReference() throws Exception
    {
        Service service = cacheFactory.ensureService("DistributedCache");
        ResourceRegistry registry = (ResourceRegistry) service.getUserContext();

        NamedCache replicatedCache = registry.getResource(NamedCache.class, "replicated-test-cache");
        assertThat(replicatedCache, is(notNullValue()));
        assertThat(replicatedCache.getCacheName(), is("replicated-test"));
    }

    @Test
    public void shouldContainInvocationService() throws Exception
    {
        Service service = cacheFactory.ensureService("DistributedCache");
        ResourceRegistry registry = (ResourceRegistry) service.getUserContext();

        InvocationService invocationService = registry.getResource(InvocationService.class, "invocation");
        assertThat(invocationService, is(notNullValue()));
        assertThat(invocationService.getInfo().getServiceName(), is("MyInvocationService"));
    }

}

If we run the test class above then all the tests should pass and should show each resource correctly created in the user context of the service.

The Power of Custom Namespaces

I think this shows just how useful custom namespaces can be for creating configurable extensions to the built in Coherence functionality. There are a few extensibility issues with the current implementation of the configuration framework which if addressed would make things like this easier without overriding too many built in classes.

Hopefully it has been useful as an aid to seeing just what you can do with namespaces in your own applications.

Enjoy…

You may also like...

2 Responses

  1. Ramakrishna Gutha says:

    Very interesting post. Thanks for sharing

    We have a requirement where Coherence cache server and cache client should run in same JVM and we want to use only local cache and to achieve that we have created 2 threads one for starting Main where we are invoking DefaultCacheServer’s main method and other thread will be polling thread which listens for a FTP directory.

    We have overridden the default coherence configuration as below:

    local
    LocalCache

    The problem when the start the Coherence server, it is terminating immediately and when we used distributed-scheme setting the server was not terminating.

    We have clueless and suggestions are appreciated

Leave a Reply

Your email address will not be published. Required fields are marked *