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.
- <?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>
<?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…
- LocalScheme localScheme = new LocalScheme();
- 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…
- 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);
- }
- }
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…
- ParameterList parameterList = new ResolvableParameterList();
- Expression<?> expression = new ParameterMacroExpression<>("{cache-name}", String.class);
- 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
.
- ReadWriteBackingMapScheme readWriteBackingMapScheme = new ReadWriteBackingMapScheme();
- readWriteBackingMapScheme.setCacheStoreScheme(cacheStoreScheme);
- 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.
- 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);
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.
- ServiceSchemeRegistry serviceSchemeRegistry = new ServiceSchemeRegistry();
- 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.
- 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.
- CacheMappingRegistry mappingRegistry = new CacheMappingRegistry();
- 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.
- CacheConfig config = new CacheConfig(new DefaultProcessingContext().getDefaultParameterResolver());
- config.setCacheMappingRegistry(mappingRegistry);
- 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…
- ExtensibleConfigurableCacheFactory.Dependencies dependencies =
- 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
…
- 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"));
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
…
- 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
- 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);
- }
- }
- }
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
...
- 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...
- SimpleElement xml = new SimpleElement("distributed-scheme");
- xml.addElement("serializer").setString("pof");
- 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...
- <distributed-scheme>
- <serializer>pof</serializer>
- </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...
- 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);
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
- ServiceSchemeRegistry serviceSchemeRegistry = new ServiceSchemeRegistry();
- serviceSchemeRegistry.register(distributedScheme);
- 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...
- // Create the Cache Factory
- ExtensibleConfigurableCacheFactory cacheFactory = new ExtensibleConfigurableCacheFactory(dependencies);
- DefaultCacheServer dcs = new DefaultCacheServer(cacheFactory);
- 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!
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.
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.
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.