Oracle Coherence Regular Expression Cache Mapping

RegEx

This blog post is going to be about Oracle Coherence regular expression cache mapping. Recently there was an e-mail exchange between Andrew Wilson, a good friend of mine, and the Oracle Coherence engineers on the subject of using Java regular expressions in cache mappings rather than just being limited to adding an asterisk suffix. Andrew suggested that Oracle could add this to the core Coherence product whereas the engineering team pointed out that there was probably enough infrastructure in Coherence to do it yourself. Well, always being up for a challenge, especially if it means writing code (or cycling), I thought I’d write it myself and do a blog post about it. After all I’d much rather the engineering team spent time on putting things in the core product that I can’t do myself.

Now, as with most things in Oracle Coherence you could approach this a number of ways depending on what version of Coherence you are using and how hacky you are comfortable with getting. For the purposes of this post I am going to write one approach using the more elegant custom namespace support in Oracle Coherence 12.1.2 and then I will cover a different approach for those of you yet to upgrade and still on 3.7.1 or earlier.

How Built-In Coherence Cache Mapping Works

To start with I’ going to go over how cache mappings work at the moment in Coherence so we can see the limitations and also understand the limitations we will introduce when we add regular expression support.

Currently in Coherence cache mappings you have two choices, you either put the exact cache name in the mapping or you put a name prefix ending with an asterisk into the mapping (or a third option, you just have an asterisk that matches everything). For example here are three mappings

  1. <cache-mapping>
  2.     <cache-name>schedule-cache</cache-name>
  3.     <scheme-name>schedule-scheme</scheme-name>
  4. </cache-mapping>
  5.  
  6. <cache-mapping>
  7.     <cache-name>replicated-*</cache-name>
  8.     <scheme-name>replicated-scheme</scheme-name>
  9. </cache-mapping>
  10.  
  11. <cache-mapping>
  12.     <cache-name>*</cache-name>
  13.     <scheme-name>distributed-scheme</scheme-name>
  14. </cache-mapping>
<cache-mapping>
    <cache-name>schedule-cache</cache-name>
    <scheme-name>schedule-scheme</scheme-name>
</cache-mapping>

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

<cache-mapping>
    <cache-name>*</cache-name>
    <scheme-name>distributed-scheme</scheme-name>
</cache-mapping>

If we had these mappings in our cache configuration file then the following rules apply:

  • Cache schedule-cache maps to the schedule-scheme cache scheme.
  • Caches starting with replicated- map to the replicated-scheme cache scheme, e.g. caches called replicated-stuff and replicated-things both match this mapping.
  • Any other cache names will match the default * mapping and map to the distributed-scheme cache scheme.

Mapping Order

The order that you put the mappings in the cache configuration file is important. Coherence applies the following rules.
1. Exact matches can go anywhere in the caching-scheme-mapping section of the configuration file and Coherence will use that mapping to map the matching cache name.
2. The default * mapping can also go anywhere in the caching-scheme-mapping section of the configuration file. When mapping a cache name if there is no exact match and no prefix match then the default mapping will be used if one has been specified.
3. If there is no exact match then the LAST prefix mapping that matches the cache name will be used. For that reason you should make sure your mappings are more generic at the top of the caching-scheme-mapping section of the configuration file and get more specific as you go down the file.

For example, suppose we have the following mappings…

  1. <cache-mapping>
  2.     <cache-name>distributed-*</cache-name>
  3.     <scheme-name>distributed-scheme</scheme-name>
  4. </cache-mapping>
  5.  
  6. <cache-mapping>
  7.     <cache-name>distributed-two-*</cache-name>
  8.     <scheme-name>distributed-scheme-two</scheme-name>
  9. </cache-mapping>
<cache-mapping>
    <cache-name>distributed-*</cache-name>
    <scheme-name>distributed-scheme</scheme-name>
</cache-mapping>

<cache-mapping>
    <cache-name>distributed-two-*</cache-name>
    <scheme-name>distributed-scheme-two</scheme-name>
</cache-mapping>

…then a cache called distributed-two-cache would map to the distributed-scheme-two scheme as the distributed-two-* mapping is the last one that matches the cache name. If we now reordered the mappings like this…

  1. <cache-mapping>
  2.     <cache-name>distributed-two-*</cache-name>
  3.     <scheme-name>distributed-scheme-two</scheme-name>
  4. </cache-mapping>
  5.  
  6. <cache-mapping>
  7.     <cache-name>distributed-*</cache-name>
  8.     <scheme-name>distributed-scheme</scheme-name>
  9. </cache-mapping>
<cache-mapping>
    <cache-name>distributed-two-*</cache-name>
    <scheme-name>distributed-scheme-two</scheme-name>
</cache-mapping>

<cache-mapping>
    <cache-name>distributed-*</cache-name>
    <scheme-name>distributed-scheme</scheme-name>
</cache-mapping>

…then the cache called distributed-two-cache would now map to the distributed-scheme scheme as the distributed-* mapping is the last one that matches the cache name. In this case the distributed-two-* mapping would never be used as the more generic distributed-* comes after it in the mappings list.

  1. <cache-mapping>
  2.     <cache-name>distributed-two-*</cache-name>
  3.     <scheme-name>distributed-scheme-two</scheme-name>
  4. </cache-mapping>
  5.  
  6. <cache-mapping>
  7.     <cache-name>distributed-*</cache-name>
  8.     <scheme-name>distributed-scheme</scheme-name>
  9. </cache-mapping>
<cache-mapping>
    <cache-name>distributed-two-*</cache-name>
    <scheme-name>distributed-scheme-two</scheme-name>
</cache-mapping>

<cache-mapping>
    <cache-name>distributed-*</cache-name>
    <scheme-name>distributed-scheme</scheme-name>
</cache-mapping>

Andrew’s Problem

Now you might wonder why we need regular expression mapping, after all, we have managed with the mapping functionality above for many years and versions of Coherence. The scenario Andrew gave was one where we have a system that has many caches, say a hundred. Of these caches some have an expiry of say 1 day and some an expiry of 2 days, but there is no common cache name prefix shared by caches with the same expiry so you end up with this…

  1. <caching-scheme-mapping>
  2.     <cache-mapping>
  3.         <cache-name>cache-one</cache-name>
  4.         <scheme-name>distributed-scheme-one-day</scheme-name>
  5.     </cache-mapping>
  6.  
  7.     <cache-mapping>
  8.         <cache-name>cache-one</cache-name>
  9.         <scheme-name>distributed-scheme-two-days</scheme-name>
  10.     </cache-mapping>
  11.  
  12.     <!-- Lots more cache mappings go here -->
  13.    
  14.     <cache-mapping>
  15.         <cache-name>cache-ninety-nine</cache-name>
  16.         <scheme-name>distributed-scheme-one-day</scheme-name>
  17.     </cache-mapping>
  18.    
  19.     <cache-mapping>
  20.         <cache-name>cache-one-hundred</cache-name>
  21.         <scheme-name>distributed-scheme-two-days</scheme-name>
  22.     </cache-mapping>    
  23. </caching-scheme-mapping>
  24.  
  25. <caching-schemes>
  26.     <distributed-scheme>
  27.         <scheme-name>distributed-scheme-one-day</scheme-name>
  28.         <scheme-ref>distributed-scheme</scheme-ref>
  29.         <backing-map-scheme>
  30.             <local-scheme>
  31.                 <expiry-delay>1d</expiry-delay>
  32.             </local-scheme>
  33.         </backing-map-scheme>
  34.     </distributed-scheme>
  35.  
  36.     <distributed-scheme>
  37.         <scheme-name>distributed-scheme-two-days</scheme-name>
  38.         <scheme-ref>distributed-scheme</scheme-ref>
  39.         <backing-map-scheme>
  40.             <local-scheme>
  41.                 <expiry-delay>2d</expiry-delay>
  42.             </local-scheme>
  43.         </backing-map-scheme>
  44.     </distributed-scheme>
  45.  
  46.     <distributed-scheme>
  47.         <scheme-name>distributed-scheme</scheme-name>
  48.         <service-name>DistributedService</service-name>
  49.         <backing-map-scheme>
  50.             <local-scheme/>
  51.         </backing-map-scheme>
  52.         <autostart>true</autostart>
  53.     </distributed-scheme>
  54. </caching-schemes>
