10 Integrate CacheFactory with Spring

The CacheFactory static factory methods allow you to access all Coherence caches and services. These methods, (such as getCache), delegate to a ConfigurableCacheFactory interface, which is pluggable by using CacheFactory.setConfigurableCacheFactory or the operational override file (tangosol-coherence-override.xml).

In the Coherence cache configuration file, (coherence-cache-config.xml) hooks are provided for end users to provide their own implementations of Coherence interfaces, such as CacheStore and MapListener. This is configured by using the class-scheme element. Coherence can instantiate these classes in two ways: it can create a new instance by using the new operator, or it can invoke a user-provided factory method.

For some applications, it may be useful for Coherence to retrieve objects configured in a class-scheme from a Spring BeanFactory instead of creating its own instance. This is especially true for cache servers configured with CacheStore objects running in a standalone JVM, as these CacheStore objects typically need to be configured with data sources, connection pools, and so on. Spring is well known for its ability to provide easy configuration of data sources for plain Java objects.

SpringAwareCacheFactory is a custom ConfigurableCacheFactory which has the ability to delegate class-scheme bean instantiations to a Spring BeanFactory. It has two modes of operation:

To configure Coherence to use the SpringAwareCacheFactory, the following XML code should be placed in the operational override file (tangosol-coherence-override.xml):

Example 10-1 Configuring the Cache to use SpringAwareCacheFactory in the Override File

<configurable-cache-factory-config>
  <class-name system-property="tangosol.coherence.cachefactory">
    com.tangosol.coherence.spring.SpringAwareCacheFactory
  </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-param id="1">
      <param-type>java.lang.String</param-type>
      <param-value system-property="tangosol.coherence.springconfig">
        application-context.xml
      </param-value>
    </init-param>
  </init-params>
</configurable-cache-factory-config>

This will, by default, use coherence-cache-config.xml as the cache configuration file and application-context.xml as the Spring configuration file.

As an alternative to using the configuration file, the SpringAwareCacheFactory can be configured programmatically as illustrated inExample 10-2:

Example 10-2 Configuring a SpringAwareCacheFactory Programmatically

BeanFactory             bf  = ...
SpringAwareCacheFactory scf = new SpringAwareCacheFactory();
        
scf.setBeanFactory(bf);
CacheFactory.setConfigurableCacheFactory(scf);

Since the SpringAwareCacheFactory is BeanFactoryAware, it can also be defined in an application context:

Example 10-3 Defining a SpringAwareCacheFactory in an Application Context

<bean id="cacheFactory"
      class="com.tangosol.coherence.spring.SpringAwareCacheFactory">
</bean>

Taking this a step further, the Coherence CacheFactory can be configured inside of the application context:

Example 10-4 Configuring a CacheFactory in an Application Context

<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetClass" value="com.tangosol.net.CacheFactory"/>
  <property name="targetMethod" value="setConfigurableCacheFactory"/>
  <property name="arguments" ref="cacheFactory"/>
</bean>

The application context may have a CacheStore configured as in Example 10-5. Note that the EntityCacheStore is scoped as prototype. This is done because Coherence will manage the lifecycle of the bean when it is retrieved from Spring, just as if Coherence had instantiated the object using new.

Example 10-5 Configuring a CacheStore in an Application Context

<bean id="dataSource" class="...">
...
</bean>

<bean id="sessionFactory" class="...">
  <property name="dataSource" ref="dataSource"/>
  ...
</bean>
  
<bean id="entityCacheStore" 
      class="com.company.app.EntityCacheStore"
      scope="prototype">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

Coherence can use the entityCacheStore bean as illustrated in Example 10-6. By using the init-param element, setter injection can be used to set properties on the bean retrieved from Spring. In the above example, the bean will have the method setEntityName invoked with the cache name before it is used by Coherence.

Example 10-6 Configuring Setter Injection to Set Properties on the Bean

<?xml version="1.0"?>

<!DOCTYPE cache-config SYSTEM "cache-config.dtd">

