View Javadoc

1   /**
2    *  Copyright 2003-2007 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.distribution;
18  
19  import net.sf.ehcache.CacheException;
20  import net.sf.ehcache.Ehcache;
21  import net.sf.ehcache.Element;
22  import net.sf.ehcache.Status;
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  import java.io.Serializable;
27  import java.util.List;
28  
29  /**
30   * Listens to {@link net.sf.ehcache.CacheManager} and {@link net.sf.ehcache.Cache} events and propagates those to
31   * {@link CachePeer} peers of the Cache.
32   *
33   * @author Greg Luck
34   * @version $Id: RMISynchronousCacheReplicator.java 512 2007-07-10 09:18:45Z gregluck $
35   */
36  public class RMISynchronousCacheReplicator implements CacheReplicator {
37  
38      private static final Log LOG = LogFactory.getLog(RMISynchronousCacheReplicator.class.getName());
39  
40  
41      /**
42       * The status of the replicator. Only replicates when <code>STATUS_ALIVE</code>
43       */
44      protected Status status;
45  
46      /**
47       * Whether to replicate puts.
48       */
49      protected final boolean replicatePuts;
50  
51      /**
52       * Whether to replicate updates.
53       */
54      protected final boolean replicateUpdates;
55  
56      /**
57       * Whether an update (a put) should be by copy or by invalidation, (a remove).
58       * <p/>
59       * By copy is best when the entry is expensive to produce. By invalidation is best when
60       * we are really trying to force other caches to sync back to a canonical source like a database.
61       * An example of a latter usage would be a read/write cache being used in Hibernate.
62       * <p/>
63       * This setting only has effect if <code>#replicateUpdates</code> is true.
64       */
65      protected final boolean replicateUpdatesViaCopy;
66  
67      /**
68       * Whether to replicate removes
69       */
70      protected final boolean replicateRemovals;
71  
72      /**
73       * Constructor for internal and subclass use
74       *
75       * @param replicatePuts
76       * @param replicateUpdates
77       * @param replicateUpdatesViaCopy
78       * @param replicateRemovals
79       */
80      public RMISynchronousCacheReplicator(
81              boolean replicatePuts,
82              boolean replicateUpdates,
83              boolean replicateUpdatesViaCopy,
84              boolean replicateRemovals) {
85          this.replicatePuts = replicatePuts;
86          this.replicateUpdates = replicateUpdates;
87          this.replicateUpdatesViaCopy = replicateUpdatesViaCopy;
88          this.replicateRemovals = replicateRemovals;
89          status = Status.STATUS_ALIVE;
90      }
91  
92      /**
93       * Called immediately after an element has been put into the cache. The {@link net.sf.ehcache.Cache#put(net.sf.ehcache.Element)} method
94       * will block until this method returns.
95       * <p/>
96       * Implementers may wish to have access to the Element's fields, including value, so the element is provided.
97       * Implementers should be careful not to modify the element. The effect of any modifications is undefined.
98       *
99       * @param cache   the cache emitting the notification
100      * @param element the element which was just put into the cache.
101      */
102     public void notifyElementPut(final Ehcache cache, final Element element) throws CacheException {
103         if (notAlive()) {
104             return;
105         }
106 
107         if (!replicatePuts) {
108             return;
109         }
110 
111         if (!element.isSerializable()) {
112             if (LOG.isWarnEnabled()) {
113                 LOG.warn("Object with key " + element.getObjectKey() + " is not Serializable and cannot be replicated");
114             }
115             return;
116         }
117 
118 
119         replicatePutNotification(cache, element);
120     }
121 
122     /**
123      * Does the actual RMI remote call
124      *
125      * @param element
126      * @param cache
127      * @throws RemoteCacheException if anything goes wrong with the remote call
128      */
129     private static void replicatePutNotification(Ehcache cache, Element element) throws RemoteCacheException {
130         List cachePeers = listRemoteCachePeers(cache);
131         for (int i = 0; i < cachePeers.size(); i++) {
132             CachePeer cachePeer = (CachePeer) cachePeers.get(i);
133             try {
134                 cachePeer.put(element);
135             } catch (Throwable t) {
136                 throw new RemoteCacheException("Error doing put to remote peer. Message was: " + t.getMessage());
137             }
138         }
139     }
140 
141 
142     /**
143      * Called immediately after an element has been put into the cache and the element already
144      * existed in the cache. This is thus an update.
145      * <p/>
146      * The {@link net.sf.ehcache.Cache#put(net.sf.ehcache.Element)} method
147      * will block until this method returns.
148      * <p/>
149      * Implementers may wish to have access to the Element's fields, including value, so the element is provided.
150      * Implementers should be careful not to modify the element. The effect of any modifications is undefined.
151      *
152      * @param cache   the cache emitting the notification
153      * @param element the element which was just put into the cache.
154      */
155     public void notifyElementUpdated(final Ehcache cache, final Element element) throws CacheException {
156         if (notAlive()) {
157             return;
158         }
159         if (!replicateUpdates) {
160             return;
161         }
162 
163         if (replicateUpdatesViaCopy) {
164             if (!element.isSerializable()) {
165                 if (LOG.isWarnEnabled()) {
166                     LOG.warn("Object with key " + element.getObjectKey() + " is not Serializable and cannot be updated via copy");
167                 }
168                 return;
169             }
170 
171             replicatePutNotification(cache, element);
172         } else {
173             if (!element.isKeySerializable()) {
174                 if (LOG.isWarnEnabled()) {
175                     LOG.warn("Key " + element.getObjectKey() + " is not Serializable and cannot be replicated.");
176                 }
177                 return;
178             }
179 
180             replicateRemovalNotification(cache, (Serializable) element.getObjectKey());
181         }
182     }
183 
184     /**
185      * Called immediately after an attempt to remove an element. The remove method will block until
186      * this method returns.
187      * <p/>
188      * This notification is received regardless of whether the cache had an element matching
189      * the removal key or not. If an element was removed, the element is passed to this method,
190      * otherwise a synthetic element, with only the key set is passed in.
191      * <p/>
192      *
193      * @param cache   the cache emitting the notification
194      * @param element the element just deleted, or a synthetic element with just the key set if
195      *                no element was removed.param element just deleted
196      */
197     public void notifyElementRemoved(final Ehcache cache, final Element element) throws CacheException {
198         if (notAlive()) {
199             return;
200         }
201 
202         if (!replicateRemovals) {
203             return;
204         }
205 
206         if (!element.isKeySerializable()) {
207             if (LOG.isWarnEnabled()) {
208                 LOG.warn("Key " + element.getObjectKey() + " is not Serializable and cannot be replicated.");
209             }
210             return;
211         }
212 
213         replicateRemovalNotification(cache, (Serializable) element.getObjectKey());
214     }
215 
216     /**
217      * Does the actual RMI remote call
218      *
219      * @param key
220      * @param cache
221      * @throws RemoteCacheException if anything goes wrong with the remote call
222      */
223     private static void replicateRemovalNotification(Ehcache cache, Serializable key) throws RemoteCacheException {
224         List cachePeers = listRemoteCachePeers(cache);
225         for (int i = 0; i < cachePeers.size(); i++) {
226             CachePeer cachePeer = (CachePeer) cachePeers.get(i);
227             try {
228                 cachePeer.remove(key);
229             } catch (Throwable e) {
230                 throw new RemoteCacheException("Error doing remove to remote peer. Message was: " + e.getMessage());
231             }
232         }
233     }
234 
235 
236     /**
237      * {@inheritDoc}
238      * <p/>
239      * This implementation does not propagate expiries. It does not need to do anything because the element will
240      * expire in the remote cache at the same time. If the remote peer is not configured the same way they should
241      * not be in an cache cluster.
242      */
243     public final void notifyElementExpired(final Ehcache cache, final Element element) {
244         /*do not propagate expiries. The element should expire in the remote cache at the same time, thus
245           preseerving coherency.
246           */
247     }
248 
249     /**
250      * Called immediately after an element is evicted from the cache. Evicted in this sense
251      * means evicted from one store and not moved to another, so that it exists nowhere in the
252      * local cache.
253      * <p/>
254      * In a sense the Element has been <i>removed</i> from the cache, but it is different,
255      * thus the separate notification.
256      * <p/>
257      * This replicator does not propagate these events
258      *
259      * @param cache   the cache emitting the notification
260      * @param element the element that has just been evicted
261      */
262     public void notifyElementEvicted(final Ehcache cache, final Element element) {
263         /**
264          * do not notify these
265          */
266     }
267 
268 
269     /**
270      * Called during {@link net.sf.ehcache.Ehcache#removeAll()} to indicate that the all
271      * elements have been removed from the cache in a bulk operation. The usual
272      * {@link #notifyElementRemoved(net.sf.ehcache.Ehcache,net.sf.ehcache.Element)}
273      * is not called.
274      * <p/>
275      * This notification exists because clearing a cache is a special case. It is often
276      * not practical to serially process notifications where potentially millions of elements
277      * have been bulk deleted.
278      *
279      * @param cache the cache emitting the notification
280      */
281     public void notifyRemoveAll(final Ehcache cache) {
282         if (notAlive()) {
283             return;
284         }
285 
286         if (!replicateRemovals) {
287             return;
288         }
289 
290         replicateRemoveAllNotification(cache);
291     }
292 
293     private void replicateRemoveAllNotification(Ehcache cache) {
294         List cachePeers = listRemoteCachePeers(cache);
295         for (int i = 0; i < cachePeers.size(); i++) {
296             CachePeer cachePeer = (CachePeer) cachePeers.get(i);
297             try {
298                 cachePeer.removeAll();
299             } catch (Throwable e) {
300                 throw new RemoteCacheException("Error doing removeAll to remote peer. Message was: " + e.getMessage());
301             }
302         }
303     }
304 
305     /**
306      * Package protected List of cache peers
307      *
308      * @param cache
309      * @return a list of {@link CachePeer} peers for the given cache, excluding the local peer.
310      */
311     static List listRemoteCachePeers(Ehcache cache) {
312         CacheManagerPeerProvider provider = cache.getCacheManager().getCachePeerProvider();
313         return provider.listRemoteCachePeers(cache);
314     }
315 
316 
317     /**
318      * @return whether update is through copy or invalidate
319      */
320     public final boolean isReplicateUpdatesViaCopy() {
321         return replicateUpdatesViaCopy;
322     }
323 
324     /**
325      * Asserts that the replicator is active.
326      *
327      * @return true if the status is not STATUS_ALIVE
328      */
329     public final boolean notAlive() {
330         return !alive();
331     }
332 
333     /**
334      * Checks that the replicator is is <code>STATUS_ALIVE</code>.
335      */
336     public final boolean alive() {
337         if (status == null) {
338             return false;
339         } else {
340             return (status.equals(Status.STATUS_ALIVE));
341         }
342     }
343 
344     /**
345      * Give the replicator a chance to cleanup and free resources when no longer needed
346      */
347     public void dispose() {
348         status = Status.STATUS_SHUTDOWN;
349     }
350 
351     /**
352      * Creates a clone of this listener. This method will only be called by ehcache before a cache is initialized.
353      * <p/>
354      * This may not be possible for listeners after they have been initialized. Implementations should throw
355      * CloneNotSupportedException if they do not support clone.
356      *
357      * @return a clone
358      * @throws CloneNotSupportedException if the listener could not be cloned.
359      */
360     public Object clone() throws CloneNotSupportedException {
361         //shutup checkstyle
362         super.clone();
363         return new RMISynchronousCacheReplicator(replicatePuts, replicateUpdates, replicateUpdatesViaCopy, replicateRemovals);
364     }
365 }