<caching-scheme-mapping>
    <cache-mapping>
        <cache-name>cache-one</cache-name>
        <scheme-name>distributed-scheme-one-day</scheme-name>
    </cache-mapping>

    <cache-mapping>
        <cache-name>cache-one</cache-name>
        <scheme-name>distributed-scheme-two-days</scheme-name>
    </cache-mapping>

    <!-- Lots more cache mappings go here -->
    
    <cache-mapping>
        <cache-name>cache-ninety-nine</cache-name>
        <scheme-name>distributed-scheme-one-day</scheme-name>
    </cache-mapping>
    
    <cache-mapping>
        <cache-name>cache-one-hundred</cache-name>
        <scheme-name>distributed-scheme-two-days</scheme-name>
    </cache-mapping>    
</caching-scheme-mapping>

<caching-schemes>
    <distributed-scheme>
        <scheme-name>distributed-scheme-one-day</scheme-name>
        <scheme-ref>distributed-scheme</scheme-ref>
        <backing-map-scheme>
            <local-scheme>
                <expiry-delay>1d</expiry-delay>
            </local-scheme>
        </backing-map-scheme>
    </distributed-scheme>

    <distributed-scheme>
        <scheme-name>distributed-scheme-two-days</scheme-name>
        <scheme-ref>distributed-scheme</scheme-ref>
        <backing-map-scheme>
            <local-scheme>
                <expiry-delay>2d</expiry-delay>
            </local-scheme>
        </backing-map-scheme>
    </distributed-scheme>

    <distributed-scheme>
        <scheme-name>distributed-scheme</scheme-name>
        <service-name>DistributedService</service-name>
        <backing-map-scheme>
            <local-scheme/>
        </backing-map-scheme>
        <autostart>true</autostart>
    </distributed-scheme>
</caching-schemes>

The comment line !-- Lots more cache mappings go here -- would be where the other 96 cache mappings go, as you can imagine a maintenance nightmare.

Now if we have the option to use regular expression instead of just the * suffix then we have a lot more flexibility and the chance that we would greatly reduce the need for our 100 individual cache mappings.

Coherence 12.1.2 Implementation

The first implementation I am going to cover is for the latest 12.1.2 version of Coherence and will be built using a custom namespace. Using a namespace is the easiest way to extend Coherence to do something other than what it does out of the box as it is now supported in the core product so you are not having to override internal Coherence classes to hack your own functionality in.

I covered some of the namespaces functionality in a previous post Coherence 12.1.2 Custom Namespaces and Oracle NoSQL and there are also some videos on the Coherence YouTube Channel that cover this too Coherence Configuration Enhancements – Part 1 – 4 so I’m going to dive straight into the implementation rather than explain namespace support.

The RegExCacheMapping Class

The first thing we need is an implementation of the built in CacheMapping class that supports regular expressions. We will call this class RegExCacheMapping. The CacheMapping class is the class that is part of the configuration framework in 12.1.2 that holds details of a particular mapping and works out whether its mapping matches a given cache name. In this case it should be easy enough to extend this class to use a regular expression.

  1. package com.thegridman.coherence.regex;
  2.  
  3. import com.tangosol.coherence.config.CacheMapping;
  4. import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder;
  5. import com.tangosol.config.annotation.Injectable;
  6. import com.tangosol.config.expression.ParameterResolver;
  7.  
  8. import java.util.ArrayList;
  9. import java.util.List;
  10. import java.util.regex.Matcher;
  11. import java.util.regex.Pattern;
  12.  
  13. /**
  14.  * @author Jonathan Knight
  15.  */
  16. public class RegExCacheMapping extends CacheMapping
  17. {
  18.     private Pattern pattern;
  19.  
  20.     public RegExCacheMapping(String regEx, String schemeName)
  21.     {
  22.         super(regEx, schemeName);
  23.         pattern = Pattern.compile(regEx);
  24.     }
  25.  
  26.     @Override
  27.     public boolean isForCacheName(String sCacheName)
  28.     {
  29.         Matcher matcher = pattern.matcher(sCacheName);
  30.         return matcher.matches();
  31.     }
  32.  
  33.     @Override
  34.     public boolean usesWildcard()
  35.     {
  36.         return true;
  37.     }
  38.  
  39.     @Override
  40.     @Injectable("interceptors")
  41.     public void setEventInterceptorBuilders(List<NamedEventInterceptorBuilder> listBuilders)
  42.     {
  43.         List<NamedEventInterceptorBuilder> regExBuilders = new ArrayList<>(listBuilders.size());
  44.         for (NamedEventInterceptorBuilder builder : listBuilders)
  45.         {
  46.             regExBuilders.add(new RegExEventInterceptorBuilder(builder));
  47.         }
  48.         super.setEventInterceptorBuilders(regExBuilders);
  49.     }
  50. }
package com.thegridman.coherence.regex;

import com.tangosol.coherence.config.CacheMapping;
import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder;
import com.tangosol.config.annotation.Injectable;
import com.tangosol.config.expression.ParameterResolver;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Jonathan Knight
 */
public class RegExCacheMapping extends CacheMapping
{
    private Pattern pattern;

    public RegExCacheMapping(String regEx, String schemeName)
    {
        super(regEx, schemeName);
        pattern = Pattern.compile(regEx);
    }

    @Override
    public boolean isForCacheName(String sCacheName)
    {
        Matcher matcher = pattern.matcher(sCacheName);
        return matcher.matches();
    }

    @Override
    public boolean usesWildcard()
    {
        return true;
    }

    @Override
    @Injectable("interceptors")
    public void setEventInterceptorBuilders(List<NamedEventInterceptorBuilder> listBuilders)
    {
        List<NamedEventInterceptorBuilder> regExBuilders = new ArrayList<>(listBuilders.size());
        for (NamedEventInterceptorBuilder builder : listBuilders)
        {
            regExBuilders.add(new RegExEventInterceptorBuilder(builder));
        }
        super.setEventInterceptorBuilders(regExBuilders);
    }
}

The code is pretty simple.

  • The class extends com.tangosol.coherence.config.CacheMapping which it has to as it will be used in the same places in the configuration.
  • The constructor takes the same arguments as CacheMapping but in this case the first constructor parameter will be the regular expression rather than the cache name. We pass this up to the super class constructor and we also compile the instance of java.util.regex.Pattern ready for use later.
  • We override the usesWildcard() to always return true. In the super class this method would return true if the cache name has an * suffix, but a reg-ex is always a wild-card mapping.
  • The isForCacheName(String sCacheName) again is a straight forward override. As its name suggests this method returns true if this mapping is for the specified cache name. In our implementation we just see if the reg-ex we have matches the specified cache name.
  • Finally we have overridden the setEventInterceptorBuilders(List<NamedEventInterceptorBuilder> listBuilders) method; the reason for which I shall explain. In the cache configuration file in 12.1.2 we can specify a list of Interceptors that can intercept events on a cache. This can be done either in the service configuration or in the cache mapping configuration. If the interceptors are specified in the cache mapping then they only listen for events on caches who’s name matches the mapping – you should be able to see where this is going, the normal interceptor supports the rules for matching cache names using only an * suffix, it does not support regular expressions so for this we need to write our own implementation of the interceptor builder NamedEventInterceptorBuilder and also an implementation of the interceptor wrapper to be built. Our implementation of setEventInterceptorBuilders(List<NamedEventInterceptorBuilder> listBuilders) just loops over the list of normal NamedEventInterceptorBuilders that it is passed and converts them to RegExEventInterceptorBuilder which are then passed to the super class.

The RegExEventInterceptorBuilder Class

