In this post we look at how to create cache stores that need to ‘communicate’ with a database and JMS. Spring will be used to configure the necessary resources which will be injected into a Hibernate template and a JMS template that will be used by the cache store. We start with a basic set-up. Subsequently, we will discuss how to integrate Spring with Coherence and show an example which uses the HibernateCacheStore that ships with Coherence. After which we show how to use Spring’s HibernateTemplate to create a custom cache store. Finally, we present an example that integrates JMS as well by using Spring’s JMSTemplate.

Basic set-up

We have one entity

package model.entities;

import com.tangosol.io.ExternalizableLite;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;
import com.tangosol.util.ExternalizableHelper;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.Serializable;

public class Klant implements PortableObject {

    private Integer klantnummer;
    private String naam;
    private String adres;
    private String stad;
    private String provincie;
    private String postcode;
    private Integer gebied;
    private String telefoonnummer;
    private Integer reputatieNummer;
    private Double kredietlimiet;
    private String commentaar;

    public Klant() {
    }

    public Integer getKlantnummer() {
        return klantnummer;
    }

    public void setKlantnummer(Integer klantnummer) {
        this.klantnummer = klantnummer;
    }

    public String getNaam() {
        return naam;
    }

    public void setNaam(String naam) {
        this.naam = naam;
    }

    public String getAdres() {
        return adres;
    }

    public void setAdres(String adres) {
        this.adres = adres;
    }

    public String getStad() {
        return stad;
    }

    public void setStad(String stad) {
        this.stad = stad;
    }

    public String getProvincie() {
        return provincie;
    }

    public void setProvincie(String provincie) {
        this.provincie = provincie;
    }

    public String getPostcode() {
        return postcode;
    }

    public void setPostcode(String postcode) {
        this.postcode = postcode;
    }

    public Integer getGebied() {
        return gebied;
    }

    public void setGebied(Integer gebied) {
        this.gebied = gebied;
    }

    public String getTelefoonnummer() {
        return telefoonnummer;
    }

    public void setTelefoonnummer(String telefoonnummer) {
        this.telefoonnummer = telefoonnummer;
    }

    public Integer getReputatieNummer() {
        return reputatieNummer;
    }

    public void setReputatieNummer(Integer reputatieNummer) {
        this.reputatieNummer = reputatieNummer;
    }

    public Double getKredietlimiet() {
        return kredietlimiet;
    }

    public void setKredietlimiet(Double kredietlimiet) {
        this.kredietlimiet = kredietlimiet;
    }

    public String getCommentaar() {
        return commentaar;
    }

    public void setCommentaar(String commentaar) {
        this.commentaar = commentaar;
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }

        if (object == null) {
            return false;
        }

        if (!(object instanceof Klant)) {
            return false;
        }

