net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicator.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicator.java

Source

/**
 *  Copyright 2003-2007 Luck Consulting Pty Ltd
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package net.sf.ehcache.distribution.jgroups;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.Status;
import net.sf.ehcache.distribution.CacheManagerPeerProvider;
import net.sf.ehcache.distribution.CachePeer;
import net.sf.ehcache.distribution.CacheReplicator;

/**
 * @author Pierre Monestie (pmonestie[at]@gmail.com)
 * @author <a href="mailto:gluck@gregluck.com">Greg Luck</a>
 * @version $Id: JGroupsCacheReplicator.java 592 2008-03-17 08:39:56Z gregluck $
 *          <p/> This implements CacheReplicator using JGroups as underlying
 *          replication mechanism The peer provider should be of type
 *          JGroupsCacheManagerPeerProvider It is assumed that the cachepeer is
 *          a JGroupManager
 */
public class JGroupsCacheReplicator implements CacheReplicator {
    /**
     * Teh default interval for async cache replication
     */
    public static final long DEFAULT_ASYNC_INTERVAL = 1000;

    private static final Log LOG = LogFactory.getLog(JGroupsCacheReplicator.class);

    private long asynchronousReplicationInterval = DEFAULT_ASYNC_INTERVAL;

    /**
     * Whether or not to replicate puts
     */
    private boolean replicatePuts;

    /**
     * Whether or not to replicate updates
     */
    private boolean replicateUpdates;

    /**
     * Replicate update via copying, if false via deleting
     */
    private boolean replicateUpdatesViaCopy;

    /**
     * Whether or not to replicate remove events
     */
    private boolean replicateRemovals;

    /**
     * Weather or not to replicate asynchronously. If true a background thread
     * is ran and fire update at a set intervale
     */
    private boolean replicateAsync;

    private ReplicationThread replicationThread;

    private List replicationQueue = new LinkedList();

    private Status status;

    /**
     * Constructor called by factory
     * 
     * @param replicatePuts
     * @param replicateUpdates
     * @param replicateUpdatesViaCopy
     * @param replicateRemovals
     * @param replicateAsync
     */
    public JGroupsCacheReplicator(boolean replicatePuts, boolean replicateUpdates, boolean replicateUpdatesViaCopy,
            boolean replicateRemovals, boolean replicateAsync) {
        super();

        this.replicatePuts = replicatePuts;
        this.replicateUpdates = replicateUpdates;
        this.replicateUpdatesViaCopy = replicateUpdatesViaCopy;
        this.replicateRemovals = replicateRemovals;
        this.replicateAsync = replicateAsync;

        if (replicateAsync) {
            replicationThread = new ReplicationThread();
            replicationThread.start();
        }
        status = Status.STATUS_ALIVE;
    }

    /**
     * Weather or not the cache is replicated asynchronously. If true a
     * background thread is ran and fire update at a set intervale
     * 
     * @return true if replicated asynchronously, false otherwise
     */
    public boolean isReplicateAsync() {
        return replicateAsync;
    }

    /**
     * Wether or not puts are replicated
     * 
     * @return true if puts are replicated, false otherwise
     */
    public boolean isReplicatePuts() {
        return replicatePuts;
    }

    /**
     * wether or not removals are replicated
     * 
     * @return true if removals are replicated, false otherwise
     */
    public boolean isReplicateRemovals() {
        return replicateRemovals;
    }

    /**
     * Wether or not updates are replicated
     * 
     * @return true if replicated, false otherwise
     */
    public boolean isReplicateUpdates() {
        return replicateUpdates;
    }