The sole purpose of the RegExEventInterceptorBuilder class is to build our interceptor wrapper class RegExEventInterceptor wrapper class instead of the default NamedEventInterceptor class.

  1. package com.thegridman.coherence.regex;
  2.  
  3. import com.tangosol.coherence.config.ParameterList;
  4. import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder;
  5. import com.tangosol.coherence.config.builder.ParameterizedBuilder;
  6. import com.tangosol.coherence.config.builder.ParameterizedBuilderHelper;
  7. import com.tangosol.config.expression.Parameter;
  8. import com.tangosol.config.expression.ParameterResolver;
  9. import com.tangosol.net.events.EventInterceptor;
  10.  
  11. /**
  12.  * @author Jonathan Knight
  13.  */
  14. public class RegExEventInterceptorBuilder extends NamedEventInterceptorBuilder
  15. {
  16.  
  17.     public RegExEventInterceptorBuilder(NamedEventInterceptorBuilder builder)
  18.     {
  19.         this.setName(builder.getName());
  20.         this.setRegistrationBehavior(builder.getRegistrationBehavior());
  21.         this.setOrder(builder.getOrder());
  22.         this.setCustomBuilder(builder.getCustomBuilder());
  23.     }
  24.  
  25.     @SuppressWarnings("unchecked")
  26.     public RegExEventInterceptor realize(ParameterResolver resolver, ClassLoader loader, ParameterList parameters)
  27.     {
  28.         loader = (loader == null) ? getClass().getClassLoader() : loader;
  29.  
  30.         ParameterizedBuilder builder = getCustomBuilder();
  31.  
  32.         if (!ParameterizedBuilderHelper.realizes(builder, EventInterceptor.class, resolver, loader))
  33.         {
  34.             throw new IllegalArgumentException("Unable to build an EventInterceptor based on the specified class: "
  35.                                                + String.valueOf(builder));
  36.         }
  37.  
  38.         EventInterceptor interceptor = (EventInterceptor) builder.realize(resolver, loader, parameters);
  39.  
  40.         Parameter serviceNameParam = resolver.resolve("service-name");
  41.         String serviceName = serviceNameParam == null ? null : serviceNameParam.evaluate(resolver).as(String.class);
  42.         Parameter cacheNameParam = resolver.resolve("cache-name");
  43.         String cacheName = cacheNameParam == null ? null : cacheNameParam.evaluate(resolver).as(String.class);
  44.  
  45.         return new RegExEventInterceptor(getName(), interceptor, cacheName,
  46.                                          serviceName, getOrder(), getRegistrationBehavior());
  47.     }
  48.  
  49. }
package com.thegridman.coherence.regex;

import com.tangosol.coherence.config.ParameterList;
import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder;
import com.tangosol.coherence.config.builder.ParameterizedBuilder;
import com.tangosol.coherence.config.builder.ParameterizedBuilderHelper;
import com.tangosol.config.expression.Parameter;
import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.net.events.EventInterceptor;

/**
 * @author Jonathan Knight
 */
public class RegExEventInterceptorBuilder extends NamedEventInterceptorBuilder
{

    public RegExEventInterceptorBuilder(NamedEventInterceptorBuilder builder)
    {
        this.setName(builder.getName());
        this.setRegistrationBehavior(builder.getRegistrationBehavior());
        this.setOrder(builder.getOrder());
        this.setCustomBuilder(builder.getCustomBuilder());
    }

    @SuppressWarnings("unchecked")
    public RegExEventInterceptor realize(ParameterResolver resolver, ClassLoader loader, ParameterList parameters)
    {
        loader = (loader == null) ? getClass().getClassLoader() : loader;

        ParameterizedBuilder builder = getCustomBuilder();

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

        EventInterceptor interceptor = (EventInterceptor) builder.realize(resolver, loader, parameters);

        Parameter serviceNameParam = resolver.resolve("service-name");
        String serviceName = serviceNameParam == null ? null : serviceNameParam.evaluate(resolver).as(String.class);
        Parameter cacheNameParam = resolver.resolve("cache-name");
        String cacheName = cacheNameParam == null ? null : cacheNameParam.evaluate(resolver).as(String.class);

        return new RegExEventInterceptor(getName(), interceptor, cacheName,
                                         serviceName, getOrder(), getRegistrationBehavior());
    }

}

The main method of interest is the realize method that will build the interceptor. The code follows the standard patter for builder code that is going to delegate to another builder. In this case the actual interceptor class to be built is specified in the inner <instance> tag of the configuration. In our method the interceptor builder will be returned by the getCustomBuilder() method and we then use that to build the actual interceptor. Once we have this it is wrapped in our RegExEventInterceptor which will ensure the interceptor only receives events for the caches which names matching the reg-ex.

The RegExEventInterceptor Class

We need and implementation of NamedEventInterceptor which works with regular expressions and which is built by our RegExEventInterceptorBuilder so we created the RegExEventInterceptor class.

  1. package com.thegridman.coherence.regex;
  2.  
  3. import com.tangosol.net.events.Event;
  4. import com.tangosol.net.events.EventDispatcher;
  5. import com.tangosol.net.events.EventInterceptor;
  6. import com.tangosol.net.events.annotation.Interceptor;
  7. import com.tangosol.net.events.internal.NamedEventInterceptor;
  8. import com.tangosol.net.events.partition.PartitionedServiceDispatcher;
  9. import com.tangosol.net.events.partition.cache.PartitionedCacheDispatcher;
  10. import com.tangosol.util.RegistrationBehavior;
  11.  
  12. import java.util.regex.Matcher;
  13. import java.util.regex.Pattern;
  14.  
  15. /**
  16.  * @author Jonathan Knight
  17.  */
  18. public class RegExEventInterceptor<E extends Event<?>> extends NamedEventInterceptor<E>
  19. {
  20.     private Pattern pattern;
  21.  
  22.     public RegExEventInterceptor(String sName, EventInterceptor<E> interceptor,
  23.                                  String cacheName, String serviceName, Interceptor.Order order,
  24.                                  RegistrationBehavior behavior)
  25.     {
  26.         super(sName, interceptor, cacheName, serviceName, order, behavior);
  27.         pattern = Pattern.compile(cacheName);
  28.     }
  29.  
  30.     @Override
  31.     public boolean isAcceptable(EventDispatcher dispatcher)
  32.     {
  33.         String serviceName = getServiceName();
  34.         if (serviceName == null && pattern == null)
  35.         {
  36.             return true;
  37.         }
  38.  
  39.         String dispatcherServiceName = null;
  40.         String dispatcherCacheName = null;
  41.         if (dispatcher instanceof PartitionedCacheDispatcher)
  42.         {
  43.             PartitionedCacheDispatcher bmd = (PartitionedCacheDispatcher) dispatcher;
  44.             dispatcherServiceName = bmd.getBackingMapContext()
  45.                     .getManagerContext().getCacheService().getInfo().getServiceName();
  46.             dispatcherCacheName = bmd.getBackingMapContext().getCacheName();
  47.         }
  48.         else if ((dispatcher instanceof PartitionedServiceDispatcher))
  49.         {
  50.             dispatcherServiceName = ((PartitionedServiceDispatcher) dispatcher).getService()
  51.                     .getInfo().getServiceName();
  52.         }
  53.  
  54.         boolean match = serviceName == null || serviceName.equals(dispatcherServiceName);
  55.         if (match && dispatcherCacheName != null && pattern != null)
  56.         {
  57.             Matcher matcher = pattern.matcher(dispatcherCacheName);
  58.             return matcher.matches();
  59.         }
  60.  
  61.         return false;
  62.     }
  63. }
package com.thegridman.coherence.regex;

import com.tangosol.net.events.Event;
import com.tangosol.net.events.EventDispatcher;
import com.tangosol.net.events.EventInterceptor;
import com.tangosol.net.events.annotation.Interceptor;
import com.tangosol.net.events.internal.NamedEventInterceptor;
import com.tangosol.net.events.partition.PartitionedServiceDispatcher;
import com.tangosol.net.events.partition.cache.PartitionedCacheDispatcher;
import com.tangosol.util.RegistrationBehavior;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Jonathan Knight
 */