        Klant klant = (Klant) object;
        return klantnummer.equals(klant.getKlantnummer());
    }

    @Override
    public int hashCode() {
        if (klantnummer != null) {
            return 37 * klantnummer.hashCode();
        } else {
            return System.identityHashCode(this);
        }
    }

    @Override
    public String toString() {
        return naam + ", " + adres;
    }

    public void readExternal(PofReader reader) throws IOException {
        setKlantnummer(reader.readInt(0));
        setNaam(reader.readString(1));
        setAdres(reader.readString(2));
        setStad(reader.readString(3));
        setProvincie(reader.readString(4));
        setPostcode(reader.readString(5));
        setGebied(reader.readInt(6));
        setTelefoonnummer(reader.readString(7));
        setReputatieNummer(reader.readInt(8));
        setKredietlimiet(reader.readDouble(9));
        setCommentaar(reader.readString(10));
    }

    public void writeExternal(PofWriter writer) throws IOException {
        if (getKlantnummer() != null) {
            writer.writeInt(0, getKlantnummer());
        }
        if (getNaam() != null) {
            writer.writeString(1, getNaam());
        }
        if (getAdres() != null) {
            writer.writeString(2, getAdres());
        }
        if (getStad() != null) {
            writer.writeString(3, getStad());
        }
        if (getProvincie() != null) {
            writer.writeString(4, getProvincie());
        }
        if (getPostcode() != null) {
            writer.writeString(5, getPostcode());
        }
        if (getGebied() != null) {
            writer.writeInt(6, getGebied());
        }
        if (getTelefoonnummer() != null) {
            writer.writeString(7, getTelefoonnummer());
        }
        if (getReputatieNummer() != null) {
            writer.writeInt(8, getReputatieNummer());
        }
        if (getKredietlimiet() != null) {
            writer.writeDouble(9, getKredietlimiet());
        }
        if (getCommentaar() != null) {
            writer.writeString(10, getCommentaar());
        }
    }

    public void readExternal(DataInput dataInput) throws IOException {
        setKlantnummer(dataInput.readInt());
        setNaam(ExternalizableHelper.readSafeUTF(dataInput));
        setAdres(ExternalizableHelper.readSafeUTF(dataInput));
        setStad(ExternalizableHelper.readSafeUTF(dataInput));
        setProvincie(ExternalizableHelper.readSafeUTF(dataInput));
        setPostcode(ExternalizableHelper.readSafeUTF(dataInput));
        setGebied(dataInput.readInt());
        setTelefoonnummer(ExternalizableHelper.readSafeUTF(dataInput));
        setReputatieNummer(dataInput.readInt());
        setKredietlimiet(dataInput.readDouble());
        setCommentaar(ExternalizableHelper.readSafeUTF(dataInput));
    }

    public void writeExternal(DataOutput dataOutput) throws IOException {
        if (getKlantnummer() != null) {
            dataOutput.writeInt(getKlantnummer());
        }
        if (getNaam() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getNaam());
        }
        if (getAdres() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getAdres());
        }
        if (getStad() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getStad());
        }
        if (getProvincie() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getProvincie());
        }
        if (getPostcode() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getPostcode());
        }
        if (getGebied() != null) {
            dataOutput.writeInt(getGebied());
        }
        if (getTelefoonnummer() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getTelefoonnummer());
        }
        if (getReputatieNummer() != null) {
            dataOutput.writeInt(getReputatieNummer());
        }
        if (getKredietlimiet() != null) {
            dataOutput.writeDouble(getKredietlimiet());
        }
        if (getCommentaar() != null) {
            ExternalizableHelper.writeSafeUTF(dataOutput, getCommentaar());
        }
    }
}

Here, we have implemented PortableObject and ExternalizableLite. We use Hibernate as the persistence framework and have to following mapping for the entity

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="model.entities">
    <class name="Klant" table="CUSTOMER">
        <id name="klantnummer" type="integer" column="CUSTID">
            <generator class="assigned"/>
        </id>
        <property name="naam" type="string" column="NAME"/>
        <property name="adres" type="string" column="ADDRESS"/>
        <property name="stad" type="string" column="CITY"/>
        <property name="provincie" type="string" column="STATE"/>
        <property name="postcode" type="string" column="ZIP"/>
        <property name="gebied" type="integer" column="AREA"/>
        <property name="telefoonnummer" type="string" column="PHONE"/>
        <property name="reputatieNummer" type="integer" column="REPID"/>
        <property name="kredietlimiet" type="double" column="CREDITLIMIT"/>
        <property name="commentaar" type="string" column="COMMENTS"/>
    </class>
</hibernate-mapping>

We perform the basic persistence operations (insert, update, delete and select) for which we define the following interface

package model.logic;

import java.io.Serializable;
import java.util.List;

public interface GenericDAO<T, ID extends Serializable> {
    public void addEntity(ID id, T entity);

    public void removeEntity(ID id);

    public void updateEntity(ID id, T entity);

    public T findEntity(ID id);

    public List<T> findEntities();
}

We will use Coherence with the write-behind pattern for updating data through the cache. Thus the client only communicates with Coherence and we can implement the interface as follows:

package model.logic;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.util.MapTriggerListener;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.filter.EqualsFilter;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public abstract class GenericCoherenceDAO<T, ID extends Serializable> implements GenericDAO<T, ID> {

    private Class<T> persistentClass;
    private NamedCache namedCache;

    public GenericCoherenceDAO() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            setPersistentClass((Class<T>) parameterizedType.getActualTypeArguments()[0]);
        } else {
            System.out.println("Not an instance of parameterized type: " + type);
        }

        setNamedCache(CacheFactory.getCache(getPersistentClass().getName()));
    }

    public Class<T> getPersistentClass() {
        return persistentClass;
    }

    public void setPersistentClass(Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

    public NamedCache getNamedCache() {
        return namedCache;
    }

    public void setNamedCache(NamedCache namedCache) {
        this.namedCache = namedCache;
    }

    public void addEntity(ID id, T entity) {
        getNamedCache().put(id, entity);
    }

    public void removeEntity(ID id) {
        getNamedCache().remove(id);
    }

    public void updateEntity(ID id, T entity) {
        getNamedCache().put(id, entity);
    }

    public T findEntity(ID id) {
        return (T) getNamedCache().get(id);
    }

    public List<T> findEntities() {
        ValueExtractor extractor = new ChainedExtractor("getClass.getName");
        Set keys = getNamedCache().keySet(new EqualsFilter(extractor, getPersistentClass().getName()));
        return new ArrayList<T>(getNamedCache().getAll(keys).values());
    }
}

