Coherence 12.1.2 – You Don’t Need That Cache Config XML File

No XML

As I alluded to in my previous blog on Coherence 12.1.2 Custom Namespaces and Oracle NoSQL, you can now run Coherence without requiring a cache configuration XML file. Just for some fun, this post is going to show a very quick demo of how to do this.

Coherence 12.1.2 has had a major refactoring of how the configuration files are processed. The new code uses a combination of classes that can handle XML (if you are familiar with the namespaces code from the older Coherence Incubator then this will look very similar), classes that know how to configure stuff called schemes and classes that build stuff, called builders. All of these work together to build a cache configuration and hence a cache factory. What this means though is that we can now bypass all the XML processing code and use the scheme and builder classes directly to build a cache factory. Once we have a cache factory, we could use it in client code, or we could pass it to a DefaultCacheServer to run a normal server cluster member.

The Cache Configuration File

For this demo I am going to run a cluster that would have the following cache configuration XML – but actually we will have no XML.

  1. <?xml version="1.0"?>
  2. <cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.              xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
  4.              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">
  5.  
  6.     <defaults>
  7.         <serializer>pof</serializer>
  8.     </defaults>
  9.  
  10.     <caching-scheme-mapping>
  11.  
  12.         <cache-mapping>
  13.             <cache-name>dist-*</cache-name>
  14.             <scheme-name>my-distributed-scheme</scheme-name>
  15.         </cache-mapping>
  16.  
  17.     </caching-scheme-mapping>
  18.  
  19.     <caching-schemes>
  20.  
  21.         <distributed-scheme>
  22.             <scheme-name>my-distributed-scheme</scheme-name>
  23.             <service-name>MyDistributedService</service-name>
  24.             <backing-map-scheme>
  25.               <read-write-backing-map-scheme>
  26.                 <internal-cache-scheme>
  27.                   <local-scheme>
  28.                       <expiry-delay>10s</expiry-delay>
  29.                   </local-scheme>
  30.                 </internal-cache-scheme>
  31.                 <cachestore-scheme>
  32.                   <class-scheme>
  33.                     <class-name>com.thegridman.coherence.noxml.NoXmlDemo$MyCacheStore</class-name>
  34.                       <init-params>
  35.                           <init-param>
  36.                               <param-type>java.lang.String</param-type>
  37.                               <param-value>{cache-name}</param-value>
  38.                           </init-param>
  39.                       </init-params>
  40.                   </class-scheme>
  41.                 </cachestore-scheme>
  42.               </read-write-backing-map-scheme>
  43.             </backing-map-scheme>
  44.         </distributed-scheme>
  45.  
  46.         <proxy-scheme>
  47.           <scheme-name>my-proxy-scheme</scheme-name>
  48.           <service-name>TcpProxyService</service-name>
  49.           <acceptor-config>
  50.             <tcp-acceptor>
  51.               <local-address>
  52.                 <address>localhost</address>
  53.                 <port>9099</port>
  54.               </local-address>
  55.             </tcp-acceptor>
  56.           </acceptor-config>
  57.           <autostart>true</autostart>
  58.         </proxy-scheme>
  59.  
  60.     </caching-schemes>
  61.  
  62. </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"
              xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-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>MyDistributedService</service-name>
            <backing-map-scheme>
              <read-write-backing-map-scheme>
                <internal-cache-scheme>
                  <local-scheme>
                      <expiry-delay>10s</expiry-delay>
                  </local-scheme>
                </internal-cache-scheme>
                <cachestore-scheme>
                  <class-scheme>
                    <class-name>com.thegridman.coherence.noxml.NoXmlDemo$MyCacheStore</class-name>
                      <init-params>
                          <init-param>
                              <param-type>java.lang.String</param-type>
                              <param-value>{cache-name}</param-value>
                          </init-param>
                      </init-params>
                  </class-scheme>
                </cachestore-scheme>
              </read-write-backing-map-scheme>
            </backing-map-scheme>
        </distributed-scheme>

        <proxy-scheme>
          <scheme-name>my-proxy-scheme</scheme-name>
          <service-name>TcpProxyService</service-name>
          <acceptor-config>
            <tcp-acceptor>
              <local-address>
                <address>localhost</address>
                <port>9099</port>
              </local-address>
            </tcp-acceptor>
          </acceptor-config>
          <autostart>true</autostart>
        </proxy-scheme>

    </caching-schemes>

</cache-config>

The configuration above is very simple, but this is just a demo, you can get as complicated as you like if you want to try this yourself.

Doing It All In Code…

To make it easier to understand we will start bottom up by creating the service configurations first.

The local-scheme