public class RegExEventInterceptor<E extends Event<?>> extends NamedEventInterceptor<E>
{
    private Pattern pattern;

    public RegExEventInterceptor(String sName, EventInterceptor<E> interceptor,
                                 String cacheName, String serviceName, Interceptor.Order order, 
                                 RegistrationBehavior behavior)
    {
        super(sName, interceptor, cacheName, serviceName, order, behavior);
        pattern = Pattern.compile(cacheName);
    }

    @Override
    public boolean isAcceptable(EventDispatcher dispatcher)
    {
        String serviceName = getServiceName();
        if (serviceName == null && pattern == null)
        {
            return true;
        }

        String dispatcherServiceName = null;
        String dispatcherCacheName = null;
        if (dispatcher instanceof PartitionedCacheDispatcher)
        {
            PartitionedCacheDispatcher bmd = (PartitionedCacheDispatcher) dispatcher;
            dispatcherServiceName = bmd.getBackingMapContext()
                    .getManagerContext().getCacheService().getInfo().getServiceName();
            dispatcherCacheName = bmd.getBackingMapContext().getCacheName();
        }
        else if ((dispatcher instanceof PartitionedServiceDispatcher))
        {
            dispatcherServiceName = ((PartitionedServiceDispatcher) dispatcher).getService()
                    .getInfo().getServiceName();
        }

        boolean match = serviceName == null || serviceName.equals(dispatcherServiceName);
        if (match && dispatcherCacheName != null && pattern != null)
        {
            Matcher matcher = pattern.matcher(dispatcherCacheName);
            return matcher.matches();
        }

        return false;
    }
}

Again, another simple class where we just extend the default built in class NamedEventInterceptor and override the relevant methods. In this case we only need to override the isAcceptable(EventDispatcher dispatcher) which returns true if this interceptor should respond to events from the given dispatcher.

In our constructor we take the same parameters as the super class and pass them straight through. We then use the cacheName parameter as our regular expression which we compile into a java.util.regex.Pattern for use later in the isAcceptable method.

In the isAcceptable(EventDispatcher dispatcher) we are basically applying a few rules around the service name and cache name provided by the dispatcher then if the cache name from the dispatcher matches our regular expression we return true.

The RegExMappingNamespaceHandler Class

Now we have all of the parts we need to plug them into a NamespaceHandler so that we can use them in the cache configuration XML file. Our RegExMappingNamespaceHandler class is a very simple implementation of a NamespaceHandler.

  1. package com.thegridman.coherence.regex;
  2.  
  3. import com.tangosol.coherence.config.CacheMapping;
  4. import com.tangosol.config.ConfigurationException;
  5. import com.tangosol.config.xml.AbstractNamespaceHandler;
  6. import com.tangosol.config.xml.ElementProcessor;
  7. import com.tangosol.config.xml.ProcessingContext;
  8. import com.tangosol.config.xml.XmlSimpleName;
  9. import com.tangosol.run.xml.XmlElement;
  10.  
  11. /**
  12.  * @author Jonathan Knight
  13.  */
  14. public class RegExMappingNamespaceHandler extends AbstractNamespaceHandler
  15. {
  16.     public static final String XMLTAG_CACHE_MAPPING = "regex-cache-mapping";
  17.  
  18.     public RegExMappingNamespaceHandler()
  19.     {
  20.     }
  21.  
  22.     @XmlSimpleName(value = XMLTAG_CACHE_MAPPING)
  23.     public static class CacheMappingElementProcessor implements ElementProcessor<RegExCacheMapping>
  24.     {
  25.         @SuppressWarnings("unchecked")
  26.         @Override
  27.         public RegExCacheMapping process(ProcessingContext context, XmlElement element) throws ConfigurationException
  28.         {
  29.             String cacheNamePattern = context.getMandatoryProperty("cache-regex", String.class, element);
  30.             String schemeName = context.getMandatoryProperty("scheme-name", String.class, element);
  31.             RegExCacheMapping mapping = new RegExCacheMapping(cacheNamePattern, schemeName);
  32.  
  33.             context.inject(mapping, element);
  34.  
  35.             XmlElement copy = (XmlElement) element.clone();
  36.             copy.setName(element.getQualifiedName().getLocalName());
  37.             context.inject(mapping, copy);
  38.             context.addCookie(CacheMapping.class, mapping);
  39.             return mapping;
  40.         }
  41.     }
  42.  
  43. }
package com.thegridman.coherence.regex;

import com.tangosol.coherence.config.CacheMapping;
import com.tangosol.config.ConfigurationException;
import com.tangosol.config.xml.AbstractNamespaceHandler;
import com.tangosol.config.xml.ElementProcessor;
import com.tangosol.config.xml.ProcessingContext;
import com.tangosol.config.xml.XmlSimpleName;
import com.tangosol.run.xml.XmlElement;

/**
 * @author Jonathan Knight
 */
public class RegExMappingNamespaceHandler extends AbstractNamespaceHandler
{
    public static final String XMLTAG_CACHE_MAPPING = "regex-cache-mapping";

    public RegExMappingNamespaceHandler()
    {
    }

    @XmlSimpleName(value = XMLTAG_CACHE_MAPPING)
    public static class CacheMappingElementProcessor implements ElementProcessor<RegExCacheMapping>
    {
        @SuppressWarnings("unchecked")
        @Override
        public RegExCacheMapping process(ProcessingContext context, XmlElement element) throws ConfigurationException
        {
            String cacheNamePattern = context.getMandatoryProperty("cache-regex", String.class, element);
            String schemeName = context.getMandatoryProperty("scheme-name", String.class, element);
            RegExCacheMapping mapping = new RegExCacheMapping(cacheNamePattern, schemeName);

            context.inject(mapping, element);

            XmlElement copy = (XmlElement) element.clone();
            copy.setName(element.getQualifiedName().getLocalName());
            context.inject(mapping, copy);
            context.addCookie(CacheMapping.class, mapping);
            return mapping;
        }
    }

}

All that our RegExMappingNamespaceHandler class does is contain a static inner class CacheMappingElementProcessor that is the processor for the <regex-cache-mapping> element in our custom namespace. This class is annotated with @XmlSimpleName so Coherence will automatically register it for us.

Our CacheMappingElementProcessor inner class is also pretty simple, it just builds instances of our RegExCacheMapping class. First it reads the <cache-regex> and <scheme-name> elements from within the <regex-cache-mapping> element and passes the values to the constructor of the RegExCacheMapping. We then need to get the configuration framework to inject any other configuration elements that may be present, such as <init-params> or <interceptors>

Using Reg-Ex Cache Mapping In The XML Configuration

