
net.sf.ehcache.distribution.jms.JMSCacheReplicator Maven / Gradle / Ivy
/**
* Copyright 2003-2008 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.jms;
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;
import net.sf.ehcache.distribution.EventMessage;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author [email protected]
* @author Greg Luck
*/
public class JMSCacheReplicator implements CacheReplicator {
/**
* The default replication interval
*/
public static final long DEFAULT_ASYNC_INTERVAL = 1000;
private static final Logger LOG = Logger.getLogger(JMSCacheReplicator.class.getName());
private long asynchronousReplicationInterval = DEFAULT_ASYNC_INTERVAL;
private boolean replicatePuts;
private boolean replicateUpdates;
private boolean replicateUpdatesViaCopy;
private boolean replicateRemovals;
private boolean replicateAsync;
private List replicationQueue;
private Status status;
/**
* Constructs a replicator
* @param replicatePuts whether to replicate puts
* @param replicateUpdates whether to replicate puts
* @param replicateUpdatesViaCopy whether to replicate updates via copy or by invalidation
* @param replicateRemovals whether to replicate removals
* @param replicateAsync whether to replicate asynchronously
* @param asynchronousReplicationInterval interval for asynchronous(batch) replicates. The default is 1000ms.
*
*/
public JMSCacheReplicator(boolean replicatePuts, boolean replicateUpdates,
boolean replicateUpdatesViaCopy, boolean replicateRemovals,
boolean replicateAsync, long asynchronousReplicationInterval) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("JMSCacheReplicator constructor ( replicatePuts = "
+ replicatePuts + ", replicateUpdates = " + replicateUpdates + ", " +
"replicateUpdatesViaCopy = " + replicateUpdatesViaCopy + ", replicateRemovals = "
+ replicateRemovals + ", replicateAsync = " + replicateAsync + " ) called");
}
replicationQueue = new LinkedList();
this.replicatePuts = replicatePuts;
this.replicateUpdates = replicateUpdates;
this.replicateUpdatesViaCopy = replicateUpdatesViaCopy;
this.replicateRemovals = replicateRemovals;
this.replicateAsync = replicateAsync;
this.asynchronousReplicationInterval = asynchronousReplicationInterval;
if (replicateAsync) {
JMSReplicationThread replicationThread = new JMSReplicationThread();
replicationThread.start();
}
status = Status.STATUS_ALIVE;
}
/**
* {@inheritDoc}
*/
public boolean alive() {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("alive ( ) called ");
}
return status == Status.STATUS_ALIVE;
}
/**
* Returns whether update is through copy or invalidate
*
* @return true if update is via copy, else false if invalidate
*/
public boolean isReplicateUpdatesViaCopy() {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("isReplicateUpdatesViaCopy ( ) called ");
}
return replicateUpdatesViaCopy;
}
/**
* Returns whether the replicator is not active.
*
* @return true if the status is not STATUS_ALIVE
*/
public boolean notAlive() {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("notAlive ( ) called ");
}
return !alive();
}
/**
* @return The asynchronous replication interval, in ms
*/
public long getAsynchronousReplicationInterval() {
return asynchronousReplicationInterval;
}
/**
* Give the listener a chance to cleanup and free resources when no longer needed
*/
public void dispose() {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("dispose ( ) called ");
}
status = Status.STATUS_SHUTDOWN;
if (replicateAsync) {
flushReplicationQueue();
}
}
/**
* Called immediately after an element is evicted from the cache. Evicted in this sense
* means evicted from one store and not moved to another, so that it exists nowhere in the
* local cache.
*
* In a sense the Element has been removed from the cache, but it is different,
* thus the separate notification.
*
* This message is not replicated by this replicator.
*
* @param cache the cache emitting the notification
* @param element the element that has just been evicted
*/
public void notifyElementEvicted(Ehcache cache, Element element) {
//noop
}
/**
* Called immediately after an element is found to be expired. The
* {@link net.sf.ehcache.Cache#remove(Object)} method will block until this method returns.
*
* As the {@link Element} has been expired, only what was the key of the element is known.
*
* Elements are checked for expiry in ehcache at the following times:
*
* - When a get request is made
*
- When an element is spooled to the diskStore in accordance with a MemoryStore
* eviction policy
*
- In the DiskStore when the expiry thread runs, which by default is
* {@link net.sf.ehcache.Cache#DEFAULT_EXPIRY_THREAD_INTERVAL_SECONDS}
*
* If an element is found to be expired, it is deleted and this method is notified.
*
* This message is not replicated by this replicator.
*
* @param cache the cache emitting the notification
* @param element the element that has just expired
*
* Deadlock Warning: expiry will often come from the DiskStore
* expiry thread. It holds a lock to the DiskStorea the time the
* notification is sent. If the implementation of this method calls into a
* synchronized Cache
method and that subsequently calls into
* DiskStore a deadlock will result. Accordingly implementers of this method
* should not call back into Cache.
*/
public void notifyElementExpired(Ehcache cache, Element element) {
//noop
}
/**
* Called immediately after an element has been put into the cache. The
* {@link net.sf.ehcache.Cache#put(net.sf.ehcache.Element)} method
* will block until this method returns.
*
* Implementers may wish to have access to the Element's fields, including value, so the
* element is provided. Implementers should be careful not to modify the element. The
* effect of any modifications is undefined.
*
* @param cache the cache emitting the notification
* @param element the element which was just put into the cache.
*/
public void notifyElementPut(Ehcache cache, Element element) throws CacheException {
if (notAlive()) {
return;
}
if (!replicatePuts) {
return;
}
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("notifyElementPut ( cache = " + cache + ", element = " + element + ") called ");
}
if (!element.isKeySerializable()) {
LOG.warning("Key " + element.getObjectKey() + " is not Serializable and cannot be replicated.");
return;
}
if (!element.isSerializable()) {
LOG.warning("Object with key " + element.getObjectKey() + " is not Serializable and cannot be replicated");
return;
}
replicatePut(cache, element);
}
/**
* Called immediately after an element has been put into the cache and the element already
* existed in the cache. This is thus an update.
*
* The {@link net.sf.ehcache.Cache#put(net.sf.ehcache.Element)} method
* will block until this method returns.
*
* Implementers may wish to have access to the Element's fields, including value, so the
* element is provided. Implementers should be careful not to modify the element. The
* effect of any modifications is undefined.
*
* @param cache the cache emitting the notification
* @param element the element which was just put into the cache.
*/
public void notifyElementUpdated(Ehcache cache, Element element) throws CacheException {
if (notAlive()) {
return;
}
if (!replicateUpdates) {
return;
}
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("notifyElementUpdated ( cache = " + cache + ", element = " + element + ") called ");
}
if (!element.isKeySerializable()) {
LOG.warning("Key " + element.getObjectKey() + " is not Serializable and cannot be replicated.");
return;
}
if (!element.isSerializable()) {
LOG.warning("Object with key " + element.getObjectKey() + " is not Serializable and cannot be replicated");
return;
}
if (replicateUpdatesViaCopy) {
replicatePut(cache, element);
} else {
replicateRemoval(cache, element);
}
}
private void replicatePut(Ehcache cache, Element element) {
JMSEventMessage message = new JMSEventMessage(Action.PUT, element.getKey(), element, cache.getName(), null);
sendNotification(cache, message);
}
/**
* Called immediately after an attempt to remove an element. The remove method will block until
* this method returns.
*
* This notification is received regardless of whether the cache had an element matching
* the removal key or not. If an element was removed, the element is passed to this method,
* otherwise a synthetic element, with only the key set is passed in.
*
* This notification is not called for the following special cases:
*
* - removeAll was called. See {@link #notifyRemoveAll(net.sf.ehcache.Ehcache)}
*
- An element was evicted from the cache.
* See {@link #notifyElementEvicted(net.sf.ehcache.Ehcache, net.sf.ehcache.Element)}
*
*
* @param cache the cache emitting the notification
* @param element the element just deleted, or a synthetic element with just the key set if
* no element was removed.
*/
public void notifyElementRemoved(Ehcache cache, Element element) throws CacheException {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("notifyElementRemoved ( cache = " + cache + ", element = " + element + ")");
}
if (notAlive()) {
return;
}
if (!replicateRemovals) {
return;
}
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("notifyElementRemoved ( cache = " + cache + ", element = " + element + ")");
}
if (!element.isKeySerializable()) {
LOG.warning("Key " + element.getObjectKey() + " is not Serializable and cannot be replicated.");
return;
}
replicateRemoval(cache, element);
}
private void replicateRemoval(Ehcache cache, Element element) {
//uniquely identifies the originator to ensure that a message does not update the originator
JMSEventMessage message = new JMSEventMessage(Action.REMOVE, element.getKey(), null, cache.getName(),
null);
sendNotification(cache, message);
}
/**
* Called during {@link net.sf.ehcache.Ehcache#removeAll()} to indicate that the all
* elements have been removed from the cache in a bulk operation. The usual
* {@link #notifyElementRemoved(net.sf.ehcache.Ehcache, net.sf.ehcache.Element)}
* is not called.
*
* This notification exists because clearing a cache is a special case. It is often
* not practical to serially process notifications where potentially millions of elements
* have been bulk deleted.
*
* @param cache the cache emitting the notification
*/
public void notifyRemoveAll(Ehcache cache) {
if (notAlive()) {
return;
}
if (!replicateRemovals) {
return;
}
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("notifyRemoveAll ( cache = " + cache + ") ");
}
JMSEventMessage message = new JMSEventMessage(Action.REMOVE_ALL, null, null, cache.getName(), null);
sendNotification(cache, message);
}
/**
* Creates a clone of this listener. This method will only be called by ehcache before a
* cache is initialized.
*
* This may not be possible for listeners after they have been initialized. Implementations
* should throw CloneNotSupportedException if they do not support clone.
*
* @return a clone
* @throws CloneNotSupportedException if the listener could not be cloned.
*/
public Object clone() throws CloneNotSupportedException {
super.clone();
return new JMSCacheReplicator(replicatePuts, replicateUpdates,
replicateUpdatesViaCopy, replicateRemovals, replicateAsync, asynchronousReplicationInterval);
}
/**
* Sends the message
*
* @param cache
* @param message
*/
protected void sendNotification(Ehcache cache, JMSEventMessage message) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("sendNotification ( " + message.toString() + " )");
}
if (replicateAsync) {
addMessageToQueue(cache, message);
return;
}
List messages = new ArrayList();
messages.add(message);
//Is only ever one for JMS in the current implementation
for (CachePeer peer : listRemoteCachePeers(cache)) {
try {
peer.send(messages);
} catch (RemoteException e) {
throw new CacheException(e);
}
}
}
/**
* @param cache the cache we are getting peers for
* @return the list of cache peers
*/
protected static List listRemoteCachePeers(Ehcache cache) {
CacheManagerPeerProvider provider = cache.getCacheManager().getCachePeerProvider();
return provider.listRemoteCachePeers(cache);
}
/**
*
*/
private final class JMSReplicationThread extends Thread {
/**
* Contructs a new replication daemon thread with normal priority.
*/
public JMSReplicationThread() {
super("JMS Replication Thread");
setDaemon(true);
setPriority(Thread.NORM_PRIORITY);
}
@Override
public void run() {
replicationThreadMain();
}
}
/**
* Used to hold the JMSEventMessage and the cache the message belongs to
*/
protected static final class AsyncJMSEventMessage {
private Ehcache cache;
private JMSEventMessage message;
/**
* @param cache -
* @param message -
*/
public AsyncJMSEventMessage(Ehcache cache, JMSEventMessage message) {
this.cache = cache;
this.message = message;
}
/**
* @return the cache this replicator is replicating for.
*/
public Ehcache getCache() {
return cache;
}
/**
* @return the message
*/
public JMSEventMessage getMessage() {
return message;
}
}
private void replicationThreadMain() {
while (true) {
// Wait for elements in the replicationQueue
while (alive() && replicationQueue != null
&& replicationQueue.size() == 0) {
try {
Thread.sleep(getAsynchronousReplicationInterval());
} catch (InterruptedException e) {
LOG.fine("Spool Thread interrupted.");
return;
}
}
if (notAlive()) {
return;
}
try {
flushReplicationQueue();
} catch (Throwable e) {
LOG.log(Level.WARNING, "Exception on flushing of replication queue: "
+ e.getMessage() + ". Continuing...", e);
}
}
}
private void addMessageToQueue(Ehcache cache, JMSEventMessage message) {
synchronized (replicationQueue) {
replicationQueue.add(new AsyncJMSEventMessage(cache, message));
}
}
private void flushReplicationQueue() {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("flushReplicationQueue ( ) called ");
}
List replicationQueueCopy;
synchronized (replicationQueue) {
if (replicationQueue.size() == 0) {
return;
}
//free up write queue
replicationQueueCopy = new ArrayList(replicationQueue);
replicationQueue.clear();
}
List messages = new ArrayList(1);
for (AsyncJMSEventMessage message : replicationQueueCopy) {
Ehcache cache = message.getCache();
List cachePeers = listRemoteCachePeers(cache);
messages.add(message.getMessage());
for (CachePeer peer : cachePeers) {
try {
peer.send(messages);
} catch (RemoteException e) {
LOG.warning("Unable to send message to remote peer. Message was: " + e.getMessage() + " continuing to send" +
"remaining messages.");
}
}
messages.clear();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy