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…
- <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>
<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
- <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">
<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.
- <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>
<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…
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.
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.
- 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));
- }
- }
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 ExtensionsNamespaceHandler
class. 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.
- 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;
- }
- }
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.
- package com.thegridman.coherence.config;
- import java.util.List;
- /**
- * @author Jonathan Knight
- */
- public interface ExtendedServiceScheme
- {
- void setExtensions(List extensions);
- }
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.
- 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);
- }
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
.
- 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;
- }
- }
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.
- <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>
<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…
- 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;
- }
- }
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…
- 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);
- }
- }
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…
- 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);
- }
- }
- }
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…
- public ExtensionsNamespaceHandler()
- {
- registerProcessor("service-listener",
- new CustomizableBuilderProcessor<>(ServiceListenerBuilder.class));
- registerProcessor("service-listeners",
- new ListOfInstancesProcessor<>(ServiceListenersScheme.class, ServiceListenerBuilder.class));
- }
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
- 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);
- }
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
- 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);
- }
- }
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…
- <?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>
<?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…
- public Object getUserContext();
- 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…
- <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>
<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 theResourceRegistry
user context. - A
NamedResourceBuilder
class that knows how to build aNamedResource
. - A
NamedResourceProcessor
class to process the<user-context-resource>
XML element and return aNamedResourceBuilder
. - A
NamedResourceScheme
class that will be ourServiceExtension
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.
- 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;
- }
- }
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.
- 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);
- }
- }
- }
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 methodsetResourceName
which will be injected from the contents of the<resource-name>
XML element. - Next is the
resourceClassName
field with the setter methodsetResourceType
which will be injected from the con tents of the<resource-type>
XML element. - Finally is the
resourceExpression
field with the setter methodsetresourceExpression
which will not be injected automatically but will be set by theNamedResourceProcessor
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…
- <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>
<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.
- 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;
- }
- }
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.
- 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);
- }
- }
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…
- 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));
- }
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
- <?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>
<?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 nametest-resource
and the typecom.thegridman.coherence.config.MyTestResource
that takes a single String constructor argument. - Second we have a resource created from an
<init-param>
element that is namedreplicated-test-cache
that will be a reference to a cache calledreplicated-test
. - Third we have another resource created from an
<init-param>
element that is namedanother-test
and is a reference to aclass-scheme
. - Finally we have yet another resource from an
<init-param>
element that is namedinvocation
and is a scheme reference to anInvocationService
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.
- 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"));
- }
- }
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…
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
There is no point using DefaultCacheServer if you just want a local cache. Just define a cache configuration file with a caches mapped to a local-scheme and then just get the caches as normal.
If you ask questions on https://community.oracle.com/community/fusion_middleware/coherence/coherence_support you will get a more in depth answer than I can post in these blog comments