So now we have all the parts we can use it in our cache configuration file. Here is an example.

  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:reg="class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler"
  5.              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
  6.                                 class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler regex-cache-config.xsd">
  7.  
  8.     <defaults>
  9.         <serializer>pof</serializer>
  10.     </defaults>
  11.  
  12.     <caching-scheme-mapping>
  13.  
  14.         <cache-mapping>
  15.             <cache-name>dist-jk</cache-name>
  16.             <scheme-name>distributed-scheme</scheme-name>
  17.             <interceptors>
  18.                 <interceptor>
  19.                     <instance>
  20.                         <class-name>com.thegridman.coherence.regex.MyInterceptor</class-name>
  21.                     </instance>
  22.                 </interceptor>
  23.             </interceptors>
  24.         </cache-mapping>
  25.  
  26.         <reg:regex-cache-mapping>
  27.             <reg:cache-regex>(.)*-test$</reg:cache-regex>
  28.             <reg:scheme-name>regex-one-scheme</reg:scheme-name>
  29.             <init-params>
  30.                 <init-param>
  31.                     <param-name>expiry</param-name>
  32.                     <param-value>2s</param-value>
  33.                 </init-param>
  34.             </init-params>
  35.         </reg:regex-cache-mapping>
  36.  
  37.         <reg:regex-cache-mapping>
  38.             <reg:cache-regex>^test-(.)*</reg:cache-regex>
  39.             <reg:scheme-name>regex-two-scheme</reg:scheme-name>
  40.         </reg:regex-cache-mapping>
  41.  
  42.         <reg:regex-cache-mapping>
  43.             <reg:cache-regex>(.)*-test-(.)*</reg:cache-regex>
  44.             <reg:scheme-name>regex-three-scheme</reg:scheme-name>
  45.             <init-params>
  46.                 <init-param>
  47.                     <param-name>expiry</param-name>
  48.                     <param-value>2s</param-value>
  49.                 </init-param>
  50.             </init-params>
  51.             <interceptors>
  52.                 <interceptor>
  53.                     <name>test-interceptor-3</name>
  54.                     <order>HIGH</order>
  55.                     <instance>
  56.                         <class-name>com.thegridman.coherence.regex.MyInterceptor</class-name>
  57.                     </instance>
  58.                 </interceptor>
  59.             </interceptors>
  60.         </reg:regex-cache-mapping>
  61.  
  62.     </caching-scheme-mapping>
  63.  
  64.     <caching-schemes>
  65.  
  66.         <distributed-scheme>
  67.             <scheme-name>regex-one-scheme</scheme-name>
  68.             <service-name>RegExOneDistributedService</service-name>
  69.             <backing-map-scheme>
  70.                 <local-scheme>
  71.                     <expiry-delay>{expiry 0}</expiry-delay>
  72.                 </local-scheme>
  73.             </backing-map-scheme>
  74.             <autostart>true</autostart>
  75.         </distributed-scheme>
  76.  
  77.         <distributed-scheme>
  78.             <scheme-name>regex-two-scheme</scheme-name>
  79.             <service-name>RegExTwoDistributedService</service-name>
  80.             <backing-map-scheme>
  81.                 <local-scheme>
  82.                     <expiry-delay>{expiry 0}</expiry-delay>
  83.                 </local-scheme>
  84.             </backing-map-scheme>
  85.             <autostart>true</autostart>
  86.         </distributed-scheme>
  87.  
  88.         <distributed-scheme>
  89.             <scheme-name>regex-three-scheme</scheme-name>
  90.             <service-name>RegExThreeDistributedService</service-name>
  91.             <backing-map-scheme>
  92.                 <local-scheme>
  93.                     <expiry-delay>{expiry 0}</expiry-delay>
  94.                 </local-scheme>
  95.             </backing-map-scheme>
  96.             <autostart>true</autostart>
  97.         </distributed-scheme>
  98.  
  99.         <distributed-scheme>
  100.             <scheme-name>distributed-scheme</scheme-name>
  101.             <service-name>DistributedService</service-name>
  102.             <backing-map-scheme>
  103.                 <local-scheme/>
  104.             </backing-map-scheme>
  105.             <autostart>true</autostart>
  106.         </distributed-scheme>
  107.  
  108.     </caching-schemes>
  109.  
  110. </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:reg="class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
                                 class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler regex-cache-config.xsd">

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

    <caching-scheme-mapping>

        <cache-mapping>
            <cache-name>dist-jk</cache-name>
            <scheme-name>distributed-scheme</scheme-name>
            <interceptors>
                <interceptor>
                    <instance>
                        <class-name>com.thegridman.coherence.regex.MyInterceptor</class-name>
                    </instance>
                </interceptor>
            </interceptors>
        </cache-mapping>

        <reg:regex-cache-mapping>
            <reg:cache-regex>(.)*-test$</reg:cache-regex>
            <reg:scheme-name>regex-one-scheme</reg:scheme-name>
            <init-params>
                <init-param>
                    <param-name>expiry</param-name>
                    <param-value>2s</param-value>
                </init-param>
            </init-params>
        </reg:regex-cache-mapping>

        <reg:regex-cache-mapping>
            <reg:cache-regex>^test-(.)*</reg:cache-regex>
            <reg:scheme-name>regex-two-scheme</reg:scheme-name>
        </reg:regex-cache-mapping>

        <reg:regex-cache-mapping>
            <reg:cache-regex>(.)*-test-(.)*</reg:cache-regex>
            <reg:scheme-name>regex-three-scheme</reg:scheme-name>
            <init-params>
                <init-param>
                    <param-name>expiry</param-name>
                    <param-value>2s</param-value>
                </init-param>
            </init-params>
            <interceptors>
                <interceptor>
                    <name>test-interceptor-3</name>
                    <order>HIGH</order>
                    <instance>
                        <class-name>com.thegridman.coherence.regex.MyInterceptor</class-name>
                    </instance>
                </interceptor>
            </interceptors>
        </reg:regex-cache-mapping>

    </caching-scheme-mapping>

    <caching-schemes>

        <distributed-scheme>
            <scheme-name>regex-one-scheme</scheme-name>
            <service-name>RegExOneDistributedService</service-name>
            <backing-map-scheme>
                <local-scheme>
                    <expiry-delay>{expiry 0}</expiry-delay>
                </local-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>

        <distributed-scheme>
            <scheme-name>regex-two-scheme</scheme-name>
            <service-name>RegExTwoDistributedService</service-name>
            <backing-map-scheme>
                <local-scheme>
                    <expiry-delay>{expiry 0}</expiry-delay>
                </local-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>

        <distributed-scheme>
            <scheme-name>regex-three-scheme</scheme-name>
            <service-name>RegExThreeDistributedService</service-name>
            <backing-map-scheme>
                <local-scheme>
                    <expiry-delay>{expiry 0}</expiry-delay>
                </local-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>

        <distributed-scheme>
            <scheme-name>distributed-scheme</scheme-name>
            <service-name>DistributedService</service-name>
            <backing-map-scheme>
                <local-scheme/>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>

    </caching-schemes>

</cache-config>

We have declared our new namespace to use the reg: prefix mapped to namespace class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler which is the name of our handler class.
In our example we can see a normal Coherence mapping on line 14 which maps to the exact cache name dist-jk.
On line 26 we have one of our new reg-ex mapping which maps to the expression (.)*-test$ – which is any cache name ending in -test. We specify the reg-ex in the <cache-regex> and scheme name in the <scheme-name> elements both prefixed with the namespace reg: prefix. You can see that this mapping also has init-params to pass through to the scheme, these are normal Coherence elements so do not need the reg: prefix.
On line 37 we have a second reg-ex mapping which uses the expression ^test-(.)* that maps to the regex-two-scheme scheme. This will match any cache name that starts with test- so is equivalent to the normal coherence test-* mapping.
On line 42 we have a third reg-ex mapping that uses the expression (.)*-test-(.)* to map caches to the regex-three-scheme scheme. This will match any cache name that contains -test- somewhere in it. This mapping also has init-params like the first one and also has interceptors specified.

Custom XSD File

The eagle eyed of you will have spotted that we have included a custom XSD file in the schemaLocation at the top of the configuration.

  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:reg="class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler"
  5.              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
  6.                                 class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler regex-cache-config.xsd">
<?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:reg="class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
                                 class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler regex-cache-config.xsd">

It is a good idea to create an XSD file to match you custom namespace as this allows the XML to be validated both at runtime and in the developers IDE and helps detect bugs earlier. The XSD for our new namespace looks like this

  1. <?xml version="1.0"?>
  2. <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  3.            targetNamespace="class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler"
  4.            xmlns="class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler"
  5.            xmlns:coh="http://xmlns.oracle.com/coherence/coherence-cache-config"
  6.            elementFormDefault="qualified"
  7.            attributeFormDefault="unqualified"
  8.            version="1.2">
  9.  
  10.     <xsd:import namespace="http://xmlns.oracle.com/coherence/coherence-cache-config"
  11.                schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd"/>
  12.  
  13.  
  14.     <xsd:element name="regex-cache-mapping" type="regex-cache-mapping-type"/>
  15.  
  16.  
  17.     <xsd:complexType name="regex-cache-mapping-type">
  18.         <xsd:sequence>
  19.             <xsd:element ref="cache-regex" />
  20.             <xsd:element ref="scheme-name" />
  21.             <xsd:element ref="coh:init-params" minOccurs="0" />
  22.             <xsd:element ref="coh:interceptors" minOccurs="0" />
  23.         </xsd:sequence>
  24.         <xsd:anyAttribute namespace="##other" processContents="lax"/>
  25.     </xsd:complexType>
  26.  
  27.     <xsd:element name="cache-regex" type="coh:coherence-string-type"/>
  28.  
  29.     <xsd:element name="scheme-name" type="coh:coherence-string-type"/>
  30.  
  31. </xsd:schema>