<cache-config>
  <caching-scheme-mapping>
    <cache-mapping>
      <cache-name>com.company.app.domain.*</cache-name>
      <scheme-name>distributed-domain</scheme-name>
    </cache-mapping>
  </caching-scheme-mapping>

  <caching-schemes>
    <distributed-scheme>
      <scheme-name>distributed-domain</scheme-name>
      <backing-map-scheme>
        <read-write-backing-map-scheme>
          <internal-cache-scheme>
            <local-scheme />
          </internal-cache-scheme>
          <cachestore-scheme>
            <class-scheme>
              <class-name>spring-bean:entityCacheStore</class-name>
              <init-params>
                <init-param>
                  <param-name>setEntityName</param-name>
                  <param-value>{cache-name}</param-value>
                </init-param>
              </init-params>
            </class-scheme>
          </cachestore-scheme>
          <write-delay>5s</write-delay>
        </read-write-backing-map-scheme>
      </backing-map-scheme>
      <autostart>true</autostart>
    </distributed-scheme>
  </caching-schemes>
</cache-config>

Example 10-7 lists the source for SpringAwareCacheFactory. It requires Coherence 3.4.x and Spring 2.x.

Example 10-7 SpringAwareCacheFactory.java

/*
* SpringAwareCacheFactory.java
*
* Copyright 2001-2007 by Oracle. All rights reserved.
*
* Oracle is a registered trademarks of Oracle Corporation and/or its affiliates.
*
* This software is the confidential and proprietary information of
* Oracle Corporation. You shall not disclose such confidential and
* proprietary information and shall use it only in accordance with the
* terms of the license agreement you entered into with Oracle.
*
* This notice may not be removed or altered.
*/
package com.tangosol.coherence.spring;

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

import com.tangosol.util.ClassHelper;

import java.util.Iterator;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
* SpringAwareCacheFactory provides a facility to access caches declared
* in a "cache-config.dtd" compliant configuration file, similar to its super
* class {@link DefaultConfigurableCacheFactory}.  In addition, this factory
* provides the ability to reference beans in a Spring application context
* by using the use of a class-scheme element.
*
* <p>This factory can be configured to start its own Spring application
* context from which to retrieve these beans.  This can be useful for standalone
* JVMs such as cache servers.  It can also be configured at runtime with a
* preconfigured Spring bean factory.  This can be useful for Coherence
* applications running in an environment that is itself responsible for starting
* the Spring bean factory, such as a web container.
*
* @see #instantiateAny(CacheInfo, XmlElement,
        BackingMapManagerContext, ClassLoader)