By using a CacheStore we will tell Coherence how to communicate with certain resources, such as a database and JMS.

Specific entity functionality is implemented in entity specific DAOs, for example,

package model.logic;

import model.entities.Klant;

import java.util.Iterator;
import java.util.Map;

public interface KlantDAO extends GenericDAO<Klant, Integer> {
    public void preload();

    public Iterator<Map.Entry<Integer, Klant>> getKlantData();
}

Which is implemented as follows

package model.logic;

import com.tangosol.net.cache.ContinuousQueryCache;
import com.tangosol.util.Filter;
import com.tangosol.util.QueryHelper;
import model.entities.Klant;
import model.util.events.ContinuousCacheListener;
import model.util.loader.*;

import java.util.Iterator;
import java.util.Map;

public class KlantDAOBean extends GenericCoherenceDAO<Klant, Integer> implements KlantDAO {

    private ContinuousQueryCache continuousQueryCache;

    public KlantDAOBean() {
        Filter filter = QueryHelper.createFilter("kredietlimiet between 4000.0 and 12000.0");
        continuousQueryCache = new ContinuousQueryCache(getNamedCache(), filter, new ContinuousCacheListener());
    }

    public void preload() {
        Source source = new HibernateSource(super.getNamedCache(), super.getPersistentClass());
        Target target = new CoherenceTarget(super.getNamedCache(), super.getPersistentClass(), "klantnummer");
        Loader loader = new Loader(source, target);
        loader.load();
    }

    public Iterator<Map.Entry<Integer, Klant>> getKlantData() {
        return continuousQueryCache.entrySet().iterator();
    }
}

This provides preloading and continuous query functionality for the Klant entity. To test the environment will use the following

package model.test;

import model.entities.Klant;
import model.logic.KlantDAO;
import model.logic.KlantDAOBean;

import java.util.Random;

public class Test {

    private Random generator = new Random();

    public static void main(String[] args) {
        Test test = new Test();

        KlantDAO klantDAO = new KlantDAOBean();

        test.doRandomReadWriteTest(klantDAO);
    }

    private void doRandomReadWriteTest(KlantDAO klantDAO) {
        klantDAO.preload();

        while (true) {
            // generate a random client
            Klant klant = createKlant();
            // insert or update a client - an update is issued when the client already exists
            klantDAO.addEntity(klant.getKlantnummer(), klant);
            if (generator.nextDouble() &lt; 0.001) {
                System.out.println("JACKPOT");
                // remove a client
                klantDAO.removeEntity(generateKlantNummer());
                // get all client data
                klantDAO.findEntities();
                // get data from the continuous query cache
                klantDAO.getKlantData();
            } else {
                // find a client by ID
                klantDAO.findEntity(generateKlantNummer());
            }
        }
    }

    private Klant createKlant() {
        int klantnummer = generateKlantNummer();

        Klant klant = new Klant();
        klant.setKlantnummer(klantnummer);
        klant.setNaam("Person" + klantnummer);
        klant.setAdres("Someware");
        klant.setStad("Else");
        klant.setProvincie("NL");
        klant.setPostcode("1234AB");
        klant.setGebied(1);
        klant.setTelefoonnummer("123-4567");
        klant.setReputatieNummer(1);
        klant.setKredietlimiet(Math.rint(generator.nextDouble() * 5000.0));
        klant.setCommentaar(Long.toString(Math.abs(generator.nextLong()), 36));

        return klant;
    }

    private Integer generateKlantNummer() {
        int klantnummer = generator.nextInt(10000);
        if (klantnummer == 0 || (klantnummer &gt;= 100 && klantnummer &lt;= 109)) {
            return 42;
        } else {
            return klantnummer;
        }
    }
}

Coherence and Spring

Access to Coherence caches and services are through the static factory methods provided by the CacheFactory class. These methods delegate to the ConfigurableCacheFactory interface. An implemention of this interface can be plugged-in by using the tangosol-coherence-override.xml file.