The first thing we need is the inner local-scheme for the distributed-scheme. If you look at the configuration above, all we have added to this local scheme is an expiry-delay of 10 seconds. Here is the code…

  1. LocalScheme localScheme = new LocalScheme();
  2. localScheme.setExpiryDelay(new LiteralExpression<Seconds>(new Seconds(10)));
LocalScheme localScheme = new LocalScheme();
localScheme.setExpiryDelay(new LiteralExpression<Seconds>(new Seconds(10)));

It couldn’t really be much simpler. If you look at the com.tangosol.coherence.config.scheme.LocalScheme class you will see that it has various methods on it for setting the different things you would normally add in the configuration file.

The cachestore-scheme

Next you will see that our distributed-scheme contains a read-write-backing-map that contains a CacheStore. The cache store class is called com.thegridman.coherence.noxml.MyCacheStore which is a simple cache store that does nothing but some logging for the purposes of our demo.The class could be any cache store at all, you just need to create an instance of it. Here is the code for the cache store…

  1. package com.thegridman.coherence.noxml;
  2.  
  3. import com.tangosol.net.cache.CacheStore;
  4.  
  5. import java.util.Collection;
  6. import java.util.Map;
  7.  
  8. /**
  9.  * @author Jonathan Knight
  10.  */
  11. public class MyCacheStore implements CacheStore
  12. {
  13.     private String cacheName;
  14.  
  15.     public MyCacheStore(String cacheName)
  16.     {
  17.         this.cacheName = cacheName;
  18.     }
  19.  
  20.     public void store(Object key, Object value)
  21.     {
  22.         System.err.println(cacheName + " store called key=" + key + " value=" + value);
  23.     }
  24.  
  25.     public void storeAll(Map entries)
  26.     {
  27.         System.err.println(cacheName + " storeAll called entries=" + entries);
  28.     }
  29.  
  30.     public Object load(Object key)
  31.     {
  32.         System.err.println(cacheName + " load called key=" + key);
  33.         return null;
  34.     }
  35.  
  36.     public Map loadAll(Collection keys)
  37.     {
  38.         System.err.println(cacheName + " loadAll called keys=" + keys);
  39.         return null;
  40.     }
  41.  
  42.     public void erase(Object key)
  43.     {
  44.         System.err.println(cacheName + " erase called key=" + key);
  45.     }
  46.  
  47.     public void eraseAll(Collection keys)
  48.     {
  49.         System.err.println(cacheName + " eraseAll called keys=" + keys);
  50.     }
  51. }
package com.thegridman.coherence.noxml;

import com.tangosol.net.cache.CacheStore;

import java.util.Collection;
import java.util.Map;

/**
 * @author Jonathan Knight
 */
public class MyCacheStore implements CacheStore
{
    private String cacheName;

    public MyCacheStore(String cacheName)
    {
        this.cacheName = cacheName;
    }

    public void store(Object key, Object value)
    {
        System.err.println(cacheName + " store called key=" + key + " value=" + value);
    }

    public void storeAll(Map entries)
    {
        System.err.println(cacheName + " storeAll called entries=" + entries);
    }

    public Object load(Object key)
    {
        System.err.println(cacheName + " load called key=" + key);
        return null;
    }

    public Map loadAll(Collection keys)
    {
        System.err.println(cacheName + " loadAll called keys=" + keys);
        return null;
    }

    public void erase(Object key)
    {
        System.err.println(cacheName + " erase called key=" + key);
    }

    public void eraseAll(Collection keys)
    {
        System.err.println(cacheName + " eraseAll called keys=" + keys);
    }
}

You can see from the sample XML above and from the cache store code that our cache store requires a constructor parameter, which is actually a Coherence configuration macro, in this case {cache-name} which will resolve to the name of the cache that the cache store is being created for.

So, how do we create all of that, we will start with the init-params part. Parameters are create as a ParameterList and each parameter is added to the list like this…

  1. ParameterList parameterList = new ResolvableParameterList();
  2. Expression<?> expression = new ParameterMacroExpression<>("{cache-name}", String.class);
  3. parameterList.add(new Parameter("cache-name", String.class, expression));
ParameterList parameterList = new ResolvableParameterList();
Expression<?> expression = new ParameterMacroExpression<>("{cache-name}", String.class);
parameterList.add(new Parameter("cache-name", String.class, expression));

First we create the ParameterList. Next we create the parameter, in this case it is a macro so we use a ParameterMacroExpression class whose constructor takes the macro text and Class type as parameters. Finally we add the expression to the ParameterList.

There are various other types of Expression class we could use, depending on what we wanted to do, any decent IDE should show you the classes that implement com.tangosol.config.expression.Expression and you can deduce what they do and how you could use them, for example, com.tangosol.config.expression.LiteralExpression is fairly obviously for literal values.