*
* @author pperalta Jun 14, 2007
*/
public class SpringAwareCacheFactory
        extends DefaultConfigurableCacheFactory
        implements BeanFactoryAware
    {
    // ----- constructors -------------------------------------------------

    /**
    * Construct a default DefaultConfigurableCacheFactory using the
    * default configuration file name.
    */
    public SpringAwareCacheFactory()
        {
        super();
        }

    /**
    * Construct a SpringAwareCacheFactory using the specified path to
    * a "cache-config.dtd" compliant configuration file or resource.  This
    * will also create a Spring ApplicationContext based on the supplied
    * path to a Spring compliant configuration file or resource.
    *
    * @param sCacheConfig location of a cache configuration
    * @param sAppContext  location of a Spring application context
    */
    public SpringAwareCacheFactory(String sCacheConfig, String sAppContext)
        {
        super(sCacheConfig);

        azzert(sAppContext != null && sAppContext.length() > 0,
                "Application context location required");

        m_beanFactory = sCacheConfig.startsWith("file:") ? (BeanFactory)
            new FileSystemXmlApplicationContext(sCacheConfig) :
            new ClassPathXmlApplicationContext(sAppContext);

        // register a shutdown hook so the bean factory cleans up
        // upon JVM exit
        ((AbstractApplicationContext) m_beanFactory).registerShutdownHook();
        }

    /**
    * Construct a SpringAwareCacheFactory using the specified path to
    * a "cache-config.dtd" compliant configuration file or resource and
    * the supplied Spring BeanFactory.
    *
    * @param sPath       the configuration resource name or file path
    * @param beanFactory Spring BeanFactory used to load Spring beans
    */
    public SpringAwareCacheFactory(String sPath, BeanFactory beanFactory)
        {
        super(sPath);

        m_beanFactory = beanFactory;
        }

    // ----- extended methods -----------------------------------------------

    /**
    * Create an Object using the "class-scheme" element.
    * <p/>
    * In addition to the functionality provided by the super class,
    * this will retreive an object from the configured Spring BeanFactory
    * for class names that use the following format:
    * <pre>
    * &lt;class-name&gt;spring-bean:sampleCacheStore&lt;/class-name&gt;
    * </pre>
    *
    * Parameters may be passed to these beans by using setter injection as well:
    * <pre>
    *   &lt;init-params&gt;
    *     &lt;init-param&gt;
    *       &lt;param-name&gt;setEntityName&lt;/param-name&gt;
    *       &lt;param-value&gt;{cache-name}&lt;/param-value&gt;
    *     &lt;/init-param&gt;
    *   &lt;/init-params&gt;
    * </pre>
    *
    * Note that Coherence will manage the lifecycle of the instantiated Spring
    * bean, therefore any beans that are retreived using this method should be
    * scoped as a prototype in the Spring configuration file, for example:
    * <pre>
    *   &lt;bean id="sampleCacheStore"
    *         class="com.company.SampleCacheStore"
    *         scope="prototype"/&gt;
    * </pre>
    *
    * @param info      the cache info
    * @param xmlClass  "class-scheme" element.
    * @param context   BackingMapManagerContext to be used
    * @param loader    the ClassLoader to instantiate necessary classes
    *
    * @return a newly instantiated Object
    *
    * @see DefaultConfigurableCacheFactory#instantiateAny(
    *        CacheInfo, XmlElement, BackingMapManagerContext, ClassLoader) 
    */
    protected Object instantiateAny(CacheInfo info, XmlElement xmlClass,
            BackingMapManagerContext context, ClassLoader loader)        {
        if (translateSchemeType(xmlClass.getName()) != SCHEME_CLASS)
            {
            throw new IllegalArgumentException(
                    "Invalid class definition: " + xmlClass);
            }

        String sClass = xmlClass.getSafeElement("class-name").getString();

        if (sClass.startsWith(SPRING_BEAN_PREFIX))
            {
            String sBeanName = sClass.substring(SPRING_BEAN_PREFIX.length());

            azzert(sBeanName != null && sBeanName.length() > 0,
                    "Bean name required");

            XmlElement xmlParams = xmlClass.getElement("init-params");
            XmlElement xmlConfig = null;
            if (xmlParams != null)
                {
                xmlConfig = new SimpleElement("config");
                XmlHelper.transformInitParams(xmlConfig, xmlParams);
                }

            Object oBean = getBeanFactory().getBean(sBeanName);
            if (xmlConfig != null)
                {
                for (Iterator iter = xmlConfig.getElementList().iterator(); iter.hasNext();)
                    {
                    XmlElement xmlElement = (XmlElement) iter.next();

                    String sMethod = xmlElement.getName();
                    String sParam  = xmlElement.getString();
                    try
                        {
                        ClassHelper.invoke(oBean, sMethod, new Object[]{sParam});
                        }
                    catch (Exception e)
                        {
                        ensureRuntimeException(e,"Could not invoke " + sMethod +
                                "(" + sParam + ") on bean " + oBean);
                        }
                    }
                }
            return oBean;
            }        else
            {
            return super.instantiateAny(info, xmlClass, context, loader);
            }
        }

    /**
    * Get the Spring BeanFactory used by this CacheFactory
    * @return the Spring {@link BeanFactory} used by this CacheFactory
    */
    public BeanFactory getBeanFactory()
        {
        azzert(m_beanFactory != null, "Spring BeanFactory == null");
        return m_beanFactory;
        }

    /**
    * Set the Spring BeanFactory used by this CacheFactory
    * @param beanFactory the Spring {@link BeanFactory} used by this CacheFactory
    */
    public void setBeanFactory(BeanFactory beanFactory)
        {
        m_beanFactory = beanFactory;
        }

    // ----- data fields ----------------------------------------------------

    /**
    * Spring BeanFactory used by this CacheFactory
    */
    private BeanFactory m_beanFactory;

    /**
    * Prefix used in cache configuration "class-name" element to indicate
    * this bean is in Spring
    */
    private static final String SPRING_BEAN_PREFIX = "spring-bean:";
    }