When configuring Coherence, we can provide our own CacheStore implementations. CacheStore implementations usually use some sort of resource, such as a data source. Spring provides the perfect way to configure all kinds of resources and manage the lifecycle of these resources. Thus it would be handy to use Spring constructs, such as transaction managers, data sources, etcetera. The SpringAwareCacheFactory provides, next to the facility to access caches in a Coherence configuration file, the ability to reference beans configured using Spring. The SpringAwareCacheFactory can be obtained here:

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 through 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 run
 * time 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)
 */
public class SpringAwareCacheFactory extends DefaultConfigurableCacheFactory implements BeanFactoryAware {

    /**
     * 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:";

    /**
     * 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(sAppContext)
                : 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;
    }

    /**
     * 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:
     * <p/>
     * &lt;class-name&gt;spring-bean:sampleCacheStore&lt;/class-name&gt;
     * <p/>
     * Parameters may be passed to these beans through setter injection as well:
     * <p/>
     * &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;
     * <p/>
     * Note that Coherence will manage the lifecycle of the instantiated Spring bean, therefore any beans that
     * are retrieved using this method should be scoped as a prototype in the Spring configuration file, for example:
     * <p/>
     * &lt;bean id="sampleCacheStore" class="com.company.SampleCacheStore" scope="prototype"/&gt;
     *
     * @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)
     */
    @Override
    public 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;
    }
}

HibernateCacheStore

To set-up the HibernateCacheStore, which ships with the Coherence distribution we proceed as follows. We start with configuring Spring

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="cacheStore" class="com.tangosol.coherence.hibernate.HibernateCacheStore" scope="prototype">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>model/entities/Klant.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.c3p0.min_size">${connectionpool.minimum.size}</prop>
                <prop key="hibernate.c3p0.max_size">${connectionpool.maximum.size}</prop>
                <prop key="hibernate.c3p0.timeout">${connectionpool.timeout}</prop>
                <prop key="hibernate.c3p0.max_statements">${connectionpool.maximum.statements}</prop>
                <prop key="hibernate.c3p0.idle_test_period">${connectionpool.idle.period}</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
    </bean>
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="driverType" value="${jdbc.driverClassName}"/>
        <property name="URL" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:spring-config.properties</value>
            </list>
        </property>
    </bean>
</beans>

Here we have configured a dataSource that is injected into a Hibernate sessionFactory, which is used by the cacheStore. Note that the cacheStore bean has protoType as its scope. This forces Spring to produce a new bean instance each time one is needed and lets Coherence manage its life cycle when we refer to it in the Coherence configuration, for example,

<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>
            <instance>
                <class-name>com.tangosol.io.pof.ConfigurablePofContext</class-name>
                <init-params>
                    <init-param>
                        <param-type>String</param-type>
                        <param-value>pof-config.xml</param-value>
                    </init-param>
                </init-params>
            </instance>
        </serializer>
    </defaults>
    <caching-scheme-mapping>
        <cache-mapping>
            <cache-name>*</cache-name>
            <scheme-name>read-write-distributed</scheme-name>
            <init-params>
                <init-param>
                    <param-name>back-size-limit</param-name>
                    <param-value>250M</param-value>
                </init-param>
            </init-params>
        </cache-mapping>
    </caching-scheme-mapping>
    <caching-schemes>
        <distributed-scheme>
            <scheme-name>read-write-distributed</scheme-name>
            <service-name>ReadWriteDistributedCache</service-name>
            <thread-count>5</thread-count>
            <backup-count>1</backup-count>
            <backup-count-after-writebehind>0</backup-count-after-writebehind>
            <backing-map-scheme>
                <read-write-backing-map-scheme>
                    <scheme-ref>read-write-backing-map</scheme-ref>
                </read-write-backing-map-scheme>
            </backing-map-scheme>
            <autostart>true</autostart>
        </distributed-scheme>
        <read-write-backing-map-scheme>
            <scheme-name>read-write-backing-map</scheme-name>
            <internal-cache-scheme>
                <local-scheme>
                    <scheme-ref>backing-map</scheme-ref>
                </local-scheme>
            </internal-cache-scheme>
			<write-max-batch-size>512</write-max-batch-size>
            <cachestore-scheme>
                <class-scheme>
                    <class-name>spring-bean:cacheStore</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>{write-delay 10s}</write-delay>
            <write-batch-factor>{write-batch-factor 0.75}</write-batch-factor>
            <write-requeue-threshold>{write-requeue-threshold 512}</write-requeue-threshold>
            <refresh-ahead-factor>{refresh-ahead-factor 0.1}</refresh-ahead-factor>
        </read-write-backing-map-scheme>
        <local-scheme>
            <scheme-name>backing-map</scheme-name>
            <eviction-policy>hybrid</eviction-policy>
            <high-units>{back-size-limit 0}</high-units>
            <unit-calculator>binary</unit-calculator>
            <expiry-delay>{back-expiry-delay 1h}</expiry-delay>
        </local-scheme>
    </caching-schemes>
</cache-config>

Configured Spring beans can be referenced in the Coherence cache configuration by using spring-bean:<bean-id>. In our case this will be spring-bean:cacheStore. By specifying init-param elements, we can specify setters that will be injected into the bean with appropriate values. In this case, we inject the entityName attribute of the HibernateCacheStore with the name of the cache, which will be model.entities.Klant when we use KlantDAO klantDAO = new KlantDAOBean().

Now that we have the Spring and Coherence configuration in place, we need to tell Coherence about them. This can be accomplished by using the tangosol-coherence-override.xml operational override file, i.e.,

<coherence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://xmlns.oracle.com/coherence/coherence-operational-config"
           xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-operational-config coherence-operational-config.xsd">
    <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">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">spring-config.xml</param-value>
            </init-param>
        </init-params>
    </configurable-cache-factory-config>
</coherence>

This tells Coherence to use com.tangosol.coherence.spring.SpringAwareCacheFactory as the CacheFactory implementation, which is invoked by using the
SpringAwareCacheFactory(String sCacheConfig, String sAppContext)
constructor.

CustomCacheStore

Before we implement our own cache store implementation, we will discuss some of the cache configurations we have made in the set-up described above. To make sure that entries are requeued upon a store operation failure, we have to set the write-requeue-threshold to the maximum number of entries that can exist in the queue. The number of entries sent to the storeAll implementation is controlled by the write-max-batch-size setting which defaults to 128. If storeAll throws an exception, the read-write backing map requeues all entries still present in mapEntries.

The write-batch-factor is used to calculate the soft-ripe time for the entries in the queue. An entry in the queue is considered ripe when it has been in the queue for at least the time configured by the write-behind element. Conceptually, the write-behind thread uses the following logic:

  • The thread waits for a queued entry to become ripe.
  • The thread dequeues all ripe and soft-ripe entries in the queue.
  • The thread writes the entries by using store, if there is one entry, or storeAll, if there are multiple entries.

The store and storeAll implementations must be idempotent, which means that the operation can be invoked again without any side effects. Another thing is that there is no ordering guarantee, thus it is not advised to have store and storeAll implementations that have dependencies, such as referential integrity and validation on the database that is not performed in the application.

When using Spring and Hibernate, we can use the HibernateTemplate, to implement our cache store implementation, for example,

package model.util.readwrite;

import com.tangosol.net.cache.AbstractCacheStore;
import org.springframework.orm.hibernate3.HibernateTemplate;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class CustomCacheStore extends AbstractCacheStore {

    private Integer batchSize = 128;
    private HibernateTemplate hibernateTemplate;
    private String entityName;

    public Integer getBatchSize() {
        return batchSize;
    }

    public void setBatchSize(Integer batchSize) {
        this.batchSize = batchSize;
    }

    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    public String getEntityName() {
        return entityName;
    }

    public void setEntityName(String entityName) {
        this.entityName = entityName;
    }

    @Override
    public Object load(Object key) {
        return findEntity(key);
    }

    @Override
    public void store(Object key, Object value) {
        getHibernateTemplate().saveOrUpdate(value);
    }

    @Override
    public void storeAll(Map mapEntries) {
        if (getBatchSize() == 0 || mapEntries.size() &lt; getBatchSize()) {
            storeBatch(mapEntries);
        } else {
            Map batch = new HashMap(getBatchSize());

            while (!mapEntries.isEmpty()) {
                Iterator iterator = mapEntries.entrySet().iterator();
                while (iterator.hasNext() && batch.size() &lt; getBatchSize()) {
                    Map.Entry entry = (Map.Entry) iterator.next();
                    batch.put(entry.getKey(), entry.getValue());
                }
                storeBatch(batch);
                mapEntries.keySet().removeAll(batch.keySet());
                batch.clear();
            }
        }
    }

    @Override
    public void erase(Object key) {
        Object find = findEntity(key);
        if (find != null) {
            getHibernateTemplate().delete(find);
        }
    }

    private void storeBatch(Map batch) {
        getHibernateTemplate().saveOrUpdateAll(batch.values());
    }

    private Object findEntity(Object key) {
        Object object = null;
        if (key instanceof Integer) {
            object = getHibernateTemplate().get(getEntityName(), (Integer) key);
        }
        return object;
    }
}