The read-write-backing-map-scheme

Now we have the local-scheme and cachestore-scheme parts we can use them to create the read-write-backing-map-scheme. You can probably see a pattern forming now, and yes indeed there is a scheme called com.tangosol.coherence.config.scheme.ReadWriteBackingMapScheme that will hold the configuration of our read-write-backing-map-scheme.

  1. ReadWriteBackingMapScheme readWriteBackingMapScheme = new ReadWriteBackingMapScheme();
  2. readWriteBackingMapScheme.setCacheStoreScheme(cacheStoreScheme);
  3. readWriteBackingMapScheme.setInternalScheme(localScheme);
ReadWriteBackingMapScheme readWriteBackingMapScheme = new ReadWriteBackingMapScheme();
readWriteBackingMapScheme.setCacheStoreScheme(cacheStoreScheme);
readWriteBackingMapScheme.setInternalScheme(localScheme);

Our read-write-backing-map-scheme is very simple, we just add the LocalScheme and CacheStoreScheme that we have already created.

The distributed-scheme

Finally we will create the distributed-scheme and yet again we use the relevant scheme class.

  1. DistributedScheme distributedScheme = new DistributedScheme();
  2. distributedScheme.setSchemeName("my-distributed-scheme");
  3. distributedScheme.setServiceName("MyDistributedService");
  4. distributedScheme.setXml(new SimpleElement("distributed-scheme"));
  5. distributedScheme.setAutoStart(true);
  6.  
  7. BackingMapScheme backingMapScheme = new BackingMapScheme();
  8. backingMapScheme.setInnerScheme(readWriteBackingMapScheme);
  9. distributedScheme.setBackingMapScheme(backingMapScheme);
DistributedScheme distributedScheme = new DistributedScheme();
distributedScheme.setSchemeName("my-distributed-scheme");
distributedScheme.setServiceName("MyDistributedService");
distributedScheme.setXml(new SimpleElement("distributed-scheme"));
distributedScheme.setAutoStart(true);

BackingMapScheme backingMapScheme = new BackingMapScheme();
backingMapScheme.setInnerScheme(readWriteBackingMapScheme);
distributedScheme.setBackingMapScheme(backingMapScheme);

On line 1, we create the DistributedScheme that will hold the configuration for our distributed-scheme and we set its scheme name and service name to the same value we would have used in the XML.
On line 4 is the first little lie of the blog post. I know I said “no XML” and I was kind of correct, we have no cache config XML, but some scheme classes still require an XmlElement for certain parameters, even if that XML is empty. So, line 4 just passes an empty XmlElement to the DistributedScheme.setXml method.
On line 5, we set the service to be auto-start, that is, when used in a cache server node, the service will be started automatically.

Now we have our DistributedScheme we need to add the ReadWriteBackingMapScheme we created above as the backing-map-scheme. Obviously we do this with a BackingMapScheme on lines 7 – 9.

So that is the cache schemes down. What about the proxy-scheme you ask, well “bear with…” we will come back to that later.

The caching-schemes

In our example we have only created a single scheme but we need to create the equivalent of the caching-schemes sec ion, which is a holder for all of our schemes. In this case Coherence has a class called ServiceSchemeRegistry which we create for this purpose and add our DistributedScheme to it.

  1. ServiceSchemeRegistry serviceSchemeRegistry = new ServiceSchemeRegistry();
  2. serviceSchemeRegistry.register(distributedScheme);
ServiceSchemeRegistry serviceSchemeRegistry = new ServiceSchemeRegistry();
serviceSchemeRegistry.register(distributedScheme);

The cache-mapping

With our cache schemes done, now we will a the cache mappings. In our example configuration file we only had a single mapping. Creating the cache mappings is very simple indeed.

  1. CacheMapping mapping = new CacheMapping("dist-*", "my-distributed-scheme");
CacheMapping mapping = new CacheMapping("dist-*", "my-distributed-scheme");

You just create an instance of the CacheMapping class that you set the cache-name and scheme-name parameters on.
Our example is very simple but you could be more complicated by adding int-params and interceptors to the cache mapping.

The cache-scheme-mapping

As with the cache schemes we need a holder for our cache mappings that represents the cache-scheme-mappings section of the configuration. Again Coherence has a class for that called CacheMappingRegistry for that purpose.

  1. CacheMappingRegistry mappingRegistry = new CacheMappingRegistry();
  2. mappingRegistry.register(mapping);
CacheMappingRegistry mappingRegistry = new CacheMappingRegistry();
mappingRegistry.register(mapping);

The Cache Configuration

Finally we have the parts we need to create the top level cache configuration class.

  1. CacheConfig config = new CacheConfig(new DefaultProcessingContext().getDefaultParameterResolver());
  2. config.setCacheMappingRegistry(mappingRegistry);
  3. config.setServiceSchemeRegistry(serviceSchemeRegistry);
CacheConfig config = new CacheConfig(new DefaultProcessingContext().getDefaultParameterResolver());
config.setCacheMappingRegistry(mappingRegistry);
config.setServiceSchemeRegistry(serviceSchemeRegistry);

Now we have a cache configuration we can use it to create a cache factory.

Create the ConfigurableCacheFactory

The implementation of ConfigurableCacheFactory that we are going to create is ExtensibleConfigurableCacheFactory, you could of course create a sub-class of this if you wanted to do more customisation.

There are a couple of things we need to create first is an instance of ExtensibleConfigurableCacheFactory.Dependencies which will hold the CacheConfig we created above…

  1. ExtensibleConfigurableCacheFactory.Dependencies dependencies =
  2.                 new ExtensibleConfigurableCacheFactory.DefaultDependencies(config);
ExtensibleConfigurableCacheFactory.Dependencies dependencies =
                new ExtensibleConfigurableCacheFactory.DefaultDependencies(config);

Next we need to add a few things to the ResourceRegistry that is contained within the ExtensibleConfigurableCacheFactory.Dependencies

  1. ResourceRegistry resourceRegistry = dependencies.getResourceRegistry();
  2. resourceRegistry.registerResource(InterceptorRegistry.class, new Registry());
  3. resourceRegistry.registerResource(EventDispatcherRegistry.class, new Registry());
  4. resourceRegistry.registerResource(XmlElement.class, "legacy-cache-config", new SimpleElement("cache-config"));
ResourceRegistry resourceRegistry = dependencies.getResourceRegistry();
resourceRegistry.registerResource(InterceptorRegistry.class, new Registry());
resourceRegistry.registerResource(EventDispatcherRegistry.class, new Registry());
resourceRegistry.registerResource(XmlElement.class, "legacy-cache-config", new SimpleElement("cache-config"));

…if you don’t do this it isn’t gonna work.

And finally we create the ExtensibleConfigurableCacheFactory

  1. ExtensibleConfigurableCacheFactory cacheFactory = new ExtensibleConfigurableCacheFactory(dependencies);
ExtensibleConfigurableCacheFactory cacheFactory = new ExtensibleConfigurableCacheFactory(dependencies);

Putting It all Together – Demo