    /**
     * {@inheritDoc}
     */
    public boolean alive() {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isReplicateUpdatesViaCopy() {
        return replicateUpdatesViaCopy;
    }

    /**
     * {@inheritDoc}
     */
    public boolean notAlive() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public void dispose() {
        status = Status.STATUS_SHUTDOWN;
        flushReplicationQueue();
    }

    /**
     * {@inheritDoc}
     */
    public void notifyElementExpired(Ehcache cache, Element element) {

        // log.trace("Sending out exp el:"+element);

    }

    /**
     * Used to send notification to the peer. If Async this method simply add
     * the element to the replication queue. If not async, searches for the
     * cachePeer and send the Message. That way the class handles both async and
     * sync replication Sending is delegated to the peer (of type JGroupManager)
     * 
     * @param cache
     * @param e
     */
    protected void sendNotification(Ehcache cache, JGroupEventMessage e) {

        if (replicateAsync) {
            addMessageToQueue(e);
            return;
        }
        CacheManagerPeerProvider provider = cache.getCacheManager().getCachePeerProvider();
        List l = provider.listRemoteCachePeers(cache);
        ArrayList a = new ArrayList();

        a.add(e);

        for (int i = 0; i < l.size(); i++) {
            CachePeer peer = (CachePeer) l.get(i);
            try {
                peer.send(a);
            } catch (RemoteException e1) {
                // e1.printStackTrace();
            }
            // peer.
        }

    }

    /**
     * {@inheritDoc}
     */
    public void notifyElementPut(Ehcache cache, Element element) throws CacheException {
        if (notAlive()) {
            return;
        }

        if (isReplicatePuts()) {
            // if (log.isTraceEnabled())
            // log.trace("Sending out add/upd el:" + element);
            replicatePutNotification(cache, element);
        }

    }

    private void replicatePutNotification(Ehcache cache, Element element) {
        if (!element.isKeySerializable()) {
            LOG.warn("Key " + element.getObjectKey() + " is not Serializable and cannot be replicated.");
            return;
        }
        if (!element.isSerializable()) {
            LOG.warn("Object with key " + element.getObjectKey()
                    + " is not Serializable and cannot be updated via copy");
            return;
        }
        JGroupEventMessage e = new JGroupEventMessage(JGroupEventMessage.PUT, (Serializable) element.getObjectKey(),
                element, cache, cache.getName());

        sendNotification(cache, e);
    }

    private void replicateRemoveNotification(Ehcache cache, Element element) {
        if (!element.isKeySerializable()) {
            LOG.warn("Key " + element.getObjectKey() + " is not Serializable and cannot be replicated.");
            return;
        }
        JGroupEventMessage e = new JGroupEventMessage(JGroupEventMessage.REMOVE,
                (Serializable) element.getObjectKey(), null, cache, cache.getName());

        sendNotification(cache, e);
    }

    /**
     * {@inheritDoc}
     */
    public void notifyElementRemoved(Ehcache cache, Element element) throws CacheException {
        if (notAlive()) {
            return;
        }
        if (isReplicateRemovals()) {
            replicateRemoveNotification(cache, element);

        }

    }

    /**
     * {@inheritDoc}
     */
    public void notifyElementUpdated(Ehcache cache, Element element) throws CacheException {
        if (notAlive()) {
            return;
        }
        if (!replicateUpdates) {
            return;
        }

        if (isReplicateUpdatesViaCopy()) {
            replicatePutNotification(cache, element);
        } else {
            replicateRemoveNotification(cache, element);
        }

    }

    /**
     * {@inheritDoc}
     */
    public void notifyElementEvicted(Ehcache cache, Element element) {

    }

    /**
     * {@inheritDoc}
     */
    public void notifyRemoveAll(Ehcache cache) {
        if (isReplicateRemovals()) {
            LOG.trace("Remove all elements called");
            JGroupEventMessage e = new JGroupEventMessage(JGroupEventMessage.REMOVE_ALL, null, null, cache,
                    cache.getName());
            sendNotification(cache, e);
        }

    }

    /**
     * Package protected List of cache peers
     * 
     * @param cache
     * @return a list of {@link CachePeer} peers for the given cache, excluding
     *         the local peer.
     */
    static List listRemoteCachePeers(Ehcache cache) {
        CacheManagerPeerProvider provider = cache.getCacheManager().getCachePeerProvider();
        return provider.listRemoteCachePeers(cache);
    }

    /**
     * {@inheritDoc}
     */
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * The replication thread
     * 
     * @author pierrem
     * 
     */
    private final class ReplicationThread extends Thread {
        public ReplicationThread() {
            super("Replication Thread");
            setDaemon(true);
            setPriority(Thread.NORM_PRIORITY);
        }

        /**
         * RemoteDebugger thread method.
         */
        public final void run() {
            replicationThreadMain();
        }
    }

    private void replicationThreadMain() {
        while (true) {
            // Wait for elements in the replicationQueue
            while (alive() && replicationQueue != null && replicationQueue.size() == 0) {
                try {

                    Thread.sleep(asynchronousReplicationInterval);
                } catch (InterruptedException e) {
                    LOG.debug("Spool Thread interrupted.");
                    return;
                }
            }
            if (notAlive()) {
                return;
            }
            try {
                if (replicationQueue.size() != 0) {
                    flushReplicationQueue();
                }
            } catch (Throwable e) {
                LOG.warn("Exception on flushing of replication queue: " + e.getMessage() + ". Continuing...", e);
            }
        }
    }

    private void addMessageToQueue(JGroupEventMessage msg) {
        synchronized (replicationQueue) {
            replicationQueue.add(msg);
        }
    }

    /**
     * Gets called once per {@link #asynchronousReplicationInterval}. <p/>
     * Sends accumulated messages in bulk to each peer. i.e. if ther are 100
     * messages and 1 peer, 1 RMI invocation results, not 100. Also, if a peer
     * is unavailable this is discovered in only 1 try. <p/> Makes a copy of the
     * queue so as not to hold up the enqueue operations. <p/> Any exceptions
     * are caught so that the replication thread does not die, and because
     * errors are expected, due to peers becoming unavailable. <p/> This method
     * issues warnings for problems that can be fixed with configuration
     * changes.
     */
    private void flushReplicationQueue() {
        List resolvedEventMessages;
        Ehcache cache;
        synchronized (replicationQueue) {
            if (replicationQueue.size() == 0) {
                return;
            }
            resolvedEventMessages = extractAndResolveEventMessages(replicationQueue);
            cache = ((JGroupEventMessage) replicationQueue.get(0)).getCache();
            replicationQueue.clear();
        }

        List cachePeers = listRemoteCachePeers(cache);

        for (int j = 0; j < cachePeers.size(); j++) {
            CachePeer cachePeer = (CachePeer) cachePeers.get(j);
            try {
                cachePeer.send(resolvedEventMessages);
            } catch (UnmarshalException e) {
                String message = e.getMessage();
                if (message.indexOf("Read time out") != 0) {
                    LOG.warn("Unable to send message to remote peer due to socket read timeout. Consider increasing"
                            + " the socketTimeoutMillis setting in the cacheManagerPeerListenerFactory. "
                            + "Message was: " + e.getMessage());
                } else {
                    LOG.debug("Unable to send message to remote peer.  Message was: " + e.getMessage());
                }
            } catch (Throwable t) {
                LOG.warn("Unable to send message to remote peer.  Message was: " + t.getMessage(), t);
            }
        }

    }

    /**
     * Extracts CacheEventMessages and attempts to get a hard reference to the
     * underlying EventMessage <p/> If an EventMessage has been invalidated due
     * to SoftReference collection of the Element, it is not propagated. This
     * only affects puts and updates via copy.
     * 
     * @param replicationQueueCopy
     * @return a list of EventMessages which were able to be resolved
     */
    private static List extractAndResolveEventMessages(List replicationQueueCopy) {
        List list = new ArrayList();
        for (int i = 0; i < replicationQueueCopy.size(); i++) {
            JGroupEventMessage eventMessage = (JGroupEventMessage) replicationQueueCopy.get(i);
            if (eventMessage != null && eventMessage.isValid()) {
                list.add(eventMessage);
            } else {
                LOG.error("Collected soft ref");
            }
        }
        return list;
    }

    /**
     * Get the time interval is ms between asynchronous replication
     * 
     * @return the interval
     */
    public long getAsynchronousReplicationInterval() {
        return asynchronousReplicationInterval;
    }

    /**
     * Set the time inteval for asynchronous replication
     * 
     * @param asynchronousReplicationInterval
     *            the interval between replication
     */
    public void setAsynchronousReplicationInterval(long asynchronousReplicationInterval) {
        this.asynchronousReplicationInterval = asynchronousReplicationInterval;
    }

}