Note that next to the write-max-batch-size setting, we can chunk the map passed by the write-behind thread by setting the batch-size. Note that smaller batches have a lighter load on the system, i.e., writing large batches to a database requires a larger transaction / rollback log in the database, more memory consumption by the JDBC driver and will also cause Coherence to consume more memory in the storage member. Data in the write-behind thread is stored in binary format and the map passed to storeAll is deserialized lazily, with the result that if a very large map is passed and deserialized, the garbage collector can introduce very long pauses.

An example Spring configuration looks as follows:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="cacheStore" class="model.util.readwrite.CustomCacheStore" scope="prototype">
        <property name="batchSize" value="256"/>
        <property name="hibernateTemplate" ref="hibernateTemplate"/>
    </bean>
    <bean id="cacheStoreProxy" parent="transactionTemplate">
        <property name="target" ref="cacheStore"/>
        <property name="proxyInterfaces" value="com.tangosol.net.cache.CacheStore"/>
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>model/entities/Klant.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.c3p0.min_size">${connectionpool.minimum.size}</prop>
                <prop key="hibernate.c3p0.max_size">${connectionpool.maximum.size}</prop>
                <prop key="hibernate.c3p0.timeout">${connectionpool.timeout}</prop>
                <prop key="hibernate.c3p0.max_statements">${connectionpool.maximum.statements}</prop>
                <prop key="hibernate.c3p0.idle_test_period">${connectionpool.idle.period}</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
    </bean>
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="driverType" value="${jdbc.driverClassName}"/>
        <property name="URL" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:spring-config.properties</value>
            </list>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="transactionTemplate" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
          abstract="true">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="store*">PROPAGATION_REQUIRED</prop>
                <prop key="erase*">PROPAGATION_REQUIRED</prop>
                <prop key="load*">PROPAGATION_SUPPORTS,readOnly</prop>
            </props>
        </property>
    </bean>
</beans>

To let Spring handle the transactions we have configured a cacheStoreProxy that uses a transactionTemplate and target this proxy to our cache store implementation. A target is an object that is being advised. Note that without AOP (aspect oriented programming) the target must handle its own transaction logic. A proxy is an object that is created after applying an advice to the target object. From a client viewpoint, the target object (pre-AOP) and the proxy object (post-AOP) are the same, i.e., the proxy class poses as the target bean, intercepting advised method calls and forwarding those calls to the target bean.

Integrating JMS

JMS resources are typically configured in an application server. Such resources are identified by using a unique name, a so-called JDNI name. In this case we need to retrieve resources that are not configured as Spring beans, but as ‘JNDI’ resources. We can use Spring’s JndiObjectFactoryBean for this purpose. The JndiObjectFactoryBean provides an abstraction layer to map JNDI objects to a bean.

If we were to retrieve the resources locally, i.e., the application is deployed on the application server where the resources are configured we can proceed as follows. First, we define a reference in, for example, the web.xml file

<resource-ref>
	<res-ref-name>jdbc/exampleDS</res-ref-name>
	<res-type>javax.sql.DataSource</res-type>
	<res-auth>Container</res-auth>
</resource-ref>

Subsequently, the Spring bean can be created as follows:

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiName" value="jdbc/exampleDS"/>
	<property name="resourceRef" value="true"/>
</bean>

When the resources have to be retrieved remotely, as in our case, we have to proceed as follows. First, we configure a JNDI template

<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
	<property name="environment">
		<props>
			<prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
			<prop key="java.naming.provider.url">t3://172.31.0.112:7002,172.31.0.112:7003</prop>
		</props>
	</property>
</bean>

Here, we connect to a WebLogic cluster. In this case we also need the weblogic.jndi.WLInitialContextFactory class in the classpath, which is provided by the wlclient.jar file. As we are planning to use WebLogic JMS, we also need the wljmsclient.jar in the classpath. To obtain the configured resources we can use the following

<bean id="connectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiTemplate" ref="jndiTemplate"/>
	<property name="jndiName" value="jms/ConnectionFactory"/>