So just to prove it works we will put it all together into a simple class with a main method

  1. package com.thegridman.coherence.noxml;
  2.  
  3. import com.tangosol.coherence.config.CacheConfig;
  4. import com.tangosol.coherence.config.CacheMapping;
  5. import com.tangosol.coherence.config.CacheMappingRegistry;
  6. import com.tangosol.coherence.config.ParameterList;
  7. import com.tangosol.coherence.config.ParameterMacroExpression;
  8. import com.tangosol.coherence.config.ResolvableParameterList;
  9. import com.tangosol.coherence.config.ServiceSchemeRegistry;
  10. import com.tangosol.coherence.config.builder.InstanceBuilder;
  11. import com.tangosol.coherence.config.scheme.BackingMapScheme;
  12. import com.tangosol.coherence.config.scheme.CacheStoreScheme;
  13. import com.tangosol.coherence.config.scheme.DistributedScheme;
  14. import com.tangosol.coherence.config.scheme.LocalScheme;
  15. import com.tangosol.coherence.config.scheme.ProxyScheme;
  16. import com.tangosol.coherence.config.scheme.ReadWriteBackingMapScheme;
  17. import com.tangosol.coherence.config.unit.Seconds;
  18. import com.tangosol.config.expression.Expression;
  19. import com.tangosol.config.expression.LiteralExpression;
  20. import com.tangosol.config.expression.Parameter;
  21. import com.tangosol.config.xml.DefaultProcessingContext;
  22. import com.tangosol.net.ExtensibleConfigurableCacheFactory;
  23. import com.tangosol.net.NamedCache;
  24. import com.tangosol.net.events.EventDispatcherRegistry;
  25. import com.tangosol.net.events.InterceptorRegistry;
  26. import com.tangosol.net.events.internal.Registry;
  27. import com.tangosol.run.xml.SimpleElement;
  28. import com.tangosol.run.xml.XmlElement;
  29. import com.tangosol.util.ResourceRegistry;
  30.  
  31. /**
  32.  * @author Jonathan Knight
  33.  */
  34. public class NoXmlDemo
  35. {
  36.     public static void main(String[] args) throws Exception
  37.     {
  38.         // Create a local-scheme
  39.         LocalScheme localScheme = new LocalScheme();
  40.         // Set expiry on our local scheme of 10 seconds
  41.         localScheme.setExpiryDelay(new LiteralExpression<>(new Seconds(10)));
  42.  
  43.         // Create a read-write-backing-map-scheme
  44.         ParameterList parameterList = new ResolvableParameterList();
  45.         Expression<?> expression = new ParameterMacroExpression<>("{cache-name}", String.class);
  46.         parameterList.add(new Parameter("cache-name", String.class, expression));
  47.  
  48.         CacheStoreScheme cacheStoreScheme = new CacheStoreScheme();
  49.         InstanceBuilder<Object> cacheStoreBuilder = new InstanceBuilder<>(MyCacheStore.class);
  50.         cacheStoreBuilder.setConstructorParameterList(parameterList);
  51.         cacheStoreScheme.setCustomBuilder(cacheStoreBuilder);
  52.  
  53.         ReadWriteBackingMapScheme readWriteBackingMapScheme = new ReadWriteBackingMapScheme();
  54.         readWriteBackingMapScheme.setCacheStoreScheme(cacheStoreScheme);
  55.         readWriteBackingMapScheme.setInternalScheme(localScheme);
  56.  
  57.         // Create a distributed-scheme
  58.         DistributedScheme distributedScheme = new DistributedScheme();
  59.         distributedScheme.setSchemeName("my-distributed-scheme");
  60.         distributedScheme.setServiceName("MyDistributedService");
  61.         distributedScheme.setXml(new SimpleElement("distributed-scheme"));
  62.         distributedScheme.setAutoStart(true);
  63.  
  64.         // Create the backing-map-scheme for the distributed-scheme
  65.         BackingMapScheme backingMapScheme = new BackingMapScheme();
  66.         backingMapScheme.setInnerScheme(readWriteBackingMapScheme);
  67.         distributedScheme.setBackingMapScheme(backingMapScheme);
  68.  
  69.         // Create the caching-schemes registry
  70.         ServiceSchemeRegistry serviceSchemeRegistry = new ServiceSchemeRegistry();
  71.         // Add the distributed scheme to the scheme registry
  72.         serviceSchemeRegistry.register(distributedScheme);
  73.  
  74.         // Create the cache-mapping
  75.         CacheMapping mapping = new CacheMapping("dist-*", "my-distributed-scheme");
  76.  
  77.         // Create the cache-scheme-mapping registry and add the mappings
  78.         CacheMappingRegistry mappingRegistry = new CacheMappingRegistry();
  79.         mappingRegistry.register(mapping);
  80.  
  81.         // Create the Cache Config and add the mappings and schemes
  82.         CacheConfig config = new CacheConfig(new DefaultProcessingContext().getDefaultParameterResolver());
  83.         config.setCacheMappingRegistry(mappingRegistry);
  84.         config.setServiceSchemeRegistry(serviceSchemeRegistry);
  85.  
  86.         // Create the ExtensibleConfigurableCacheFactory
  87.         ExtensibleConfigurableCacheFactory.Dependencies dependencies =
  88.                 new ExtensibleConfigurableCacheFactory.DefaultDependencies(config);
  89.  
  90.         ResourceRegistry resourceRegistry = dependencies.getResourceRegistry();
  91.         resourceRegistry.registerResource(InterceptorRegistry.class, new Registry());
  92.         resourceRegistry.registerResource(EventDispatcherRegistry.class, new Registry());
  93.         resourceRegistry.registerResource(XmlElement.class, "legacy-cache-config", new SimpleElement("cache-config"));
  94.  
  95.         // Create the actual Cache Factory
  96.         ExtensibleConfigurableCacheFactory cacheFactory = new ExtensibleConfigurableCacheFactory(dependencies);
  97.  
  98.         //Start the services;
  99.         cacheFactory.startServices();
  100.  
  101.         // Get a cache and put stuff in it
  102.         NamedCache cache = cacheFactory.ensureCache("dist-test", null);
  103.         cache.put("Key-1", "Value-1");
  104.         for (int i=0; i<11; i++)
  105.         {
  106.             System.err.println("Result: " + cache.get("Key-1"));
  107.             Thread.sleep(1000);
  108.         }
  109.     }
  110. }
package com.thegridman.coherence.noxml;

