1 /***
2 * Redistribution and use of this software and associated documentation
3 * ("Software"), with or without modification, are permitted provided
4 * that the following conditions are met:
5 *
6 * 1. Redistributions of source code must retain copyright
7 * statements and notices. Redistributions must also contain a
8 * copy of this document.
9 *
10 * 2. Redistributions in binary form must reproduce the
11 * above copyright notice, this list of conditions and the
12 * following disclaimer in the documentation and/or other
13 * materials provided with the distribution.
14 *
15 * 3. The name "Exolab" must not be used to endorse or promote
16 * products derived from this Software without prior written
17 * permission of Exoffice Technologies. For written permission,
18 * please contact info@exolab.org.
19 *
20 * 4. Products derived from this Software may not be called "Exolab"
21 * nor may "Exolab" appear in their names without prior written
22 * permission of Exoffice Technologies. Exolab is a registered
23 * trademark of Exoffice Technologies.
24 *
25 * 5. Due credit should be given to the Exolab Project
26 * (http://www.exolab.org/).
27 *
28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39 * OF THE POSSIBILITY OF SUCH DAMAGE.
40 *
41 * Copyright 2001-2003 (C) Exoffice Technologies Inc. All Rights Reserved.
42 *
43 * $Id: DestinationCache.java,v 1.34 2003/12/29 13:09:15 tanderson Exp $
44 *
45 * Date Author Changes
46 * 3/1/2001 jima Created
47 */
48 package org.exolab.jms.messagemgr;
49
50 import java.sql.Connection;
51 import java.util.Collections;
52 import java.util.Iterator;
53 import java.util.LinkedList;
54 import java.util.List;
55
56 import javax.transaction.TransactionManager;
57
58 import org.apache.commons.logging.Log;
59 import org.apache.commons.logging.LogFactory;
60
61 import org.exolab.jms.Identifiable;
62 import org.exolab.jms.client.JmsDestination;
63 import org.exolab.jms.gc.GarbageCollectable;
64 import org.exolab.jms.lease.LeaseEventListenerIfc;
65 import org.exolab.jms.message.MessageHandle;
66 import org.exolab.jms.message.MessageImpl;
67 import org.exolab.jms.persistence.DatabaseService;
68 import org.exolab.jms.persistence.PersistenceException;
69 import org.exolab.jms.persistence.SQLHelper;
70 import org.exolab.jms.util.UUID;
71
72
73 /***
74 * A DestinationCache is used to cache messages for a particular destination.
75 * <p>
76 * It implements {@link MessageManagerEventListener} in order to be notified
77 * of messages being added to and removed from
78 * {@link MessageMgr}
79 * <p>
80 * A {@link ConsumerEndpoint} registers with a {@link DestinationCache} to
81 * receive messages for a particular destination.
82 * <p>
83 * In all instances this class doesn't deal with <code>Message</code> objects
84 * directly, but instead uses their corresponding {@link MessageHandle},
85 * which is far more lightweight.
86 * <p>
87 * A two level cache is used to facilitate quick seeding of registered
88 * consumers and quick per-consumer-acknowledgment strategy. The two level
89 * cache includes this, the DestinationCache, at the highest level and then
90 * {@link ConsumerManager} at the lowest level.
91 * <p>
92 * In addition to registering {@link ConsumerEndpoint} objects the cache also
93 * supports {@link DestinationCacheEventListener}s. A listener will be
94 * notified when messages are added to the cache but do not actually consume
95 * messages. A cache can have one or more registered listeners. This feature is
96 * predominately used by browsers or iterators of destinations.
97 * <p>
98 * Clients can also become lifecycle listeners for this object to get notified
99 * during initialization and shutdwon.
100 * <p>
101 * This cache is ordered on priority.
102 *
103 * @version $Revision: 1.34 $ $Date: 2003/12/29 13:09:15 $
104 * @author <a href="mailto:jima@exoffice.com">Jim Alateras</a>
105 */
106 public abstract class DestinationCache
107 implements MessageManagerEventListener, Identifiable,
108 LeaseEventListenerIfc, GarbageCollectable {
109
110 /***
111 * The identity of this object
112 */
113 private String _id;
114
115 /***
116 * Create a message cache for this destination
117 */
118 private MessageCache _cache = new MessageCache();
119
120 /***
121 * Maintain the maximum size of this cache. When the cache reaches this
122 * size then messages will be lost. We should only drop transient messages
123 * and maintain persistent messages. Remove transient messages from the
124 * top of the cache and replace them with persistent messages.
125 */
126 private int _maximumSize = Integer.MAX_VALUE;
127
128 /***
129 * This is the list of consumers that have subscribed to this cache. It
130 * hosts both durable and transient subscribers
131 */
132 protected List _consumers =
133 Collections.synchronizedList(new LinkedList());
134
135 /***
136 * The message lease helper is used to manage leases for messages
137 * cached by the destination
138 */
139 protected MessageLeaseHelper _leaseHelper = null;
140
141 /***
142 * The logger
143 */
144 private static final Log _log = LogFactory.getLog(DestinationCache.class);
145
146 /***
147 * Construct a message cache for a particular destination.
148 */
149 DestinationCache() {
150 _id = UUID.next();
151 }
152
153 /***
154 * Register this destination with the message manager. If it cannot
155 * initialise then throw FailedToInitializeException
156 */
157 void init() throws FailedToInitializeException {
158 // moved to after lease helper creation
159 //MessageMgr.instance().addEventListener(getDestination(), this);
160
161 try {
162 _leaseHelper = new MessageLeaseHelper(this);
163 } catch (PersistenceException exception) {
164 String msg = "Failed to initialise destination cache";
165 _log.error(msg, exception);
166 throw new FailedToInitializeException(
167 msg + ":" + exception.getMessage());
168 }
169
170 MessageMgr.instance().addEventListener(getDestination(), this);
171 }
172
173 /***
174 * Register this destination with the message manager and create the
175 * lease helper. The {@link MessageLeaseHelper} is initialized using the
176 * specified {@link Connection}.
177 * <p>
178 * If there are problems with the initialization then throw
179 * FailedToInitializeException
180 *
181 * @param connection - the connection to use
182 * @throws FailedToInitializeException
183 */
184 void init(Connection connection)
185 throws FailedToInitializeException {
186 MessageMgr.instance().addEventListener(getDestination(), this);
187
188 try {
189 _leaseHelper = new MessageLeaseHelper(connection, this);
190 } catch (Exception exception) {
191 // rethrow
192 throw new FailedToInitializeException("Error initialising " +
193 exception.toString());
194 }
195 }
196
197 /***
198 * Set the maximum size of the cache. If there are more than this number
199 * of messages in the cache the {@link CacheEvictionPolicy} is enforced
200 * to remove messages.
201 *
202 * @param size - maximum number of messages a destination can hold
203 */
204 public void setMaximumSize(int size) {
205 _maximumSize = size;
206 }
207
208 /***
209 * Return the cache's maximum size
210 *
211 * @return int - size of cache
212 */
213 public int getMaximumSize() {
214 return _maximumSize;
215 }
216
217 /***
218 * Return a reference to the underlying destination
219 *
220 * @return JmsDestination
221 */
222 abstract public JmsDestination getDestination();
223
224 /***
225 * Return the string form of the destination
226 *
227 * @return String
228 */
229 public String getDestinationByName() {
230 return getDestination().getName();
231 }
232
233 /***
234 * Set the {@link CacheEvictionPolicy} for this object. This determines
235 * how messages are removed when the cache's upper limit is reached.
236 *
237 * @param policy the eviction policy
238 */
239 public void setCacheEvictionPolicy(CacheEvictionPolicy policy) {
240 // not implemented
241 }
242
243 /***
244 * Register a consumer with this cache. Part of the registration process
245 * will be to seed the {@link ConsumerEndpoint} with an initial list of
246 * messages that it needs to consume and then feed messages to it through
247 * the specified listener object.
248 * <p>
249 * Messages are subsequently passed down to the consumer's through the
250 * listener, as they enter the DestinationCache.
251 *
252 * @param consumer - message consumer for this destination
253 * @return boolean - true if registered and false otherwise
254 */
255 public boolean registerConsumer(ConsumerEndpoint consumer) {
256
257 boolean result = false;
258
259 // check to see that the consumer is actually one for this
260 // destination
261 if (consumer.getDestination().equals(getDestination())) {
262 if (!_consumers.contains(consumer)) {
263 _consumers.add(consumer);
264 consumer.setMaximumSize(this.getMaximumSize());
265 result = true;
266 }
267 }
268
269 return result;
270 }
271
272 /***
273 * Remove the consumer for the list of registered consumers. If the
274 * consumer does not exist then the call fails silently.
275 *
276 * @param consumer - consumer to remove.
277 */
278 public void unregisterConsumer(ConsumerEndpoint consumer) {
279 if (_consumers.contains(consumer)) {
280 _consumers.remove(consumer);
281 } else {
282 }
283 }
284
285 /***
286 * Return an enmeration of all consumers attached to this cache.
287 *
288 * @return Iterator - list of registered consumers
289 */
290 public Iterator getConsumers() {
291 return _consumers.iterator();
292 }
293
294 /***
295 * Return the consumers as an array of {@link ConsumerEndpoint} objects
296 * This is a safer way to get a list of consumers since it avoids
297 * concurrent modification exceptions
298 *
299 * @return Object[] - an array of ConsumerEndpoint objects
300 */
301 Object[] getConsumersByArray() {
302 return _consumers.toArray();
303 }
304
305 /***
306 * This method is called when the {@link MessageMgr} adds a message
307 * for this destination to the cache
308 *
309 * @param message - message added to cache
310 */
311 abstract public boolean messageAdded(JmsDestination destination,
312 MessageImpl message);
313
314 /***
315 * This method is called when the {@link MessageMgr} removes a
316 * message from the cache.
317 *
318 * @param message - message removed from cache
319 */
320 abstract public void messageRemoved(JmsDestination destination,
321 MessageImpl message);
322
323 /***
324 * Return the number of messages currently active for this destination
325 *
326 * @return int - number of active messages
327 */
328 public int getMessageCount() {
329 return _cache.getHandleCount();
330 }
331
332 /***
333 * Notify the listeners that a non-persistent message has been added to the
334 * cache
335 *
336 * @param handle - message that was added
337 * @return boolean - true of at least one listener has processed itx
338 */
339 abstract boolean notifyOnAddMessage(MessageImpl message);
340
341 /***
342 * Notify the listeners that a non-persistent message has been removed form
343 * the cache
344 *
345 * @param handle - message that was removed
346 */
347 abstract void notifyOnRemoveMessage(MessageImpl message);
348
349 /***
350 * Notify the listeners that a persistent message has been added to the
351 * cache
352 *
353 * @param connection - the persistent connection to use
354 * @param handle - message that was added
355 * @return boolean - true of at least one listener has processed it
356 * @throws PersistenceException - if there is a persistence related error
357 */
358 boolean notifyOnAddPersistentMessage(Connection connection,
359 MessageImpl message)
360 throws PersistenceException {
361
362 //default implementation
363 return true;
364 }
365
366 /***
367 * Notify the listeners that a persistent message has been removed form
368 * the cache
369 *
370 * @param connection - the persistent connection to use.
371 * @param handle - message that was removed
372 * @throws PersistenceException - if there is a persistence related error
373 */
374 void notifyOnRemovePersistentMessage(Connection connection,
375 MessageImpl message)
376 throws PersistenceException {
377 //default implementation is empty
378 }
379
380 /***
381 * Check whether there are any attached consumers to this cache
382 *
383 * @return boolean - true if there are attached consumers
384 */
385 abstract boolean hasActiveConsumers();
386
387 /***
388 * This method is called whenever a lease expires. It passes the
389 * object that has expired.
390 *
391 * @param leasedObject reference to the leased object
392 */
393 public void onLeaseExpired(Object leasedObject) {
394 if (leasedObject != null) {
395 MessageHandle handle = (MessageHandle) leasedObject;
396
397 // retrieve an instance of the message
398 MessageImpl message = resolveExpiredMessage(handle);
399
400 // determine whether the message is persistent or not and take
401 // the corresponding action
402 if (handle instanceof PersistentMessageHandle) {
403 Connection connection = null;
404 try {
405 connection = DatabaseService.getConnection();
406 persistentMessageRemoved(connection, getDestination(),
407 message);
408 connection.commit();
409 } catch (Exception exception) {
410 SQLHelper.rollback(connection);
411 _log.error("Failure in onLeaseExpired", exception);
412 } finally {
413 SQLHelper.close(connection);
414 }
415 } else {
416 // notify it's listeners that the non-persistent message has
417 // been removed
418 messageRemoved(getDestination(), message);
419 }
420 }
421 }
422
423 /***
424 * Determines if this cache can be destroyed
425 *
426 * @return <code>true</code> if the cache can be destroyed, otherwise
427 * <code>false</code>
428 */
429 public abstract boolean canDestroy();
430
431 /***
432 * Check to see if the message has a TTL. If so then set up a lease
433 * for it. An expiry time of 0 means that the message never expires
434 *
435 * @param message - message to add
436 */
437 void checkMessageExpiry(MessageImpl message) {
438 if (message != null) {
439 _leaseHelper.addLease(message);
440 }
441 }
442
443 /***
444 * Destory this cache.
445 */
446 synchronized void destroy() {
447 // clear the cache
448 _cache.clear();
449
450 // remove the consumers
451 _consumers.clear();
452
453 // unregister itself from the message manager
454 MessageMgr.instance().removeEventListener(getDestination(), this);
455
456 // remove the lease
457 _leaseHelper.clear();
458 }
459
460 /***
461 * Close the cache and unregister all the consumers. Notify any and all
462 * DestinationCacheLifecycleListeners.
463 * <p>
464 * Once the cache is closed it will no longger receive messages for this
465 * destination.
466 */
467 public void shutdown() {
468 destroy();
469 }
470
471 /***
472 * Insert the specified handle to the handles cache.
473 *
474 * @param handle - handle to add
475 */
476 void addMessage(MessageHandle handle) {
477 handle.setConsumerName(getDestination().getName());
478 _cache.addHandle(handle);
479 }
480
481 /***
482 * Add the following handle and corresponding message to their respective
483 * caches
484 *
485 * @param handle - handle to add
486 * @param message - the corresponding message to add
487 */
488 void addMessage(MessageHandle handle, MessageImpl message) {
489 handle.setConsumerName(getDestination().getName());
490 _cache.addMessage(handle, message);
491 }
492
493 /***
494 * Return the message for the specified handle
495 *
496 * @param handle - the handle
497 * @return MessageImpl - the associated message
498 */
499 MessageImpl getMessage(MessageHandle handle) {
500 return _cache.getMessage(handle);
501 }
502
503 /***
504 * Remove the message handle from the cache, if it exists.
505 *
506 * @param handle - handle to remove
507 * @return boolean - true if it was removed
508 */
509 boolean removeMessage(MessageHandle handle) {
510 return _cache.removeHandle(handle);
511 }
512
513 /***
514 * Remove and return the first message handle in the cache
515 *
516 * @return MessageHandle - the first handle or null if cache is empty
517 */
518 final MessageHandle removeFirstMessage() {
519 return _cache.removeFirstHandle();
520 }
521
522 /***
523 * Return the message handles in the cache as an array
524 *
525 * @return Object[] - array of message handles
526 */
527 final Object[] toMessageArray() {
528 return _cache.getHandleArray();
529 }
530
531 /***
532 * Delete the message with the specified handle from the cache
533 *
534 * @param handle - the handle
535 */
536 void deleteMessage(MessageHandle handle) {
537 _cache.removeMessage(handle.getMessageId());
538 }
539
540 // implementation of Identifiable.getId
541 public String getId() {
542 return _id;
543 }
544
545 // implementation of GarbageCollectable.collectGarbage
546 public void collectGarbage(boolean aggressive) {
547 if (aggressive) {
548 // clear all persistent messages in the cache
549 _cache.clearPersistentMessages();
550 if (_log.isDebugEnabled()) {
551 _log.debug("Evicted all persistent messages from cache "
552 + getDestination().getName());
553 }
554 }
555
556 if (_log.isDebugEnabled()) {
557 _log.debug("DESTCACHE -" + getDestination().getName()
558 + " Messages: P[" + _cache.getPersistentCount()
559 + "] T[" + _cache.getTransientCount() + "] Handles: ["
560 + _cache.getHandleCount() + "]");
561 }
562 }
563
564 /***
565 * Resolve an expired message through its handle
566 *
567 * @param handle the expired message's handle
568 * @return the expired message. May be null.
569 */
570 protected MessageImpl resolveExpiredMessage(MessageHandle handle) {
571 return handle.getMessage();
572 }
573
574 }
This page was automatically generated by Maven