</bean>
<bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
	<property name="jndiTemplate" ref="jndiTemplate"/>
	<property name="jndiName" value="jms/CompanyQueue"/>
</bean>

The WebLogic JMS configuration is the same as the one presented in the post Managing Failure Conditions in a WebLogic Environment. To use JMS in conjunction with Spring, we can resort to the JMSTemplate. To use JMSTemplate we need to configure it, i.e.,

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="defaultDestination" ref="destination"/>
</bean>

Before, we add JMS to the cache store let us first test the set-up. In order to do this we create a sender

package model.util.jms;

import model.entities.Klant;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import javax.jms.*;

public class JMSSender {

    private JmsTemplate jmsTemplate;

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendTextMessage() {
        getJmsTemplate().send(new MessageCreator(){
            public Message createMessage(Session session) throws JMSException {
                TextMessage message = session.createTextMessage();
                message.setText("Testing the JMS set-up one-two, one-two test");
                return message;
            }
        });
    }

    public void sendMessage(final Klant klant) {
        getJmsTemplate().send(new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                ObjectMessage message = session.createObjectMessage();
                message.setObject(klant);
                return message;
            }
        });
    }
}

and a receiver

package model.util.jms;

import model.entities.Klant;
import javax.jms.*;

public class JMSReceiver implements MessageListener {