import com.tangosol.coherence.config.CacheConfig;
import com.tangosol.coherence.config.CacheMapping;
import com.tangosol.coherence.config.CacheMappingRegistry;
import com.tangosol.coherence.config.ParameterList;
import com.tangosol.coherence.config.ParameterMacroExpression;
import com.tangosol.coherence.config.ResolvableParameterList;
import com.tangosol.coherence.config.ServiceSchemeRegistry;
import com.tangosol.coherence.config.builder.InstanceBuilder;
import com.tangosol.coherence.config.scheme.BackingMapScheme;
import com.tangosol.coherence.config.scheme.CacheStoreScheme;
import com.tangosol.coherence.config.scheme.DistributedScheme;
import com.tangosol.coherence.config.scheme.LocalScheme;
import com.tangosol.coherence.config.scheme.ProxyScheme;
import com.tangosol.coherence.config.scheme.ReadWriteBackingMapScheme;
import com.tangosol.coherence.config.unit.Seconds;
import com.tangosol.config.expression.Expression;
import com.tangosol.config.expression.LiteralExpression;
import com.tangosol.config.expression.Parameter;
import com.tangosol.config.xml.DefaultProcessingContext;
import com.tangosol.net.ExtensibleConfigurableCacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.net.events.EventDispatcherRegistry;
import com.tangosol.net.events.InterceptorRegistry;
import com.tangosol.net.events.internal.Registry;
import com.tangosol.run.xml.SimpleElement;
import com.tangosol.run.xml.XmlElement;
import com.tangosol.util.ResourceRegistry;

/**
 * @author Jonathan Knight
 */
public class NoXmlDemo
{
    public static void main(String[] args) throws Exception
    {
        // Create a local-scheme
        LocalScheme localScheme = new LocalScheme();
        // Set expiry on our local scheme of 10 seconds
        localScheme.setExpiryDelay(new LiteralExpression<>(new Seconds(10)));

        // Create a read-write-backing-map-scheme
        ParameterList parameterList = new ResolvableParameterList();
        Expression<?> expression = new ParameterMacroExpression<>("{cache-name}", String.class);
        parameterList.add(new Parameter("cache-name", String.class, expression));

        CacheStoreScheme cacheStoreScheme = new CacheStoreScheme();
        InstanceBuilder<Object> cacheStoreBuilder = new InstanceBuilder<>(MyCacheStore.class);
        cacheStoreBuilder.setConstructorParameterList(parameterList);
        cacheStoreScheme.setCustomBuilder(cacheStoreBuilder);

        ReadWriteBackingMapScheme readWriteBackingMapScheme = new ReadWriteBackingMapScheme();
        readWriteBackingMapScheme.setCacheStoreScheme(cacheStoreScheme);
        readWriteBackingMapScheme.setInternalScheme(localScheme);

        // Create a distributed-scheme
        DistributedScheme distributedScheme = new DistributedScheme();
        distributedScheme.setSchemeName("my-distributed-scheme");
        distributedScheme.setServiceName("MyDistributedService");
        distributedScheme.setXml(new SimpleElement("distributed-scheme"));
        distributedScheme.setAutoStart(true);

        // Create the backing-map-scheme for the distributed-scheme
        BackingMapScheme backingMapScheme = new BackingMapScheme();
        backingMapScheme.setInnerScheme(readWriteBackingMapScheme);
        distributedScheme.setBackingMapScheme(backingMapScheme);

        // Create the caching-schemes registry
        ServiceSchemeRegistry serviceSchemeRegistry = new ServiceSchemeRegistry();
        // Add the distributed scheme to the scheme registry
        serviceSchemeRegistry.register(distributedScheme);

        // Create the cache-mapping
        CacheMapping mapping = new CacheMapping("dist-*", "my-distributed-scheme");

        // Create the cache-scheme-mapping registry and add the mappings
        CacheMappingRegistry mappingRegistry = new CacheMappingRegistry();
        mappingRegistry.register(mapping);

        // Create the Cache Config and add the mappings and schemes
        CacheConfig config = new CacheConfig(new DefaultProcessingContext().getDefaultParameterResolver());
        config.setCacheMappingRegistry(mappingRegistry);
        config.setServiceSchemeRegistry(serviceSchemeRegistry);

        // Create the ExtensibleConfigurableCacheFactory
        ExtensibleConfigurableCacheFactory.Dependencies dependencies =
                new ExtensibleConfigurableCacheFactory.DefaultDependencies(config);

        ResourceRegistry resourceRegistry = dependencies.getResourceRegistry();
        resourceRegistry.registerResource(InterceptorRegistry.class, new Registry());
        resourceRegistry.registerResource(EventDispatcherRegistry.class, new Registry());
        resourceRegistry.registerResource(XmlElement.class, "legacy-cache-config", new SimpleElement("cache-config"));

        // Create the actual Cache Factory
        ExtensibleConfigurableCacheFactory cacheFactory = new ExtensibleConfigurableCacheFactory(dependencies);

        //Start the services;
        cacheFactory.startServices();

        // Get a cache and put stuff in it
        NamedCache cache = cacheFactory.ensureCache("dist-test", null);
        cache.put("Key-1", "Value-1");
        for (int i=0; i<11; i++)
        {
            System.err.println("Result: " + cache.get("Key-1"));
            Thread.sleep(1000);
        }
    }
}