<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler"
            xmlns="class://com.thegridman.coherence.regex.RegExMappingNamespaceHandler"
            xmlns:coh="http://xmlns.oracle.com/coherence/coherence-cache-config"
            elementFormDefault="qualified"
            attributeFormDefault="unqualified"
            version="1.2">

    <xsd:import namespace="http://xmlns.oracle.com/coherence/coherence-cache-config"
                schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd"/>


    <xsd:element name="regex-cache-mapping" type="regex-cache-mapping-type"/>


    <xsd:complexType name="regex-cache-mapping-type">
        <xsd:sequence>
            <xsd:element ref="cache-regex" />
            <xsd:element ref="scheme-name" />
            <xsd:element ref="coh:init-params" minOccurs="0" />
            <xsd:element ref="coh:interceptors" minOccurs="0" />
        </xsd:sequence>
        <xsd:anyAttribute namespace="##other" processContents="lax"/>
    </xsd:complexType>

    <xsd:element name="cache-regex" type="coh:coherence-string-type"/>

    <xsd:element name="scheme-name" type="coh:coherence-string-type"/>

</xsd:schema>

So if you are using Coherence 12.1.2 that is all you need to do to use regular expression cache mappings. It is only a handful of classes and makes use of the correct way to extend Coherence functionality.

Coherence 3.7.1 Implementation

If you have not upgraded to 12.1.2 yet – what are you waiting for!!! Seriously though, I know a lot of projects have not upgraded. At my current client we are still on 3.7.1 and having a big debate about when to go to 12.1.2 and what it will involve.

So, getting regular expression mapping to work in 3.7.1 (and also earlier versions) is also very straight forward as we only need a single class but it is not as elegant as the 12.1.2 solution.

We will make the 3.7.1 version work with exactly the same XML configuration elements and a custom namespace just as the 12.1.2 version. In this case though we will not have all of the custom namespace code, just an XSD file.

Custom ConfigurableCacheFactory Class

In Coherence 3.7.1 and earlier there is no clever configuration framework like there is in 12.1.2. Basically the ConfigurableCacheFactory implementation just uses the cache configuration XML directly to work out what to do. This means all we need to do is create a custom ConfigurableCacheFactory by extending DefaultConfigurableCacheFactory and override two methods.

  1. package com.thegridman.coherence.regex;
  2.  
  3. import com.tangosol.net.DefaultConfigurableCacheFactory;
  4. import com.tangosol.run.xml.XmlElement;
  5. import com.tangosol.run.xml.XmlHelper;
  6.  
  7. import java.util.HashMap;
  8. import java.util.Iterator;
  9. import java.util.LinkedHashMap;
  10. import java.util.Map;
  11. import java.util.regex.Matcher;
  12. import java.util.regex.Pattern;
  13.  
  14. /**
  15.  * @author Jonathan Knight
  16.  */
  17. public class RegExConfigurableCacheFactory extends DefaultConfigurableCacheFactory
  18. {
  19.     public static final String NAMESPACE_URI = "http://xmlns.oracle.com/coherence/regex-coherence-cache-config";
  20.  
  21.     private Map<XmlElement,Pattern> cacheMappingPatterns;
  22.     private Map<String,XmlElement> exactCacheMappings;
  23.     private String namespacePrefix;
  24.  
  25.     public RegExConfigurableCacheFactory()
  26.     {
  27.     }
  28.  
  29.     public RegExConfigurableCacheFactory(String sPath)
  30.     {
  31.         super(sPath);
  32.     }
  33.  
  34.     public RegExConfigurableCacheFactory(String sPath, ClassLoader loader)
  35.     {
  36.         super(sPath, loader);
  37.     }
  38.  
  39.     public RegExConfigurableCacheFactory(XmlElement xmlConfig)
  40.     {
  41.         super(xmlConfig);
  42.     }
  43.  
  44.     public RegExConfigurableCacheFactory(XmlElement xmlConfig, ClassLoader loader)
  45.     {
  46.         super(xmlConfig, loader);
  47.     }
  48.  
  49.     @SuppressWarnings("unchecked")
  50.     @Override
  51.     public void setConfig(XmlElement xmlConfig)
  52.     {
  53.         super.setConfig(xmlConfig);
  54.         namespacePrefix = XmlHelper.getNamespacePrefix(xmlConfig, NAMESPACE_URI);
  55.         cacheMappingPatterns = new LinkedHashMap<>();
  56.         Iterator<XmlElement> regExMappingIterator =
  57.                 xmlConfig.getSafeElement("caching-scheme-mapping")
  58.                         .getElements(namespacePrefix + ":regex-cache-mapping");
  59.  
  60.         while (regExMappingIterator.hasNext())
  61.         {
  62.             XmlElement mappingElement = regExMappingIterator.next();
  63.             String regEx = mappingElement.getSafeElement(namespacePrefix + ":cache-regex").getString();
  64.             Pattern pattern = Pattern.compile(regEx);
  65.             cacheMappingPatterns.put(mappingElement, pattern);
  66.         }
  67.  
  68.         exactCacheMappings = new LinkedHashMap<>();
  69.         Iterator<XmlElement> mappingIterator =
  70.                 xmlConfig.getSafeElement("caching-scheme-mapping")
  71.                     .getElements("cache-mapping");
  72.  
  73.         while (mappingIterator.hasNext())
  74.         {
  75.             XmlElement mappingElement = mappingIterator.next();
  76.             String cacheName = mappingElement.getSafeElement("cache-name").getString();
  77.             exactCacheMappings.put(cacheName, mappingElement);
  78.         }
  79.     }
  80.  
  81.     @SuppressWarnings("unchecked")
  82.     @Override
  83.     public CacheInfo findSchemeMapping(String cacheName)
  84.     {
  85.         XmlElement match = null;
  86.         String schemeName = null;
  87.         if (exactCacheMappings.containsKey(cacheName))
  88.         {
  89.             match = exactCacheMappings.get(cacheName);
  90.             schemeName = match.getSafeElement("scheme-name").getString();
  91.         }
  92.         else
  93.         {
  94.             for (Map.Entry<XmlElement,Pattern> mappingEntry : cacheMappingPatterns.entrySet())
  95.             {
  96.                 Matcher matcher = mappingEntry.getValue().matcher(cacheName);
  97.                 if (matcher.matches())
  98.                 {
  99.                     match = mappingEntry.getKey();
  100.                     schemeName = match.getSafeElement(namespacePrefix + ":scheme-name").getString();
  101.                 }
  102.             }
  103.         }
  104.  
  105.         if (match == null)
  106.         {
  107.             throw new IllegalArgumentException("No scheme for cache: \"" + cacheName + '"');
  108.         }
  109.  
  110.         Map initParamMap = new HashMap();
  111.         Iterator<XmlElement> initParamIterator = match.getSafeElement("init-params").getElements("init-param");
  112.         while(initParamIterator.hasNext())
  113.         {
  114.             XmlElement initParamElement = initParamIterator.next();
  115.             String paramName = initParamElement.getSafeElement("param-name").getString();
  116.             if (paramName.length() > 0)
  117.             {
  118.                 String paramValue = initParamElement.getSafeElement("param-value").getString();
  119.                 initParamMap.put(paramName, paramValue);
  120.             }
  121.         }
  122.  
  123.         return new CacheInfo(cacheName, schemeName, initParamMap);
  124.     }
  125.  
  126. }
