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 }