There is everything inside a single method. If you run the code it will create a ExtensibleConfigurableCacheFactory in the way described above.

Lines 99 - 108 are extra to prove it all works.
Line 99 tells the cache factory to start all of its autostart services.
Line 102 gets a NamedCache called dist-test from the cache factory.
Line 103 puts a key/value pair into the cache.
Line 104 - 108 just loop round getting the value from the cache every second for 11 seconds. This is to prove that the 10 seconds expiry we set way back when we created the local scheme is actually working.

If you run the class you will see something like this...

dist-test store called key=Key-1 value=Value-1
Result: Value-1
Result: Value-1
Result: Value-1
Result: Value-1
Result: Value-1
Result: Value-1
Result: Value-1
Result: Value-1
Result: Value-1
Result: Value-1
dist-test load called key=Key-1
Result: null

The first line is the store() method our cache store being called in response to the initial put on the cache. You can see that the cache name at the start of the line is correct, so out cache store macro parameter worked.
Then there are a number of lines where the result of the get() method is printed every second.
Eventually after 10 seconds the value will be expired from the cache so we then see the load() method being called on the cache store.
Finally we see the "Result: null" line as our cache store's load() method returned null.

So there we are, it all worked.

But We Missed Some Bits...

Yes, there are a couple of things missing from this very simple example.

The Serializer

The eagle eyed of you will have spotted that in the cache configuration XML at the start of the post there is a defaults section that specifies the default serializer to use in the rest of the configuration. We did not actually do anything with this above and if you run the code in a debugger and look at the serializer being used by the CacheService you will see it is not a PofSerializer.

There isn't actually anything in the classes we have mentioned above to handle the defaults (at least not anything I have found yet). We need to fall back to goo old XML, which I already mentioned when creating the distributed-scheme. You will remember that we had a line that injected an empty XmlElement into the DistributedScheme...

  1. distributedScheme.setXml(new SimpleElement("distributed-scheme"));
distributedScheme.setXml(new SimpleElement("distributed-scheme"));

Well, this is where we also need to inject the serializer, which we can do like this...

  1. SimpleElement xml = new SimpleElement("distributed-scheme");
  2. xml.addElement("serializer").setString("pof");
  3. distributedScheme.setXml(xml);
SimpleElement xml = new SimpleElement("distributed-scheme");
xml.addElement("serializer").setString("pof");
distributedScheme.setXml(xml);

So in effect we are injecting a snippet of XML that looks like this...

  1. <distributed-scheme>
  2.     <serializer>pof</serializer>
  3. </distributed-scheme>
<distributed-scheme>
    <serializer>pof</serializer>
</distributed-scheme>

...which is enough to make the service use the default POF serializer.

The proxy-service

Finally we come back to the proxy-service which I skipped previously. The reason for that is there is no proper scheme for a proxy service, or rather, there is a scheme but you need to configure it with XML - so the title of the post should be "You almost Don't Need That Cache Config XML File".

To configure the Proxy Scheme we need to do this...

  1. XmlElement proxyConfigElement = new SimpleElement("proxy-scheme");
  2. XmlElement acceptorElement = proxyConfigElement.addElement("acceptor-config");
  3. XmlElement tcpAcceptorElement = acceptorElement.addElement("tcp-acceptor");
  4. XmlElement localAddressElement = tcpAcceptorElement.addElement("local-address");
  5. XmlElement addressElement = localAddressElement.addElement("address");
  6. XmlElement portElement = localAddressElement.addElement("port");
  7. addressElement.setString("localhost");
  8. portElement.setInt(9099);
  9.  
  10. ProxyScheme proxyScheme = new ProxyScheme();
  11. proxyScheme.setSchemeName("my-proxy-scheme");
  12. proxyScheme.setServiceName("TcpProxyService");
  13. proxyScheme.setAutoStart(true);
  14. proxyScheme.setXml(proxyConfigElement);
XmlElement proxyConfigElement = new SimpleElement("proxy-scheme");
XmlElement acceptorElement = proxyConfigElement.addElement("acceptor-config");
XmlElement tcpAcceptorElement = acceptorElement.addElement("tcp-acceptor");
XmlElement localAddressElement = tcpAcceptorElement.addElement("local-address");
XmlElement addressElement = localAddressElement.addElement("address");
XmlElement portElement = localAddressElement.addElement("port");
addressElement.setString("localhost");
portElement.setInt(9099);

ProxyScheme proxyScheme = new ProxyScheme();
proxyScheme.setSchemeName("my-proxy-scheme");
proxyScheme.setServiceName("TcpProxyService");
proxyScheme.setAutoStart(true);
proxyScheme.setXml(proxyConfigElement);