package com.thegridman.coherence.regex;

import com.tangosol.net.DefaultConfigurableCacheFactory;
import com.tangosol.run.xml.XmlElement;
import com.tangosol.run.xml.XmlHelper;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Jonathan Knight
 */
public class RegExConfigurableCacheFactory extends DefaultConfigurableCacheFactory
{
    public static final String NAMESPACE_URI = "http://xmlns.oracle.com/coherence/regex-coherence-cache-config";

    private Map<XmlElement,Pattern> cacheMappingPatterns;
    private Map<String,XmlElement> exactCacheMappings;
    private String namespacePrefix;

    public RegExConfigurableCacheFactory()
    {
    }

    public RegExConfigurableCacheFactory(String sPath)
    {
        super(sPath);
    }

    public RegExConfigurableCacheFactory(String sPath, ClassLoader loader)
    {
        super(sPath, loader);
    }

    public RegExConfigurableCacheFactory(XmlElement xmlConfig)
    {
        super(xmlConfig);
    }

    public RegExConfigurableCacheFactory(XmlElement xmlConfig, ClassLoader loader)
    {
        super(xmlConfig, loader);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void setConfig(XmlElement xmlConfig)
    {
        super.setConfig(xmlConfig);
        namespacePrefix = XmlHelper.getNamespacePrefix(xmlConfig, NAMESPACE_URI);
        cacheMappingPatterns = new LinkedHashMap<>();
        Iterator<XmlElement> regExMappingIterator =
                xmlConfig.getSafeElement("caching-scheme-mapping")
                        .getElements(namespacePrefix + ":regex-cache-mapping");

        while (regExMappingIterator.hasNext())
        {
            XmlElement mappingElement = regExMappingIterator.next();
            String regEx = mappingElement.getSafeElement(namespacePrefix + ":cache-regex").getString();
            Pattern pattern = Pattern.compile(regEx);
            cacheMappingPatterns.put(mappingElement, pattern);
        }

        exactCacheMappings = new LinkedHashMap<>();
        Iterator<XmlElement> mappingIterator =
                xmlConfig.getSafeElement("caching-scheme-mapping")
                    .getElements("cache-mapping");

        while (mappingIterator.hasNext())
        {
            XmlElement mappingElement = mappingIterator.next();
            String cacheName = mappingElement.getSafeElement("cache-name").getString();
            exactCacheMappings.put(cacheName, mappingElement);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public CacheInfo findSchemeMapping(String cacheName)
    {
        XmlElement match = null;
        String schemeName = null;
        if (exactCacheMappings.containsKey(cacheName))
        {
            match = exactCacheMappings.get(cacheName);
            schemeName = match.getSafeElement("scheme-name").getString();
        }
        else
        {
            for (Map.Entry<XmlElement,Pattern> mappingEntry : cacheMappingPatterns.entrySet())
            {
                Matcher matcher = mappingEntry.getValue().matcher(cacheName);
                if (matcher.matches())
                {
                    match = mappingEntry.getKey();
                    schemeName = match.getSafeElement(namespacePrefix + ":scheme-name").getString();
                }
            }
        }

        if (match == null)
        {
            throw new IllegalArgumentException("No scheme for cache: \"" + cacheName + '"');
        }

        Map initParamMap = new HashMap();
        Iterator<XmlElement> initParamIterator = match.getSafeElement("init-params").getElements("init-param");
        while(initParamIterator.hasNext())
        {
            XmlElement initParamElement = initParamIterator.next();
            String paramName = initParamElement.getSafeElement("param-name").getString();
            if (paramName.length() > 0)
            {
                String paramValue = initParamElement.getSafeElement("param-value").getString();
                initParamMap.put(paramName, paramValue);
            }
        }

        return new CacheInfo(cacheName, schemeName, initParamMap);
    }

}

That is the code for the whole class. We have written the same constructors as DefaultConfigurableCacheFactory and overridden the setConfig(XmlElement xmlConfig) and findSchemeMapping(String cacheName) methods.

Our setConfig(XmlElement xmlConfig) method first calls the super.setConfig(XmlElement xmlConfig) method and then needs to go through the mappings defined in the configuration. As I said already, there is no clever configuration framework, we just need to look at the XML. First we create a Map of our reg-ex mappings in the cacheMappingPatterns field. The key of the map is the mapping XMLElement and the value of the map is the compiled reg-ex Pattern.

Next the setConfig(XmlElement xmlConfig) method will create a Map of the normal Coherence cache mappings stored in the exactCacheMappings field. This time the key of the map is the value of the <cache-name> element and the value is the mapping XMLElement.

The findSchemeMapping(String cacheName) is where we have to do the work to find the scheme mapping. First we check to see if there is an exact match to the specified cache name and if there is we use that XMLElement for the mapping. If there is no exact match we go through the reg-ex mappings to see if any of them match and if so use that XMLElement for the mapping. If neither match we throw an exception just as normal Coherence would.

If we find a match we create a CacheInfo and include any <init-params> in the mapping element. The CacheInfo is then returned from the method.

Override File

To use our custom ConfigurableCacheFactory class we need to tell Coherence about it. This is usually done by adding it to the tangosol-coherence-override.xml file like this.

  1. <coherence>
  2.     <configurable-cache-factory-config>
  3.         <class-name>com.thegridman.coherence.regex.RegExConfigurableCacheFactory</class-name>
  4.         <init-params>
  5.             <init-param>
  6.                 <param-type>java.lang.String</param-type>
  7.                 <param-value system-property="tangosol.coherence.cacheconfig">coherence-cache-config.xml</param-value>
  8.             </init-param>
  9.         </init-params>
  10.     </configurable-cache-factory-config>
  11. </coherence>
<coherence>
    <configurable-cache-factory-config>
        <class-name>com.thegridman.coherence.regex.RegExConfigurableCacheFactory</class-name>
        <init-params>
            <init-param>
                <param-type>java.lang.String</param-type>
                <param-value system-property="tangosol.coherence.cacheconfig">coherence-cache-config.xml</param-value>
            </init-param>
        </init-params>
    </configurable-cache-factory-config>
</coherence>

There are other ways of using a custom ConfigurableCacheFactory such as a custom builder or just new one up, but changing the tangosol-coherence-override.xml file is probably most common.

3.7.1 Custom XSD

We need to specify a custom XSD namespace declared in the cache configuration file to make this code work. The XSD file looks similar to the 12.1.2 version.

  1. <?xml version="1.0"?>
  2. <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  3.            targetNamespace="http://xmlns.oracle.com/coherence/regex-coherence-cache-config"
  4.            xmlns="http://xmlns.oracle.com/coherence/regex-coherence-cache-config"
  5.            xmlns:coh="http://xmlns.oracle.com/coherence/coherence-cache-config"
  6.            elementFormDefault="qualified"
  7.            attributeFormDefault="unqualified"
  8.            version="1.2">
  9.  
  10.     <xsd:import namespace="http://xmlns.oracle.com/coherence/coherence-cache-config"
  11.                schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd"/>
  12.  
  13.     <xsd:element name="regex-cache-mapping" type="regex-cache-mapping-type"/>
  14.  
  15.     <xsd:complexType name="regex-cache-mapping-type">
  16.         <xsd:sequence>
  17.             <xsd:element ref="cache-regex" />
  18.             <xsd:element ref="scheme-name" />
  19.             <xsd:element ref="coh:init-params" minOccurs="0" />
  20.         </xsd:sequence>
  21.         <xsd:anyAttribute namespace="##other" processContents="lax"/>
  22.     </xsd:complexType>
  23.  
  24.     <xsd:element name="cache-regex" type="coh:coherence-string-type"/>
  25.  
  26.     <xsd:element name="scheme-name" type="coh:coherence-string-type"/>
  27.  
  28. </xsd:schema>
<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://xmlns.oracle.com/coherence/regex-coherence-cache-config"
            xmlns="http://xmlns.oracle.com/coherence/regex-coherence-cache-config"
            xmlns:coh="http://xmlns.oracle.com/coherence/coherence-cache-config"
            elementFormDefault="qualified"
            attributeFormDefault="unqualified"
            version="1.2">

    <xsd:import namespace="http://xmlns.oracle.com/coherence/coherence-cache-config"
                schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd"/>

    <xsd:element name="regex-cache-mapping" type="regex-cache-mapping-type"/>

    <xsd:complexType name="regex-cache-mapping-type">
        <xsd:sequence>
            <xsd:element ref="cache-regex" />
            <xsd:element ref="scheme-name" />
            <xsd:element ref="coh:init-params" minOccurs="0" />
        </xsd:sequence>
        <xsd:anyAttribute namespace="##other" processContents="lax"/>
    </xsd:complexType>

    <xsd:element name="cache-regex" type="coh:coherence-string-type"/>

    <xsd:element name="scheme-name" type="coh:coherence-string-type"/>

</xsd:schema>

Using Reg-Ex Cache Mapping In The XML Configuration

Using the reg-ex cache mapping in a 3.7.1 cache configuration file is just like it was in 12.1.2 but with a slightly different namespace declaration at the top.

  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:reg="http://xmlns.oracle.com/coherence/regex-coherence-cache-config"
  5.              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
  6.                                 http://xmlns.oracle.com/coherence/regex-coherence-cache-config regex-cache-config.xsd">
  7.  
  8.     <defaults>
  9.         <serializer>pof</serializer>
  10.     </defaults>
  11.  
  12.     <caching-scheme-mapping>
  13.  
  14.         <cache-mapping>
  15.             <cache-name>dist-test</cache-name>
  16.             <scheme-name>my-distributed-scheme</scheme-name>
  17.         </cache-mapping>
  18.  
  19.         <reg:regex-cache-mapping>
  20.             <reg:cache-regex>dist-(.)*</reg:cache-regex>
  21.             <reg:scheme-name>regex-scheme-one</reg:scheme-name>
  22.             <init-params>
  23.                 <init-param>
  24.                     <param-name>expiry</param-name>
  25.                     <param-value>1d</param-value>
  26.                 </init-param>
  27.                 <init-param>
  28.                     <param-name>test-param</param-name>
  29.                     <param-value>test-value</param-value>
  30.                 </init-param>
  31.             </init-params>
  32.         </reg:regex-cache-mapping>
  33.  
  34.         <reg:regex-cache-mapping>
  35.             <reg:cache-regex>(.)*-test$</reg:cache-regex>
  36.             <reg:scheme-name>regex-scheme-two</reg:scheme-name>
  37.         </reg:regex-cache-mapping>
  38.  
  39.         <reg:regex-cache-mapping>
  40.             <reg:cache-regex>dist-(.)*-test$</reg:cache-regex>
  41.             <reg:scheme-name>regex-scheme-three</reg:scheme-name>
  42.         </reg:regex-cache-mapping>
  43.  
  44.     </caching-scheme-mapping>
  45.  
  46.     <caching-schemes>
  47.  
  48.         <distributed-scheme>
  49.             <scheme-name>my-distributed-scheme</scheme-name>
  50.             <service-name>DistributedService</service-name>
  51.             <backing-map-scheme>
  52.                 <local-scheme/>
  53.             </backing-map-scheme>
  54.             <autostart>true</autostart>
  55.         </distributed-scheme>
  56.  
  57.         <distributed-scheme>
  58.             <scheme-name>regex-scheme-one</scheme-name>
  59.             <service-name>DistributedRegExOneService</service-name>
  60.             <backing-map-scheme>
  61.                 <local-scheme>
  62.                     <expiry-delay>{expiry 0}</expiry-delay>
  63.                 </local-scheme>
  64.             </backing-map-scheme>
  65.             <autostart>true</autostart>
  66.         </distributed-scheme>
  67.  
  68.         <distributed-scheme>
  69.             <scheme-name>regex-scheme-two</scheme-name>
  70.             <service-name>DistributedRegExTwoService</service-name>
  71.             <backing-map-scheme>
  72.                 <local-scheme/>
  73.             </backing-map-scheme>
  74.             <autostart>true</autostart>
  75.         </distributed-scheme>
  76.  
  77.         <distributed-scheme>
  78.             <scheme-name>regex-scheme-three</scheme-name>
  79.             <service-name>DistributedRegExThreeService</service-name>
  80.             <backing-map-scheme>
  81.                 <local-scheme/>
  82.             </backing-map-scheme>
  83.             <autostart>true</autostart>
  84.         </distributed-scheme>
  85.  
  86.     </caching-schemes>
  87.  
  88. </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:reg="http://xmlns.oracle.com/coherence/regex-coherence-cache-config"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd
                                 http://xmlns.oracle.com/coherence/regex-coherence-cache-config regex-cache-config.xsd">

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

    <caching-scheme-mapping>

        <cache-mapping>
            <cache-name>dist-test</cache-name>
            <scheme-name>my-distributed-scheme</scheme-name>
        </cache-mapping>

        <reg:regex-cache-mapping>
            <reg:cache-regex>dist-(.)*</reg:cache-regex>
            <reg:scheme-name>regex-scheme-one</reg:scheme-name>
            <init-params>
                <init-param>
                    <param-name>expiry</param-name>
                    <param-value>1d</param-value>
                </init-param>
                <init-param>
                    <param-name>test-param</param-name>
                    <param-value>test-value</param-value>
                </init-param>
            </init-params>
        </reg:regex-cache-mapping>

        <reg:regex-cache-mapping>
            <reg:cache-regex>(.)*-test$</reg:cache-regex>
            <reg:scheme-name>regex-scheme-two</reg:scheme-name>
        </reg:regex-cache-mapping>

        <reg:regex-cache-mapping>
            <reg:cache-regex>dist-(.)*-test$</reg:cache-regex>
            <reg:scheme-name>regex-scheme-three</reg:scheme-name>
        </reg:regex-cache-mapping>

    </caching-scheme-mapping>

    <caching-schemes>

        <distributed-scheme>
            <scheme-name>my-distributed-scheme</scheme-name>
            <service-name>DistributedService</service-name>
            <backing-map-scheme>
                <local-scheme/>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>

        <distributed-scheme>
            <scheme-name>regex-scheme-one</scheme-name>
            <service-name>DistributedRegExOneService</service-name>
            <backing-map-scheme>
                <local-scheme>
                    <expiry-delay>{expiry 0}</expiry-delay>
                </local-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>

        <distributed-scheme>
            <scheme-name>regex-scheme-two</scheme-name>
            <service-name>DistributedRegExTwoService</service-name>
            <backing-map-scheme>
                <local-scheme/>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>

        <distributed-scheme>
            <scheme-name>regex-scheme-three</scheme-name>
            <service-name>DistributedRegExThreeService</service-name>
            <backing-map-scheme>
                <local-scheme/>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>

    </caching-schemes>

</cache-config>

You can see on lines 4 and 6 that the namespace declaration uses a proper http: URI rather than the class: prefix used in 12.1.2, other than that it is identical to use.

Caveats and Gotchas

There are a few things to bear in mind when using the two implementations above regardless of the Coherence version.

  • It is best not to mix reg-ex mappings with normal Coherence wild card mapping that end with an asterisk. In the 3.7.1 version Coherence wild cards are not supported anyway. It is easy enough to do the same thing as an asterisk in a regular expression anyway, just put (.)* at then end of the reg-ex, for example a Coherence mapping of replicated-* would be replicated-(.)* in reg-ex mapping.
  • If the reg-ex expressions get complicated it could be difficult to work out which might apply if a developer is reading the cache configuration file and it would also be hard to work out which reg-ex might be the more specific mapping when working out what order to put the mappings into the file.

Challenge Met!

There we go then, the challenge of implementing regular expression cache mapping has been met.
The code for this is all on GitHub. I have decided to create a repo for all my blog code here https://github.com/thegridman/Coherence-Blog-Stuff

– enjoy.

You may also like...

Leave a Reply

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