    public void onMessage(Message message) {
        if (message instanceof TextMessage) {
            TextMessage text = (TextMessage) message;
            try {
                message.acknowledge();
                System.out.println("received the following message: " + text.getText());
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }

        if (message instanceof ObjectMessage) {
            ObjectMessage objectMessage = (ObjectMessage) message;
            try {
                message.acknowledge();
                Klant person = (Klant) objectMessage.getObject();
                System.out.println("RECEIVED MESSAGE " + person);
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
    }
}

Note that the receiver looks very much like an equivalent of a message-driven bean, except for the MessageDriven annotation. In order for this to work we need to wire the receiver into a message listener container, i.e.,

<bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="destination" ref="destination"/>
	<property name="messageListener" ref="receiver"/>
</bean>
<bean id="sender" class="model.util.jms.JMSSender">
	<property name="jmsTemplate" ref="jmsTemplate"/>
</bean>
<bean id="receiver" class="model.util.jms.JMSReceiver"/>

Here, we also configured beans for the sender and the receiver. The test class looks as follows

package model.util.jms;

import model.entities.Klant;
import model.util.spring.SpringUtil;

import java.util.Random;

public class JMSTest {

    private Random generator = new Random();

    public static void main(String[] args) {
        JMSTest test = new JMSTest();

        JMSSender jmsSender = SpringUtil.getJMSSender();
        jmsSender.sendMessage(test.createKlant());
        System.out.println("done sending message");
    }

    private Klant createKlant() {
        int klantnummer = generateKlantNummer();

        Klant klant = new Klant();
        klant.setKlantnummer(klantnummer);
        klant.setNaam("Person" + klantnummer);
        klant.setAdres("Someware");
        klant.setStad("Else");
        klant.setProvincie("NL");
        klant.setPostcode("1234AB");
        klant.setGebied(1);
        klant.setTelefoonnummer("123-4567");
        klant.setReputatieNummer(1);
        klant.setKredietlimiet(Math.rint(generator.nextDouble() * 5000.0));
        klant.setCommentaar(Long.toString(Math.abs(generator.nextLong()), 36));

        return klant;
    }

    private Integer generateKlantNummer() {
        int klantnummer = generator.nextInt(10000);
        if (klantnummer == 0 || (klantnummer &gt;= 100 && klantnummer &lt;= 109)) {
            return 42;
        } else {
            return klantnummer;
        }
    }
}

When the JMS set-up is succesfully tested, we can continue with integrating JMS into the CustomCacheStore. We alter the CustomCacheStore such that a message is send when a customer is deleted. In this case the CustomCacheStore looks as follows

package model.util.readwrite;

import com.tangosol.net.cache.AbstractCacheStore;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.orm.hibernate3.HibernateTemplate;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class CustomCacheStore extends AbstractCacheStore {

    private Integer batchSize = 128;
    private HibernateTemplate hibernateTemplate;
    private JmsTemplate jmsTemplate;
    private String entityName;

    public Integer getBatchSize() {
        return batchSize;
    }

    public void setBatchSize(Integer batchSize) {
        this.batchSize = batchSize;
    }

    public HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    public JmsTemplate getJmsTemplate() {
        return jmsTemplate;
    }

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public String getEntityName() {
        return entityName;
    }

    public void setEntityName(String entityName) {
        this.entityName = entityName;
    }

    @Override
    public Object load(Object key) {
        return findEntity(key);
    }

    @Override
    public void store(Object key, Object value) {
        getHibernateTemplate().saveOrUpdate(value);
    }

    @Override
    public void storeAll(Map mapEntries) {
        if (getBatchSize() == 0 || mapEntries.size() &lt; getBatchSize()) {
            storeBatch(mapEntries);
        } else {
            Map batch = new HashMap(getBatchSize());

            while (!mapEntries.isEmpty()) {
                Iterator iterator = mapEntries.entrySet().iterator();
                while (iterator.hasNext() && batch.size() &lt; getBatchSize()) {
                    Map.Entry entry = (Map.Entry) iterator.next();
                    batch.put(entry.getKey(), entry.getValue());
                }
                storeBatch(batch);
                mapEntries.keySet().removeAll(batch.keySet());
                batch.clear();
            }
        }
    }

    @Override
    public void erase(Object key) {
        final Object find = findEntity(key);
        if (find != null) {
            getHibernateTemplate().delete(find);

            getJmsTemplate().send(new MessageCreator() {
                public Message createMessage(Session session) throws JMSException {
                    TextMessage message = session.createTextMessage();
                    message.setText("Deleted " + find.toString());
                    return message;
                }
            });
        }
    }

    private void storeBatch(Map batch) {
        getHibernateTemplate().saveOrUpdateAll(batch.values());
    }

    private Object findEntity(Object key) {
        Object object = null;
        if (key instanceof Integer) {
            object = getHibernateTemplate().get(getEntityName(), (Integer) key);
        }
        return object;
    }
}

The complete Spring config file looks as follows:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="cacheStore" class="model.util.readwrite.CustomCacheStore" scope="prototype">
        <property name="batchSize" value="256"/>
        <property name="hibernateTemplate" ref="hibernateTemplate"/>
        <property name="jmsTemplate" ref="jmsTemplate"/>
    </bean>
    <bean id="cacheStoreProxy" parent="transactionTemplate">
        <property name="target" ref="cacheStore"/>
        <property name="proxyInterfaces" value="com.tangosol.net.cache.CacheStore"/>
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingResources">
            <list>
                <value>model/entities/Klant.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.c3p0.min_size">${connectionpool.minimum.size}</prop>
                <prop key="hibernate.c3p0.max_size">${connectionpool.maximum.size}</prop>
                <prop key="hibernate.c3p0.timeout">${connectionpool.timeout}</prop>
                <prop key="hibernate.c3p0.max_statements">${connectionpool.maximum.statements}</prop>
                <prop key="hibernate.c3p0.idle_test_period">${connectionpool.idle.period}</prop>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
            </props>
        </property>
    </bean>
    <bean id="dataSource" class="oracle.jdbc.pool.OracleDataSource" destroy-method="close">
        <property name="driverType" value="${jdbc.driverClassName}"/>
        <property name="URL" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:spring-config.properties</value>
            </list>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <bean id="transactionTemplate" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
          abstract="true">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="store*">PROPAGATION_REQUIRED</prop>
                <prop key="erase*">PROPAGATION_REQUIRED</prop>
                <prop key="load*">PROPAGATION_SUPPORTS,readOnly</prop>
            </props>
        </property>
    </bean>
    <!-- JMS configuration -->
    <bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
        <property name="environment">
            <props>
                <prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
                <prop key="java.naming.provider.url">t3://172.31.0.112:7002,172.31.0.112:7003</prop>
            </props>
        </property>
    </bean>
    <bean id="connectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="jndiTemplate"/>
        <property name="jndiName" value="jms/ConnectionFactory"/>
    </bean>
    <bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="jndiTemplate"/>
        <property name="jndiName" value="jms/CompanyQueue"/>
    </bean>
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="defaultDestination" ref="destination"/>
    </bean>
    <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="destination"/>
        <property name="messageListener" ref="receiver"/>
    </bean>
    <bean id="sender" class="model.util.jms.JMSSender">
        <property name="jmsTemplate" ref="jmsTemplate"/>
    </bean>
    <bean id="receiver" class="model.util.jms.JMSReceiver"/>
</beans>

Test the set-up by using the Test class presented in the ‘basic set-up’ section.

References

[1] Coherence Integration Guide.