...First we programatically create the XML to configure the proxy service. We could have done this various ways, but here we chose to use a few lines of code. We could have put the XML in a file and loaded it with the XmlHelper too if we wanted.
After we create the XML we create a ProxyScheme which following the same pattern as everything else, is the class that contains the configuration for the proxy service. We then set the scheme name and service name and set the configuration XML.

Now we have a proxy service we can add it to the service registry, where previously we just added the distributed scheme

  1. ServiceSchemeRegistry serviceSchemeRegistry = new ServiceSchemeRegistry();
  2. serviceSchemeRegistry.register(distributedScheme);
  3. serviceSchemeRegistry.register(proxyScheme);
ServiceSchemeRegistry serviceSchemeRegistry = new ServiceSchemeRegistry();
serviceSchemeRegistry.register(distributedScheme);
serviceSchemeRegistry.register(proxyScheme);

Now if we run the demo class above with these changes you will see in the logs that the proxy service is started and is listening on port 9099.

Running a DefaultCacheServer

I mentioned previously that we could use our programatically created cache factory inside a server node running a DefaultCacheServer. We can do this with a couple of lines of code like this...

  1. // Create the Cache Factory
  2. ExtensibleConfigurableCacheFactory cacheFactory = new ExtensibleConfigurableCacheFactory(dependencies);
  3. DefaultCacheServer dcs = new DefaultCacheServer(cacheFactory);
  4. dcs.startAndMonitor(5000L);
// Create the Cache Factory
ExtensibleConfigurableCacheFactory cacheFactory = new ExtensibleConfigurableCacheFactory(dependencies);
DefaultCacheServer dcs = new DefaultCacheServer(cacheFactory);
dcs.startAndMonitor(5000L);

Would I Use This in Production

Ha Ha Ha...

Whilst the code above was really to prove a point and show that you can now do a lot without an XML configuration file the work is not quite complete. There are still a few places where we need to fall back on XML. The classes used above are all internal to Coherence and not part of the documented API, so could and probably will change in forthcoming releases.

Right now, it has been fun to play with and occasionally useful when testing a bit of functionality as I can fire bits of Coherence up without a configuration file, so my test code is not full of XML files.

At least with an XML configuration file you have some idea of what your cluster should look like, although having said that, with custom namespaces and the ability to combine configuration files, this is becoming more complicated. I can imagine the poor guys in the Oracle Coherence Support Team not looking forward to the day when they ask for your XML configuration only to be sent some Java code instead!

You may also like...

3 Responses

  1. ivan says:

    Hmm, seems like the API was migrated from Common incubator to a major branch (very unstable in terms of API changes). And still no “easy to use” configuration API is introduced so far, overengenering is still in place =(((, I mean all of those ResolvableParameterList, ParameterMacroExpression and so on.

    • jk says:

      The API wasn’t migrated from the Incubator as the Incubator did not have the concept of builders and schemes for configuration. The Incubator did have custom namespace support, which has been migrated to the core product and in the process of this migration a lot of work was done to re-write how configuration works.

      Yes, I would agree the API is unstable but then it is not yet part of the documented public API, so I would expect it to be unstable for a while yet. For that reason I would not use a non-XML approach in any of the systems I am building at the moment. Where I might use it is to make small proof of concepts or experiments work without me having to keep masses of cache configuration files lying around.

  2. Brian Oliver says:

    A few points of clarification;

    1. The *internal* and undocumented Coherence Configuration API was not ported over from Coherence Incubator Commons. As JK correctly points out, the Coherence Namespace support was somewhat inspired by the work that was done with Coherence Incubator Commons, but for the most part it was re-written. A few interfaces remain the same but that’s about it.

    2. What Coherence 12.1.2 can do in terms of configuration namespaces is far superior to what was “incubated” as an “idea” in Coherence Commons. Hence the reason why the namespaces implementation in the Coherence Incubator Commons library was completely removed in Coherence Incubator 12, thus allowing all the Incubator patterns that used the feature to be re-written and simplified to use Coherence 12.1.2 (and not Commons).

    3. Configuring Coherence is a sophisticated activity. Unlike most “simple” Caches, Coherence allows developers to configure multi-layered and multi-topology caches to be, in most cases lazily, without trying to restrain the type of deployment, with interaction across Java, .NET, C++ (about 10 different dialects), REST, JSON et al, not to mention almost every available application server on the market. If it were “easy” it would be. The complexity and over-engineering you reference for the Coherence *internal* and undocumented configuration interfaces is very necessary for the types of customers that adopt Coherence and the use-cases they try to solve. This is why customers aren’t exposed to our internal interfaces!

    If it were as simple as a java.util.Map, there’d be no configuration at all.

Leave a Reply

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