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: ConsumerManager.java,v 1.33 2003/09/25 11:24:16 tanderson Exp $
44 *
45 * Date Author Changes * 3/1/2001 jima Created
46 */
47 package org.exolab.jms.messagemgr;
48
49 import java.sql.Connection;
50 import java.util.Enumeration;
51 import java.util.HashMap;
52 import java.util.Iterator;
53 import java.util.LinkedList;
54 import java.util.Vector;
55
56 import javax.jms.DeliveryMode;
57 import javax.jms.InvalidSelectorException;
58 import javax.jms.JMSException;
59 import javax.transaction.TransactionManager;
60
61 import org.apache.commons.logging.Log;
62 import org.apache.commons.logging.LogFactory;
63
64 import org.exolab.core.service.ServiceException;
65 import org.exolab.jms.client.JmsDestination;
66 import org.exolab.jms.client.JmsQueue;
67 import org.exolab.jms.client.JmsTopic;
68 import org.exolab.jms.gc.GarbageCollectable;
69 import org.exolab.jms.gc.GarbageCollectionService;
70 import org.exolab.jms.message.MessageImpl;
71 import org.exolab.jms.persistence.DatabaseService;
72 import org.exolab.jms.persistence.PersistenceAdapter;
73 import org.exolab.jms.persistence.PersistenceException;
74 import org.exolab.jms.persistence.SQLHelper;
75 import org.exolab.jms.scheduler.Scheduler;
76 import org.exolab.jms.server.JmsServerSession;
77
78
79 /***
80 * The consumer manager is responsible for creating and managing the
81 * lifecycle of Consumer. The consumer manager maintains a list of all
82 * active consumers.
83 * <p>
84 * The consumer manager, in an attempt to make better utilization of
85 * resource will also act as a proxy for all inactive durable subscribers
86 * So it will act as a DestinationCacheEventListener and process the
87 * message on behalf of the inactive consumer.
88 *
89 * @version $Revision: 1.33 $ $Date: 2003/09/25 11:24:16 $
90 * @author <a href="mailto:jima@exoffice.com">Jim Alateras</a>
91 */
92 public class ConsumerManager
93 implements DestinationCacheEventListener, GarbageCollectable {
94
95 /***
96 * Maintains a cache of all active endpoints.
97 */
98 private HashMap _endpoints = new HashMap();
99
100 /***
101 * Maintains a list of all unique consumers, durable and non-durable.
102 * Each entry has an associated {@link #ConsumerEntry} record. All
103 * durable subscribers are maintained in memory until they are removed
104 * from the system entirely. All non-durable subscribers are maintained
105 * in memory until their endpoint is removed.
106 */
107 private HashMap _consumerCache = new HashMap();
108
109 /***
110 * Maintais a mapping between destinations and consumers. A destination
111 * can have more than one consumer and a consumer can also be registered
112 * to more than one destination
113 */
114 private HashMap _destToConsumerMap = new HashMap();
115
116 /***
117 * Maintains a list of wildcard subscriptions using subscription name
118 * and the JmsTopic.
119 */
120 private HashMap _wildcardConsumers = new HashMap();
121
122 /***
123 * Cache a copy of the scheduler instance
124 */
125 private Scheduler _scheduler = null;
126
127 /***
128 * The singleton instance of the consumer manager
129 */
130 private static ConsumerManager _instance = null;
131
132 /***
133 * The logger
134 */
135 private static final Log _log = LogFactory.getLog(ConsumerManager.class);
136
137
138 /***
139 * Create the singleton instance of the consumer manager
140 *
141 * @return the singleton instance
142 * @throws ServiceException if the service cannot be initialised
143 */
144 public static ConsumerManager createInstance() throws ServiceException {
145 _instance = new ConsumerManager();
146 return _instance;
147 }
148
149 /***
150 * Return the singleton instance of the ConsumerManager
151 *
152 * @return ConsumerManager
153 */
154 public static ConsumerManager instance() {
155 return _instance;
156 }
157
158 /***
159 * Construct the <code>ConsumerManager</code>
160 *
161 * @throws ServiceException - if it fails to initialise
162 */
163 private ConsumerManager() throws ServiceException {
164 // register with the GarbageCollectionService
165 GarbageCollectionService.instance().register(this);
166
167 init();
168 }
169
170 /***
171 * This method creates an actual durable consumer for the specified and
172 * caches it. It does not create and endpoint. To create the endpoint the
173 * client should call createDurableConsumerEndpoint.
174 *
175 * @param topic - the topic destination
176 * @param name - the consumer name
177 * @exception JMSException - if it cannot be created
178 */
179 public synchronized void createDurableConsumer(JmsTopic topic, String name)
180 throws JMSException {
181
182 PersistenceAdapter adapter = DatabaseService.getAdapter();
183
184 Connection connection = null;
185 try {
186
187 connection = DatabaseService.getConnection();
188
189 // ensure that we are trying to create a durable consumer to an
190 // administered destination.
191 if (!adapter.checkDestination(connection, topic.getName())) {
192 throw new JMSException(
193 "Cannot create durable consumer, name=" + name +
194 ", for non-administered topic=" + topic.getName());
195 }
196
197 if (!adapter.durableConsumerExists(connection, name)) {
198 adapter.addDurableConsumer(connection, topic.getName(), name);
199 }
200
201 connection.commit();
202 // cache the consumer locally
203 addToConsumerCache(name, topic, true);
204 } catch (JMSException exception) {
205 throw exception;
206 } catch (Exception exception) { // PersistenceException, SQLException
207 SQLHelper.rollback(connection);
208 String msg = "Failed to create durable consumer, name=" + name
209 + ", for topic=" + topic.getName();
210 _log.error(msg, exception);
211 throw new JMSException(msg + ": " + exception.getMessage());
212 } finally {
213 SQLHelper.close(connection);
214 }
215 }
216
217
218 /***
219 * This method will remove the durable consumer from the database and
220 * from transient memory only if it exists and is inactive. If there
221 * is an active endpoint then it cannot be deleted and an exception
222 * will be raise.
223 * <p>
224 * If the durable consumer does not exist then an exception is also
225 * raised.
226 *
227 * @param name - the consumer name
228 * @exception JMSException - if it cannot be removed
229 */
230 public synchronized void removeDurableConsumer(String name)
231 throws JMSException {
232 if (_log.isDebugEnabled()) {
233 _log.debug("removeDurableConsumer(name=" + name + ")");
234 }
235
236 // check to see that the durable consumer exists
237 if (!durableConsumerExists(name)) {
238 throw new JMSException("Durable consumer " + name +
239 " is not defined.");
240 }
241 if (isDurableConsumerActive(name)) {
242 throw new JMSException(
243 "Cannot remove durable consumer=" + name
244 + ": consumer is active");
245 }
246
247 // remove it from the persistent store.
248 Connection connection = null;
249 try {
250 connection = DatabaseService.getConnection();
251
252 DatabaseService.getAdapter().removeDurableConsumer(connection,
253 name);
254 // if it has been successfully removed from persistent store then
255 // clear up the transient references.
256 ConsumerEndpoint endpoint = getConsumerEndpoint(name);
257 if (endpoint != null) {
258 deleteConsumerEndpoint(endpoint);
259 }
260 removeFromConsumerCache(name);
261 connection.commit();
262 } catch (Exception exception) { // PersistenceException, SQLException
263 SQLHelper.rollback(connection);
264 String msg = "Failed to remove durable consumer, name=" + name;
265 _log.error(msg, exception);
266 throw new JMSException(msg + ":" + exception.getMessage());
267 } finally {
268 SQLHelper.close(connection);
269 }
270 }
271
272 /***
273 * This method will remove all the durable consumers from the database and
274 * from transient memory whether they are active or not.
275 * <p>
276 * If we have problems removing the durable consumers then throw the
277 * JMSException.
278 *
279 * @param topic the topic to remove consumers for
280 * @throw JMSException if the consumers cannot be removed
281 */
282 public synchronized void removeDurableConsumers(JmsDestination topic)
283 throws JMSException {
284
285 Vector consumers = (Vector) _destToConsumerMap.get(topic);
286 if (consumers != null) {
287 Enumeration entries = consumers.elements();
288 while (entries.hasMoreElements()) {
289 ConsumerEntry entry = (ConsumerEntry) entries.nextElement();
290 if (entry._durable) {
291 // remove the actual durable consumer from transient and
292 // secondary memory.
293 removeDurableConsumer(entry._name);
294 }
295 }
296 }
297
298 // remove all consumers for the specified destination
299 removeFromConsumerCache(topic);
300 }
301
302 /***
303 * Create a transient consumer for the specified destination. The client
304 * can optionally specify a selector to filter messages.
305 * <p>
306 * The clientId parameter is used to indirectly reference the remote client
307 * which is uniquely identifiable within a session and is used during
308 * asynchronous message delivery.
309 *
310 * @param clientId - indirect reference to the remote client
311 * @param destination - consumer for this destination
312 * @param selector - the consumer's selector if specified.
313 * @return transient consumer
314 */
315 public synchronized ConsumerEndpoint createConsumerEndpoint(
316 JmsServerSession session, long clientId, JmsDestination destination,
317 String selector)
318 throws JMSException, InvalidSelectorException {
319
320 if (_log.isDebugEnabled()) {
321 _log.debug("createConsumerEndpoint(session=[sessionId="
322 + session.getSessionId() + "], clientId=" + clientId
323 + ", destination=" + destination
324 + ", selector=" + selector + ")");
325 }
326
327 ConsumerEndpoint endpoint = null;
328 DestinationManager destmgr = DestinationManager.instance();
329
330 // before we create the destination we need to check the
331 // characteristics of the destination. If the destination
332 // is an administered destination then it must be already
333 // defined. If the destination is a temporary destination
334 // then we may need to add it to the cache.
335 if (destination.getPersistent()) {
336 if (!destmgr.destinationExists(destination)) {
337 throw new JMSException("Cannot create consumer endpoint: "
338 + "destination=" + destination
339 + " does not exist");
340 }
341 } else {
342 if (!destmgr.destinationExists(destination)) {
343 destmgr.createDestination(destination);
344 }
345 }
346
347 // determine what type of consumer endpoint to create based on
348 // the destination it subscribes too.
349 if (destination instanceof JmsTopic) {
350 JmsTopic topic = (JmsTopic) destination;
351 endpoint = new TopicConsumerEndpoint(session, clientId, topic,
352 selector, _scheduler);
353 } else if (destination instanceof JmsQueue) {
354 // this seems like a good opportunity to clean up any unreferenced
355 // endpoints attached to this queue.
356 cleanUnreferencedEndpoints(destination);
357
358 // now we can create the endpoint
359 endpoint = new QueueConsumerEndpoint(session, clientId,
360 (JmsQueue) destination, selector, _scheduler);
361 }
362
363 if (endpoint != null) {
364 // add it to the list on managed consumers
365 String id = endpoint.getPersistentId();
366 _endpoints.put(id, endpoint);
367 addToConsumerCache(id, destination, false);
368 }
369
370 return endpoint;
371 }
372
373 /***
374 * Create a durable consumer with the specified well-known name.
375 *
376 * @param session the owning session
377 * @param topic consumer for this topic
378 * @param name the unique subscriber name
379 * @param clientId the remote client identity
380 * @param selector the message selector. May be <code>null</code>
381 * @return the durable consumer endpoint
382 * @throws JMSException if a durable consumer is already active with
383 * the same <code>name</code>, or the <code>topic</code> doesn't exist,
384 * or <code>selector</code> is an invalid selector
385 */
386 public synchronized DurableConsumerEndpoint createDurableConsumerEndpoint(
387 JmsServerSession session, JmsTopic topic, String name,
388 long clientId, String selector)
389 throws JMSException {
390
391 if (_log.isDebugEnabled()) {
392 _log.debug("createDurableConsumerEndpoint(session=[sessionId="
393 + session.getSessionId() + "], topic=" + topic
394 + ", name=" + name + ", clientId=" + clientId
395 + ", selector=" + selector);
396 }
397
398 // check that the durable subscriber is not already registered. If it
399 // is registered then check to see whether the client is still active.
400 DurableConsumerEndpoint endpoint =
401 (DurableConsumerEndpoint) _endpoints.get(name);
402 if (endpoint != null) {
403 if (endpoint.getSession().isClientEndpointActive()) {
404 throw new JMSException(name + " is already registered");
405 } else {
406 // client endpoint must have been lost
407 if (_log.isDebugEnabled()) {
408 _log.debug("Closing session for inactive durable " +
409 "consumer [name=" + name + "]");
410 }
411 endpoint.getSession().close();
412 }
413 }
414
415 // check that the destination actually exists, if the topic
416 // is not a wildcard
417 if (!topic.isWildCard() &&
418 !DestinationManager.instance().destinationExists(topic)) {
419 throw new JMSException("Cannot create a durable consumer for "
420 + topic);
421 }
422
423 // if we get this far then we need to create the durable consumer
424 endpoint = new DurableConsumerEndpoint(session, clientId, topic, name,
425 selector, _scheduler);
426 _endpoints.put(endpoint.getPersistentId(), endpoint);
427
428 return endpoint;
429 }
430
431 /***
432 * Check whether there are active durable consumers for the specified
433 * destination.
434 *
435 * @param topic the destination to check
436 * @param <code>true</code> if there is at least one active consumer
437 */
438 public synchronized boolean hasActiveDurableConsumers(
439 JmsDestination topic) {
440
441 boolean result = false;
442 Vector consumers = (Vector) _destToConsumerMap.get(topic);
443 if (consumers != null) {
444 Enumeration iterator = consumers.elements();
445 while (iterator.hasMoreElements()) {
446 ConsumerEntry entry = (ConsumerEntry) iterator.nextElement();
447 if (entry._durable) {
448 result = true;
449 break;
450 }
451 }
452 }
453
454 return result;
455 }
456
457 /***
458 * Create a browser for the specified destination and the selector. A
459 * browser is responsible for passing all messages back to the client
460 * that reside on the queue
461 *
462 * @param session the owning session
463 * @param clientId the remote client identity, which is session scoped
464 * @param queue the queue destination cache
465 * @param selector optional filter
466 * @return the queue browser endpoint
467 */
468 public synchronized ConsumerEndpoint createQueueBrowserEndpoint(
469 JmsServerSession session, long clientId, JmsQueue queue,
470 String selector)
471 throws JMSException {
472
473 ConsumerEndpoint consumer = null;
474
475 if (queue != null) {
476 consumer = new QueueBrowserEndpoint(session, clientId,
477 queue, selector, _scheduler);
478 } else {
479 throw new JMSException("Cannot create a browser for a null queue");
480 }
481
482 String id = consumer.getPersistentId();
483 _endpoints.put(id, consumer);
484 addToConsumerCache(id, queue, false);
485
486 return consumer;
487 }
488
489 /***
490 * Destroy the endpoint associated with the specified durable consumer
491 *
492 * @param name - name of the durable consumer
493 * @exception JMSException - if itt cannot complete the request
494 */
495 public synchronized void deleteDurableConsumerEndpoint(String name)
496 throws JMSException {
497
498 if (_log.isDebugEnabled()) {
499 _log.debug("deleteDurableConsumerEndpoint(name=" + name + ")");
500 }
501
502 ConsumerEntry entry = (ConsumerEntry) _consumerCache.get(name);
503 if (entry != null) {
504 if (entry._durable) {
505 deleteConsumerEndpoint((ConsumerEndpoint) _endpoints.get(name));
506 } else {
507 throw new JMSException(name + " is not a durable subscriber");
508 }
509 } else {
510 // ignore since the consumer is not active
511 if (_log.isDebugEnabled()) {
512 _log.debug("deleteDurableConsumerEndpoint(name=" + name
513 + "): failed to locate consumer");
514 }
515 }
516 }
517
518 /***
519 * Destroy the specified consumer
520 *
521 * @param consumer the consumer to destroy
522 */
523 public synchronized void deleteConsumerEndpoint(
524 ConsumerEndpoint consumer) {
525
526 if (_log.isDebugEnabled()) {
527 _log.debug("deleteConsumerEndpoint(consumer=[clientId="
528 + consumer.getClientId() + ", destination="
529 + consumer.getDestination() + ", session=[sessionId="
530 + consumer.getSession().getSessionId() + "]])");
531 }
532
533 String id = consumer.getPersistentId();
534
535 // if the consumer is currently active then delete it
536 ConsumerEndpoint existing = (ConsumerEndpoint) _endpoints.get(id);
537 if (existing != null) {
538 // unregister itself from all the caches
539 consumer.unregister();
540
541 // remove it from the list of active endpoints
542 // As a fix for bug 759752, only remove the consumer if it
543 // matches the existing one
544 if (consumer.getId().equals(existing.getId())) {
545 _endpoints.remove(id);
546 } else {
547 if (_log.isDebugEnabled()) {
548 _log.debug("Existing endpoint doesn't match that to " +
549 "be deleted - retaining");
550 }
551 }
552
553 // close the endpoint
554 consumer.close();
555
556 // remove it from the consumer cache if and only if it is a
557 // non-durable subscriber
558 if (!(consumer instanceof DurableConsumerEndpoint)) {
559 try {
560 removeFromConsumerCache(id);
561 } catch (JMSException exception) {
562 _log.debug("Failed to remove " + id + " from the cache",
563 exception);
564 }
565 }
566 }
567 }
568
569 /***
570 * Return the consumer with the specified identity
571 *
572 * @param id - identity of the consumer
573 * @return Consumer - associated consumer object or null
574 */
575 public ConsumerEndpoint getConsumerEndpoint(String id) {
576 return (ConsumerEndpoint) _endpoints.get(id);
577 }
578
579 /***
580 * Return a list of {@link ConsumerEndpoint} objects, both transient and
581 * durable that are currently active.
582 *
583 * @return Iterator - iterator of {@link ConsumerEndpoint} objects
584 */
585 public Iterator consumerEndpoints() {
586 return _endpoints.values().iterator();
587 }
588
589 /***
590 * Return a list of consumer names id's currently active in the
591 * consumer manager.
592 *
593 * @return Iterator - iterator of {#link String} ids
594 */
595 public Iterator consumerIds() {
596 return _endpoints.keySet().iterator();
597 }
598
599 /***
600 * Check whether a consumer, with the specified identity actually
601 * exists.
602 *
603 * @param id - identity of the consumer
604 * @return boolean - true if one exists
605 */
606 public boolean exists(String id) {
607 return (getConsumerEndpoint(id) != null);
608 }
609
610 /***
611 * Check whether there is an active consumer for the specified
612 * destination
613 *
614 * @param destination - the destination to check
615 * @return boolean - true if it exists
616 */
617 public boolean hasActiveConsumers(JmsDestination destination)
618 throws JMSException {
619 boolean result = false;
620
621 Object[] endpoints = _endpoints.values().toArray();
622 for (int index = 0; index < endpoints.length; index++) {
623 ConsumerEndpoint endpoint = (ConsumerEndpoint) endpoints[index];
624 JmsDestination endpoint_dest = endpoint.getDestination();
625
626 if ((destination instanceof JmsTopic) &&
627 (endpoint_dest instanceof JmsTopic) &&
628 (((JmsTopic) endpoint_dest).isWildCard())) {
629 if (((JmsTopic) endpoint_dest).match((JmsTopic) destination)) {
630 result = true;
631 break;
632 }
633 } else {
634 if (endpoint_dest.equals(destination)) {
635 result = true;
636 break;
637 }
638 }
639 }
640
641 return result;
642 }
643
644 /***
645 * Check whether a particular durable consumer is active
646 *
647 * @param name - the consumer name
648 * @return boolean - true if active
649 */
650 public boolean isDurableConsumerActive(String name) {
651 return (_endpoints.get(name) != null);
652 }
653
654 /***
655 * Return the destination assoicated with the specified durable
656 * consumer.
657 *
658 * @param name - consumer name
659 * @return JmsDestination - the destination is it registered under or null
660 */
661 public JmsDestination getDestinationForConsumerName(String name) {
662 ConsumerEntry entry = (ConsumerEntry) _consumerCache.get(name);
663 return (entry != null) ? entry._destination : null;
664 }
665
666 /***
667 * Check if the specified durable consumer exists
668 *
669 * @param name - the name of the durable consumer
670 * @return boolean - true if successful
671 */
672 public boolean durableConsumerExists(String name) {
673 boolean result = false;
674 ConsumerEntry entry = (ConsumerEntry) _consumerCache.get(name);
675 if ((entry != null) &&
676 (entry._durable)) {
677 result = true;
678 }
679
680 return result;
681 }
682
683 /***
684 * This method will check that the name-destination pair actually
685 * are valid and exist as a DurableConsumer entity
686 *
687 * @param topic - the name of the topic
688 * @param name - the name of the durable consumer
689 * @return boolean - true if valid and false otherwise
690 */
691 public boolean validSubscription(String topic, String name) {
692
693 boolean result = false;
694 ConsumerEntry entry = (ConsumerEntry) _consumerCache.get(name);
695
696 if ((entry != null) &&
697 (entry._destination != null) &&
698 (entry._destination.getName().equals(topic))) {
699 result = true;
700 }
701
702 return result;
703 }
704
705 // implementation of DestinationCacheEventListener.messageAdded
706 synchronized public boolean messageAdded(MessageImpl message) {
707 return false;
708 }
709
710 // implementation of DestinationCacheEventListener.messageRemoved
711 synchronized public boolean messageRemoved(MessageImpl message) {
712 return false;
713 }
714
715 // implementation of DestinationCacheEventListener.persistentMessageAdded
716 synchronized public boolean persistentMessageAdded(Connection connection,
717 MessageImpl message)
718 throws PersistenceException {
719 try {
720 JmsDestination dest = (JmsDestination) message.getJMSDestination();
721
722 // check to ensure that the message is not of type queue. If it
723 // is then we don't want to process it here. All the processing
724 // for non-resident queues are done at the cache level
725 if (dest instanceof JmsQueue) {
726 return false;
727 }
728
729 // for each durable consumer we need to add a persistent handle
730 Vector names = getDurableConsumersForDest((JmsTopic) dest);
731 while (names.size() > 0) {
732 String name = (String) names.remove(0);
733 MessageHandleFactory.createPersistentHandle(connection, dest,
734 name, message);
735 }
736 } catch (JMSException exception) {
737 // rethrow as a PersistenceException...but does it make sense
738 throw new PersistenceException(
739 "Failed to create persistent handle", exception);
740 }
741
742 // return false, even though we processed it to force the
743 // message manager not to keep the message in memory.
744 return false;
745 }
746
747 // implementation of DestinationCacheEventListener.persistentMessageRemoved
748 synchronized public boolean persistentMessageRemoved(Connection connection,
749 MessageImpl message)
750 throws PersistenceException {
751 try {
752
753 JmsDestination dest = (JmsDestination) message.getJMSDestination();
754
755 // check to ensure that the message is not of type queue. If it
756 // is then we don't want to process it here.
757 if (dest instanceof JmsQueue) {
758 return false;
759 }
760
761 Vector consumers = (Vector) _destToConsumerMap.get(dest);
762 if (consumers != null) {
763 // now search through the list of inactive durable consumers
764 // and remove it from their cache
765 Enumeration entries = consumers.elements();
766 while (entries.hasMoreElements()) {
767 ConsumerEntry entry = (ConsumerEntry) entries.nextElement();
768 if ((entry._durable) &&
769 (!_endpoints.containsKey(entry._name))) {
770 MessageHandleFactory.destroyPersistentHandle(connection,
771 dest, entry._name, message);
772 }
773 }
774 }
775 } catch (PersistenceException exception) {
776 throw exception;
777 } catch (Exception exception) {
778 // rethrow as a PersistenceException...but does it make sense
779 throw new PersistenceException(
780 "Exception in ConsumerManager.persistentMessageRemoved " +
781 exception.toString());
782 }
783
784 return true;
785 }
786
787 /***
788 * Destroy this manager. This is brutal and final
789 */
790 public synchronized void destroy() {
791
792 // clean up all the destinations
793 Object[] endpoints = _endpoints.values().toArray();
794 for (int index = 0; index < endpoints.length; index++) {
795 deleteConsumerEndpoint((ConsumerEndpoint) endpoints[index]);
796 }
797 _endpoints.clear();
798
799 // remove cache data structures
800 _consumerCache.clear();
801 _consumerCache = null;
802 _destToConsumerMap.clear();
803 _destToConsumerMap = null;
804 _wildcardConsumers.clear();
805 _wildcardConsumers = null;
806
807 // reset the singleton
808 _instance = null;
809 }
810
811 /***
812 * Return a list of durable subscribers for the specified destination
813 *
814 * @return Vector - a vector of strings, which denote the name
815 */
816 public synchronized Vector getDurableConsumersForDest(JmsTopic dest) {
817 Vector names = new Vector();
818
819 Vector consumers = (Vector) _destToConsumerMap.get(dest);
820 if (consumers != null) {
821 Enumeration entries = consumers.elements();
822 while (entries.hasMoreElements()) {
823 ConsumerEntry entry = (ConsumerEntry) entries.nextElement();
824 if (entry._durable) {
825 names.add(entry._name);
826 }
827 }
828 }
829
830 // if the destination is a topic and part is also a wildcard then
831 // check the wildcardConsumers for additional consumers
832 Iterator wildconsumers = _wildcardConsumers.keySet().iterator();
833 while (wildconsumers.hasNext()) {
834 ConsumerEntry entry = (ConsumerEntry) wildconsumers.next();
835 JmsDestination adest = entry._destination;
836 if ((entry._durable) &&
837 (adest instanceof JmsTopic) &&
838 (((JmsTopic) adest).match((JmsTopic) dest))) {
839 names.add(entry._name);
840 }
841 }
842
843 return names;
844 }
845
846 /***
847 * Clean all the unreferenced endpoints for a specified destination.
848 * Unreference endpoints usually occur when the client abnormally
849 * terminates leaving some dangling endpoints on the server.
850 *
851 * @param dest- the destination to query
852 */
853 public void cleanUnreferencedEndpoints(JmsDestination dest) {
854 Object[] endpoints = _endpoints.values().toArray();
855
856 for (int index = 0; index < endpoints.length; index++) {
857 ConsumerEndpoint endpoint = (ConsumerEndpoint) endpoints[index];
858 if (dest.equals(endpoint.getDestination())) {
859 if (!endpoint.getSession().isClientEndpointActive()) {
860 try {
861 endpoint.getSession().close();
862 } catch (Exception ignore) {
863 // ignore this exception
864 }
865 }
866 }
867 }
868 }
869
870 /***
871 * Return a list of {@link ConsumerEndpoint} objects attached to the
872 * specified destination
873 *
874 * @param dest the destination to query
875 * @return list of endpoints
876 */
877 public synchronized LinkedList getEndpointsForDest(JmsDestination dest) {
878 LinkedList endpoints = new LinkedList();
879 Iterator iter = _endpoints.values().iterator();
880
881 while (iter.hasNext()) {
882 ConsumerEndpoint endpoint = (ConsumerEndpoint) iter.next();
883 if (dest.equals(endpoint.getDestination())) {
884 endpoints.add(endpoint);
885 }
886 }
887
888 return endpoints;
889 }
890
891 // implement of GarbageCollectable.collectGarbage
892 public synchronized void collectGarbage(boolean aggressive) {
893 if (aggressive) {
894 Object[] endpoints = _endpoints.values().toArray();
895 int count = endpoints.length;
896
897 for (int index = 0; index < count; index++) {
898 ((ConsumerEndpoint) endpoints[index]).collectGarbage(aggressive);
899 }
900 }
901 }
902
903 /***
904 * Add the specified consumer to the cache.
905 *
906 * @param name - the name of the consumer
907 * @param dest - the destination it is subscribed to. It can be a wildcard
908 * @param durable - indicates whether it is a durable subscription
909 */
910 synchronized void addToConsumerCache(String name, JmsDestination dest,
911 boolean durable)
912 throws JMSException {
913
914 if (_log.isDebugEnabled()) {
915 _log.debug("addToConsumerCache(name=" + name + ", dest=" + dest
916 + ", durable=" + durable + ")");
917 }
918
919 if (!_consumerCache.containsKey(name)) {
920 ConsumerEntry entry = new ConsumerEntry(name, dest, durable);
921 _consumerCache.put(name, entry);
922
923 // if the specified destination is a JmsTopic and also a wildcard
924 // then we need to add it to all matching desitnations
925 if ((dest instanceof JmsTopic) &&
926 (((JmsTopic) dest).isWildCard())) {
927 // store wild card consumers in a separate array.
928 _wildcardConsumers.put(new ConsumerEntry(name, dest, durable),
929 dest);
930 } else {
931 // we also need to add the reverse mapping
932 Vector consumers = (Vector) _destToConsumerMap.get(dest);
933 if (consumers == null) {
934 consumers = new Vector();
935 _destToConsumerMap.put(dest, consumers);
936 }
937
938 // add the mapping
939 consumers.add(entry);
940 }
941 }
942 }
943
944 /***
945 * Remove the specified consumer from the cache and make all necessary
946 * adjustments
947 *
948 * @param name - name of consumer to remove
949 */
950 synchronized void removeFromConsumerCache(String name)
951 throws JMSException {
952
953 if (_log.isDebugEnabled()) {
954 _log.debug("removeFromConsumerCache(name=" + name + ")");
955 }
956
957 if (_consumerCache.containsKey(name)) {
958 ConsumerEntry entry = (ConsumerEntry) _consumerCache.remove(name);
959 JmsDestination dest = entry._destination;
960
961
962 if ((dest instanceof JmsTopic) &&
963 (((JmsTopic) dest).isWildCard())) {
964 // remove it from the wildcard cache.
965 _wildcardConsumers.remove(name);
966 } else {
967 // remove it from the specified destination
968 Vector consumers = (Vector) _destToConsumerMap.get(dest);
969 if (consumers != null) {
970 consumers.remove(entry);
971
972 // if consumers is of size 0 then remove it
973 if (consumers.size() == 0) {
974 _destToConsumerMap.remove(dest);
975 }
976 }
977 }
978 } else {
979 if (_log.isDebugEnabled()) {
980 _log.debug("removeFromConsumerCache(name=" + name +
981 "): consumer not found");
982 }
983 }
984 }
985
986 /***
987 * Remove all the consumers for the specified destination from the
988 * cache.
989 *
990 * @param destination - destination to remove.
991 */
992 synchronized void removeFromConsumerCache(JmsDestination destination) {
993 if (_destToConsumerMap.containsKey(destination)) {
994 _destToConsumerMap.remove(destination);
995
996 // i am not too sure whether we need more house keeping
997 }
998 }
999
1000 /***
1001 * Return the number of active consumer endpoints
1002 *
1003 * @return int - number of active consumer endpoints
1004 */
1005 int getConsumerEndpointCount() {
1006 return _endpoints.size();
1007 }
1008
1009 /***
1010 * Initialises the consumer manager
1011 *
1012 * @throws ServiceException if the manager can't be initialised
1013 */
1014 private void init() throws ServiceException {
1015 _scheduler = Scheduler.instance();
1016
1017 Connection connection = null;
1018 try {
1019 connection = DatabaseService.getConnection();
1020
1021 PersistenceAdapter adapter = DatabaseService.getAdapter();
1022 connection.commit();
1023
1024 // return a list of JmsDestination objects.
1025 HashMap map = adapter.getAllDurableConsumers(connection);
1026 Iterator iter = map.keySet().iterator();
1027
1028 // Create an endpoint for each durable consumer
1029 while (iter.hasNext()) {
1030 // for each destination, create the destination cache
1031 String consumer = (String) iter.next();
1032 String deststr = (String) map.get(consumer);
1033
1034 JmsDestination dest =
1035 DestinationManager.instance().destinationFromString(
1036 deststr);
1037 if (dest == null) {
1038 // this maybe a wildcard subscription
1039 dest = new JmsTopic(deststr);
1040 if (!((JmsTopic) dest).isWildCard()) {
1041 dest = null;
1042 }
1043 }
1044
1045 if (consumer != null && dest != null &&
1046 dest instanceof JmsTopic) {
1047 // cache the consumer-destination mapping in memory.
1048 addToConsumerCache(consumer, dest, true);
1049 } else {
1050 // what should we do about this stage
1051 _log.error(
1052 "Failure in ConsumerManager.init : " + consumer +
1053 ":" + dest);
1054 }
1055 }
1056 } catch (ServiceException exception) {
1057 SQLHelper.rollback(connection);
1058 throw exception;
1059 } catch (Exception exception) {
1060 SQLHelper.rollback(connection);
1061 throw new ServiceException("Failed to initialise ConsumerManager",
1062 exception);
1063 } finally {
1064 SQLHelper.close(connection);
1065 }
1066 }
1067
1068 /***
1069 * This private static class is used to maintain consumer information
1070 */
1071 private static class ConsumerEntry {
1072
1073 /***
1074 * The name of the consumer. This name is either the durable
1075 * subscriber name or a uniquely generated name.
1076 */
1077 String _name = null;
1078
1079 /***
1080 * Indicated whether this entry is for a durable subscriber
1081 */
1082 boolean _durable = false;
1083
1084 /***
1085 * The destination that the consumer is actually subscribed too
1086 */
1087 JmsDestination _destination = null;
1088
1089 /***
1090 * Construct an instance of this class using the specified
1091 * name and durable subscriber indicator
1092 *
1093 * @param name - the name of the consumer
1094 * @param destination - the destination consumer is subscribed too
1095 * @param durable - indicates whether it is a durable subscription
1096 */
1097 ConsumerEntry(String name, JmsDestination destination,
1098 boolean durable) {
1099 _name = name;
1100 _destination = destination;
1101 _durable = durable;
1102 }
1103
1104 // override Object.equals
1105 public boolean equals(Object obj) {
1106
1107 boolean result = false;
1108 if ((obj != null) &&
1109 (obj instanceof ConsumerEntry) &&
1110 (((ConsumerEntry) obj)._name.equals(_name))) {
1111 result = true;
1112 }
1113
1114 return result;
1115 }
1116
1117 } //-- ConsumerEntry
1118
1119 } //-- ConsumerManager
1120
This page was automatically generated by Maven