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: DestinationManager.java,v 1.36 2003/08/16 06:37:43 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.sql.SQLException;
52 import java.util.Collections;
53 import java.util.Enumeration;
54 import java.util.HashMap;
55 import java.util.Iterator;
56 import java.util.LinkedList;
57 import java.util.Map;
58 import java.util.Vector;
59
60 import javax.jms.DeliveryMode;
61 import javax.jms.JMSException;
62 import javax.naming.Context;
63 import javax.naming.InitialContext;
64 import javax.naming.NamingException;
65 import javax.transaction.TransactionManager;
66
67 import org.apache.commons.logging.Log;
68 import org.apache.commons.logging.LogFactory;
69
70 import org.exolab.core.service.ServiceException;
71 import org.exolab.jms.client.JmsDestination;
72 import org.exolab.jms.client.JmsQueue;
73 import org.exolab.jms.client.JmsTopic;
74 import org.exolab.jms.config.AdministeredDestinations;
75 import org.exolab.jms.config.AdministeredQueue;
76 import org.exolab.jms.config.AdministeredTopic;
77 import org.exolab.jms.config.Configuration;
78 import org.exolab.jms.config.ConfigurationManager;
79 import org.exolab.jms.config.MessageManagerConfiguration;
80 import org.exolab.jms.config.Subscriber;
81 import org.exolab.jms.gc.GarbageCollectable;
82 import org.exolab.jms.gc.GarbageCollectionService;
83 import org.exolab.jms.message.MessageImpl;
84 import org.exolab.jms.persistence.DatabaseService;
85 import org.exolab.jms.persistence.PersistenceAdapter;
86 import org.exolab.jms.persistence.PersistenceException;
87 import org.exolab.jms.persistence.SQLHelper;
88 import org.exolab.jms.server.NamingHelper;
89
90
91 // @todo - need to restructure dirs
92
93
94 /***
95 * The destination manager is responsible for creating and managing the
96 * lifecycle of {@link DestinationCache} objects. The destination manager
97 * is also responsible for managing messages, that are received by the
98 * message manager, which do not have any registered {@link DestinationCache}
99 *
100 * @version $Revision: 1.36 $ $Date: 2003/08/16 06:37:43 $
101 * @author <a href="mailto:jima@exoffice.com">Jim Alateras</a>
102 */
103 public class DestinationManager
104 implements MessageManagerEventListener, GarbageCollectable {
105
106 /***
107 * This structure maintains a list of active caches.
108 */
109 private Map _caches = Collections.synchronizedMap(new HashMap());
110
111 /***
112 * A list of administered and non-administered destinations are maintained
113 * in this data structure.
114 */
115 private HashMap _destinationCache = new HashMap();
116
117 /***
118 * The maximum no. of messages that each destination cache can hold
119 */
120 private final int _maxCacheSize;
121
122 /***
123 * Maintains a list of wildcard destinations, which can either be durable
124 * or transient
125 */
126 private LinkedList _wildcardDestinations = new LinkedList();
127
128 /***
129 * Maintains a linked list of DestinationEventListener objects. These
130 * listeners will be informated when destination are added or destroyed
131 */
132 private LinkedList _listeners = new LinkedList();
133
134 /***
135 * Manage the singleton instance of the DestinationManager
136 */
137 private static volatile DestinationManager _instance = null;
138
139 /***
140 * Caches the root context for all jndi binding.
141 */
142 private InitialContext _context = null;
143
144 /***
145 * The logger
146 */
147 private static final Log _log =
148 LogFactory.getLog(DestinationManager.class);
149
150 /***
151 * Construct a new <code>DestinationManager</code>
152 *
153 * @throws ServiceException if the service cannot be initialised
154 */
155 private DestinationManager() throws ServiceException {
156 MessageManagerConfiguration config =
157 ConfigurationManager.getConfig().getMessageManagerConfiguration();
158 _maxCacheSize = config.getDestinationCacheSize();
159
160 init();
161
162 // register with the GarbageCollectionService
163 GarbageCollectionService.instance().register(this);
164 }
165
166 /***
167 * Create the singleton instance of the destination manager
168 *
169 * @return the singleton instance
170 * @throws ServiceException if the service cannot be initialised
171 */
172 public static DestinationManager createInstance() throws ServiceException {
173 _instance = new DestinationManager();
174 return _instance;
175 }
176
177 /***
178 * Return the singleton destination manager
179 *
180 * @return the singleton instance, or <code>null</code> if it hasn't
181 * been initialised
182 */
183 public static DestinationManager instance() {
184 return _instance;
185 }
186
187 /***
188 * Create a destination of the specified type. If the destination already
189 * exists then simply return it. If it doesn't exist then create it.
190 *
191 * @param dest - the destination to create
192 * @return DestinationCache - the created destination cache
193 */
194 public synchronized DestinationCache createDestinationCache(
195 JmsDestination dest) {
196 return createDestinationCache(null, dest);
197 }
198
199 /***
200 * Create a destination of the specified type. If the destination already
201 * exists then simply return it. If it doesn't exist then create it. If
202 * a connection is supplied then create the cache using the connection
203 * context, otherwise create it without a context.
204 *
205 * @param connection - the connection to use.
206 * @param dest - the destination to create
207 * @return DestinationCache - the created destination cache
208 */
209 public synchronized DestinationCache createDestinationCache(
210 Connection connection, JmsDestination dest) {
211
212 // check to see if it exists first
213 DestinationCache cache = (DestinationCache) _caches.get(dest);
214
215 if (cache == null) {
216 // create a destination based on its type
217 try {
218 if (dest instanceof JmsTopic) {
219 cache = (connection != null) ?
220 new TopicDestinationCache(connection, (JmsTopic) dest) :
221 new TopicDestinationCache((JmsTopic) dest);
222 } else if (dest instanceof JmsQueue) {
223 cache = (connection != null) ?
224 new QueueDestinationCache(connection, (JmsQueue) dest) :
225 new QueueDestinationCache((JmsQueue) dest);
226 }
227 // set the configured size of each destination cache
228 cache.setMaximumSize(_maxCacheSize);
229
230 // notify the listeners that a new destination has been added
231 // to the destination manager
232 notifyDestinationAdded(dest, cache);
233 //cache it first
234 _caches.put(dest, cache);
235 } catch (Exception exception) {
236 _log.error("Failed to createDestinationCache", exception);
237 }
238 }
239
240 return cache;
241 }
242
243 /***
244 * Delete the specfied destination
245 *
246 * @param cahe - the destination to destroy
247 */
248 public synchronized void destroyDestinationCache(DestinationCache cache) {
249 destroyDestinationCache(cache.getDestination());
250 }
251
252 /***
253 * Delete the specfied destination
254 *
255 * @deprecated use destroyDestination(JmsDestination) instead
256 * @param name - destination name
257 */
258 public synchronized void destroyDestinationCache(String name) {
259
260 // currently we are doing a linear search through the
261 // destinations until we find the corresponding {@link JmsDestination}
262 // We want to discourage the use of this function.
263 Iterator iter = _caches.keySet().iterator();
264 while (iter.hasNext()) {
265 JmsDestination dest = (JmsDestination) iter.next();
266 if (dest.getName().equals(name)) {
267 destroyDestinationCache(dest);
268 break;
269 }
270 }
271 }
272
273 /***
274 * Delete the specfied destination
275 *
276 * @param dest - the destination to destroy
277 */
278 public synchronized void destroyDestinationCache(JmsDestination dest) {
279 DestinationCache cache = (DestinationCache) _caches.remove(dest);
280 if (cache != null) {
281 cache.destroy();
282
283 // notify the listeners that a destination has been removed from
284 // the destination manager
285 notifyDestinationRemoved(dest, cache);
286 }
287 }
288
289 /***
290 * Return the JmsDestination corresponding to the specified destination
291 * name
292 *
293 * @param name - destination name
294 * @return JmsDestination - the corresponding object or null
295 */
296 public synchronized JmsDestination destinationFromString(String name) {
297 return (JmsDestination) _destinationCache.get(name);
298 }
299
300 /***
301 * Register the specified DestinationEventListener. If the listener is
302 * already registered then do not re-register it again.
303 *
304 * @param listener - listener to add
305 */
306 void addDestinationEventListener(DestinationEventListener listener) {
307 synchronized (_listeners) {
308 if (!_listeners.contains(listener)) {
309 _listeners.add(listener);
310 }
311
312 }
313 }
314
315 /***
316 * Remove the specified DestinationEventListener from the list
317 *
318 * @param listener - listener to remove
319 */
320 void removeDestinationEventListener(DestinationEventListener listener) {
321 synchronized (_listeners) {
322 _listeners.remove(listener);
323 }
324 }
325
326 /***
327 * Return the destination cache associated with the dest object
328 *
329 * @param dest - the destination
330 * @return DestinationCache - associated destination object or null
331 */
332 public DestinationCache getDestinationCache(JmsDestination dest) {
333 return (DestinationCache) _caches.get(dest);
334 }
335
336 /***
337 * Return the destination object associated with destination
338 *
339 * @param dest - the name of the destination
340 * @return DestinationCache - associated destination object or null
341 */
342 public DestinationCache getDestinationCache(String dest) {
343 return getDestinationCache(destinationFromString(dest));
344 }
345
346 /***
347 * Return the {@link DestinationCache} for this message.
348 *
349 * @param message - the message to query
350 * @return DestinationCache - the corresponding cahce or null
351 */
352 public DestinationCache getDestinationCache(MessageImpl message) {
353 DestinationCache cache = null;
354
355 if (message != null) {
356 try {
357 cache = getDestinationCache(
358 (JmsDestination) message.getJMSDestination());
359 } catch (JMSException exception) {
360 // ignore it, probably means thant no destination has been
361 // assigned.
362 }
363 }
364
365 return cache;
366 }
367
368 /***
369 * Check if the specified destination exists.
370 *
371 * @param dest - destination to check
372 * @return boolean - true if it exists
373 */
374 public boolean hasDestinationCache(JmsDestination dest) {
375 return (_caches.containsKey(dest));
376 }
377
378 /***
379 * Create a non-administered destination and cache it. It does not
380 * check to see whether or not it is an administered destination
381 * this must be done by the caller
382 *
383 * @param destination - the destination to create
384 */
385 public synchronized void createDestination(JmsDestination destination) {
386 addToDestinationCache(destination);
387 }
388
389 /***
390 * Create an administered destination using the specified destination.
391 * It will create the destination in the database and register it with
392 * the jndi context.
393 *
394 * @param dest - the destination
395 * @return boolean - true if successful
396 */
397 public synchronized boolean createAdministeredDestination(
398 JmsDestination dest) throws JMSException {
399 if (_log.isDebugEnabled()) {
400 _log.debug("createAdministeredDestination(dest=" + dest + ")");
401 }
402
403 boolean success = true;
404 boolean queue = (dest instanceof JmsQueue) ? true : false;
405 PersistenceAdapter adapter = DatabaseService.getAdapter();
406
407 // check that the destination does not exist. If it exists then return
408 // false. If it doesn't exists the create it and bind it to the jndi
409 // context
410
411 Connection connection = null;
412 try {
413
414 connection = DatabaseService.getConnection();
415
416 if (!adapter.checkDestination(connection, dest.getName())) {
417 adapter.addDestination(connection, dest.getName(), queue);
418
419 // destination was created in persistent store, now create it
420 // in transient memory and also bind it in the jndi context
421 addToDestinationCache(dest);
422 try {
423 dest.setPersistent(true);
424 ContextHelper.rebind(getContext(), dest.getName(), dest);
425 } catch (NamingException exception) {
426 String msg = "Failed to add destination " + dest.getName()
427 + " to JNDI context";
428 _log.error(msg, exception);
429 throw new JMSException(msg + ": " +
430 exception.getMessage());
431 }
432 } else {
433 success = false;
434 }
435 connection.commit();
436 } catch (JMSException exception) {
437 SQLHelper.rollback(connection);
438 throw exception;
439 } catch (Exception exception) { // PersistenceException, SQLException
440 SQLHelper.rollback(connection);
441 String msg = "Failed to create administered destination"
442 + dest.getName();
443 _log.error(msg, exception);
444 throw new JMSException(msg + ": " + exception.getMessage());
445 } finally {
446 SQLHelper.close(connection);
447 }
448
449 return success;
450 }
451
452 /***
453 * Remove the corresponding administered destination from persistent
454 * store, from transient memory and from the jndi context. This will
455 * also remove all durable consumers for this topic.
456 *
457 * @param dest - the destination to remove
458 * @return boolean - true if successful
459 */
460 public synchronized void deleteAdministeredDestination(JmsDestination dest)
461 throws JMSException {
462
463 if (_log.isDebugEnabled()) {
464 _log.debug("deleteAdministeredDestination(dest=" + dest + ")");
465 }
466
467 boolean success = false;
468 boolean queue = (dest instanceof JmsQueue) ? true : false;
469 ConsumerManager consumerMgr = ConsumerManager.instance();
470
471 // If we are dealing with a topic then first check that there are
472 // no active durable consumers for the destination
473 if (!queue) {
474 if (consumerMgr.hasActiveDurableConsumers(dest)) {
475 throw new JMSException(
476 "Cannot delete the administered destination " + dest
477 + " since there are active durable consumers.");
478 }
479 // no active consumers. Remove all durable consumers to this
480 // destination
481 consumerMgr.removeDurableConsumers(dest);
482 }
483
484 // make sure there are not active endpoints, but first clear
485 // unreferenced endpoints.
486 consumerMgr.cleanUnreferencedEndpoints(dest);
487 int active = consumerMgr.getEndpointsForDest(dest).size();
488 if (active > 0) {
489 throw new JMSException(
490 "Cannot delete the administered destination" + dest
491 + " since there are " + active + " active endpoints.");
492 }
493
494 // unbind it from the jndi context so that now it is unavailable
495 // to other consumers
496 try {
497 getContext().unbind(dest.getName());
498 } catch (NamingException error) {
499 _log.error("Failed to remove destination " + dest.getName()
500 + " from JNDI", error);
501 }
502
503 // now that we have removed all the durable consumers we can remove
504 // the administered topic. First delete it from memory and then
505 // from the persistent store
506 Connection connection = null;
507 try {
508 connection = DatabaseService.getConnection();
509
510 DatabaseService.getAdapter().removeDestination(connection,
511 dest.getName());
512 destroyDestinationCache(dest);
513 removeFromDestinationCache(dest);
514 connection.commit();
515 } catch (PersistenceException exception) {
516 SQLHelper.rollback(connection);
517 String msg = "Failed to remove destination " + dest.getName();
518 _log.error(msg, exception);
519 throw new JMSException(msg + ":" + exception.getMessage());
520 } catch (SQLException exception) {
521 SQLHelper.rollback(connection);
522 String msg = "Failed to remove destination " + dest.getName();
523 _log.error(msg, exception);
524 throw new JMSException(msg + ":" + exception.getMessage());
525 } finally {
526 SQLHelper.close(connection);
527 }
528 }
529
530 /***
531 * Return a list of destination names currently supported by the destination
532 * manager. This includes all types of destinations.
533 *
534 * @return Iterator - iterator for {@link JmsDestination} objects
535 */
536 public Iterator destinationNames() {
537 return _destinationCache.values().iterator();
538 }
539
540 /***
541 * Return a list of {@link DestinationCache} objects that are currently
542 * active and in memory. This will return a list of all destination
543 * types (temporary. transient, administered}.
544 *
545 * @return Iterator - set of DestinationCache objects
546 */
547 public Iterator destinations() {
548 return _caches.values().iterator();
549 }
550
551 /***
552 * This method will create the administered destinations specified in
553 * the configuration. A topic may also have zero or more preconfigured
554 * durable sunbscribers. An equivalent entity for queues does not
555 * exist.
556 */
557 public void registerConfiguredAdministeredDestinations() {
558 AdministeredDestinations destinations =
559 ConfigurationManager.getConfig().getAdministeredDestinations();
560 if (destinations != null) {
561
562 // first process the topics
563 int count = destinations.getAdministeredTopicCount();
564 for (int index = 0; index < count; index++) {
565 AdministeredTopic topic = destinations.getAdministeredTopic(index);
566
567 // define a persistent topic destination and then use the
568 // message manager administrator to add it
569 JmsTopic destination = new JmsTopic(topic.getName());
570 destination.setPersistent(true);
571 try {
572
573 createAdministeredDestination(destination);
574
575 // register the subscribers for each topic.
576 int scount = topic.getSubscriberCount();
577 ConsumerManager mgr = ConsumerManager.instance();
578 for (int sindex = 0; sindex < scount; sindex++) {
579 Subscriber subscriber = topic.getSubscriber(sindex);
580
581 // create the durable consumer only if one does
582 // not already exist
583 if (!mgr.exists(subscriber.getName())) {
584 mgr.createDurableConsumer(destination,
585 subscriber.getName());
586 }
587 }
588 } catch (JMSException exception) {
589 _log.error("Failed to register persistent topic "
590 + topic.getName(), exception);
591 }
592 }
593
594 // next process the queue destinations. QueueDestinations do not
595 // have any associated durable subscribers
596 count = destinations.getAdministeredQueueCount();
597 for (int index = 0; index < count; index++) {
598 AdministeredQueue queue = destinations.getAdministeredQueue(index);
599
600 // define a persistent topic destination and then use the
601 // message manager administrator to add it
602 JmsQueue destination = new JmsQueue(queue.getName());
603 destination.setPersistent(true);
604 try {
605 createAdministeredDestination(destination);
606 } catch (JMSException exception) {
607 _log.error("Failed to register persistent queue "
608 + queue.getName(), exception);
609 }
610 }
611 }
612 }
613
614 // implementation of MessageManagerEventListener.messageAdded
615 public synchronized boolean messageAdded(JmsDestination destination, MessageImpl message) {
616 boolean result = false;
617 try {
618 if (destination instanceof JmsTopic) {
619 // check to see whether there are active consumers for the
620 // specified destination. If there are then we need to
621 // create a destination cache and pass the message to it.
622 if (ConsumerManager.instance().hasActiveConsumers(destination)) {
623 if (!destinationExists(destination)) {
624 createDestination(destination);
625 }
626 DestinationCache cache = createDestinationCache(destination);
627 result = cache.messageAdded(destination, message);
628 }
629 } else {
630 // assume that the destination is a queue. since the message
631 // is non-persistent then we need to create the cache and pass the
632 // message to it.
633 if (!destinationExists(destination)) {
634 createDestination(destination);
635 }
636 DestinationCache cache = createDestinationCache(destination);
637 result = cache.messageAdded(destination, message);
638 }
639 } catch (Exception exception) {
640 _log.error("Exception in DestinationManager.messageAdded",
641 exception);
642 }
643
644 return result;
645 }
646
647 // implementation of MessageManagerEventListener.messageRemoved
648 public void messageRemoved(JmsDestination destination, MessageImpl message) {
649 // removing a non-persistent messages, when the associated destination
650 // is not active is a noop
651 }
652
653 // implementation of MessageManagerEventListener.persistentMessageAdded
654 public synchronized boolean persistentMessageAdded(Connection connection,
655 JmsDestination destination, MessageImpl message)
656 throws PersistenceException {
657
658 boolean result = false;
659
660 try {
661 if (destination instanceof JmsTopic) {
662 // the cache for this destination is inactive. Determine, if
663 // there are any active wildcard consumers for this destination
664 // If there are then create the destination cache and let it
665 // handle the message. Otherwise send it to the ConsumerManager
666 // for processing
667 ConsumerManager manager = ConsumerManager.instance();
668 if (manager.hasActiveConsumers(destination)) {
669 // create the destincation cache and let it process the
670 // message
671 DestinationCache cache = createDestinationCache(connection,
672 destination);
673 result = cache.persistentMessageAdded(connection,
674 destination, message);
675 } else {
676 // This is now handled by the MessageMgr when the message
677 // enters the system
678 // let the consumer manager handle this
679 // result = ConsumerManager.instance().persistentMessageAdded(
680 // connection, message);
681 }
682 } else {
683 // This is now handled by the MessageMgr when the message
684 // enters the system
685 // assume that the destination is a queue. Since the message is
686 // persistent then we do not need to activate the cache, simply
687 // create a persistent handle and be done with it.
688 // MessageHandleFactory.createPersistentHandle(connection,
689 // destination, null, message);
690 }
691 } catch (Exception exception) {
692 // rethrow as a PersistenceException
693 exception.printStackTrace();
694 throw new PersistenceException(
695 "Exception in DestinationManager.messageAdded " +
696 exception.toString());
697 }
698
699 return result;
700 }
701
702 // implementation of MessageManagerEventListener.persistentMessageRemoved
703 public void persistentMessageRemoved(Connection connection,
704 JmsDestination destination, MessageImpl message)
705 throws PersistenceException {
706 try {
707 if (destination instanceof JmsTopic) {
708 // this is a persistent message so we need to retrieve the
709 // set of durable subscribers for this.
710 Vector names =
711 ConsumerManager.instance().getDurableConsumersForDest(
712 (JmsTopic) destination);
713
714 // for each durable consumer we need to destory that handle
715 while (names.size() > 0) {
716 String name = (String) names.remove(0);
717 MessageHandleFactory.destroyPersistentHandle(connection,
718 destination, name, message);
719 }
720 } else {
721 // assume it is a queue and destroy the handle.
722 MessageHandleFactory.destroyPersistentHandle(connection,
723 destination, null, message);
724 }
725 } catch (PersistenceException exception) {
726 // catch and rethrow
727 throw exception;
728 } catch (Exception exception) {
729 throw new PersistenceException(
730 "Exception in DestinationManager.messageRemoved " +
731 exception.toString());
732 }
733 }
734
735 // implement of GarbageCollectable.collectGarbage
736 public synchronized void collectGarbage(boolean aggressive) {
737 // before continuing we should change the priority of the thread
738 // to the lowest value.
739 int gc_caches = 0;
740 int gc_destinations = 0;
741
742 Object[] caches = _caches.values().toArray();
743 for (int index = 0; index < caches.length; index++) {
744 DestinationCache cache = (DestinationCache) caches[index];
745 if (cache.canDestroy()) {
746 if (_log.isDebugEnabled()) {
747 _log.debug("Garbage collecting destination cache="
748 + cache);
749 }
750 destroyDestinationCache(cache);
751 gc_caches++;
752 } else {
753 // the cache is active, so issue a garbage collection
754 // request on it
755 cache.collectGarbage(aggressive);
756 }
757 }
758
759 // get rid of non-administered destinations, without
760 // associated caches.
761 Iterator destinations = _destinationCache.values().iterator();
762 Vector to_delete = new Vector();
763 while (destinations.hasNext()) {
764 JmsDestination dest = (JmsDestination) destinations.next();
765 if (!(dest.getPersistent()) &&
766 (!_caches.containsKey(dest))) {
767 to_delete.add(dest);
768 gc_destinations++;
769 }
770 }
771
772 // now delete the actual destinations
773 Enumeration todel = to_delete.elements();
774 while (todel.hasMoreElements()) {
775 _destinationCache.remove(((JmsDestination) todel.nextElement()).getName());
776 }
777
778 // log the information
779 _log.info("DMGC Collected " + gc_caches + " caches, " + _caches.size()
780 + " remaining.");
781 _log.info("DMGC Collected " + gc_destinations + " destinations, "
782 + _destinationCache.size() + " remaining.");
783 }
784
785 /***
786 * Return a HashMap of all destinations that match the specified topic
787 * If the topic represents a wildcard then it may match none, one or more
788 * destinations. The results are returns as a mapping of destination and
789 * the corresponding cache
790 * <p>
791 * The topic maybe a straight topic name or a wildcard
792 *
793 * @param topic - topic to query
794 * @return HashMap
795 */
796 synchronized HashMap getTopicDestinationCaches(JmsTopic topic) {
797 HashMap result = new HashMap();
798
799 Iterator iter = _caches.keySet().iterator();
800 while (iter.hasNext()) {
801 JmsDestination dest = (JmsDestination) iter.next();
802 if ((dest instanceof JmsTopic) &&
803 (topic.match((JmsTopic) dest))) {
804 result.put(dest, _caches.get(dest));
805 }
806 }
807
808 return result;
809 }
810
811 /***
812 * Destroy this manager. This is brutal and final
813 */
814 public synchronized void destroy() {
815 // clean up all the destinations
816 Object[] dests = _caches.keySet().toArray();
817 for (int index = 0; index < dests.length; index++) {
818 destroyDestinationCache((JmsDestination) dests[index]);
819 }
820
821 _caches.clear();
822 _caches = null;
823
824 _destinationCache.clear();
825 _destinationCache = null;
826
827 // remove all the listeners
828 _listeners.clear();
829 _listeners = null;
830
831 _context = null;
832
833 // reset the singleton
834 _instance = null;
835 }
836
837 /***
838 * Test whether the specified destination is an administered destination. It
839 * assumes that the destination exsits.
840 *
841 * @param name - the name of the destination
842 * @return boolean - true if it is and false otherwise
843 */
844 public boolean isAdministeredDestination(String name) {
845 boolean result = false;
846 JmsDestination dest = (JmsDestination) _destinationCache.get(name);
847 if ((dest != null) &&
848 (dest.getPersistent())) {
849 result = true;
850 }
851
852 return result;
853 }
854
855 /***
856 * Test whether the specified message is for an administered destination.
857 * This would be the case if the destination is administered or if there
858 * is an administered wildcard destination that is satisfied by the
859 * message destination
860 *
861 * @param message - the message to check
862 * @return boolean
863 */
864 public boolean isMessageForAdministeredDestination(MessageImpl msg) {
865 boolean result = false;
866 try {
867 JmsDestination mdest = (JmsDestination) msg.getJMSDestination();
868 JmsDestination dest = (JmsDestination) _destinationCache.get(mdest.getName());
869
870 if (dest != null) {
871 if (dest.getPersistent()) {
872 result = true;
873 } else if (mdest instanceof JmsTopic) {
874 // check if any of the wildcards are administered
875 Object[] dests = _wildcardDestinations.toArray();
876 for (int index = 0; index < dests.length; index++) {
877 JmsTopic adest = (JmsTopic) dests[index];
878 if ((adest.match((JmsTopic) mdest)) &&
879 (adest.getPersistent())) {
880 result = true;
881 break;
882 }
883 }
884
885 }
886 }
887 } catch (JMSException ignore) {
888 }
889
890 return result;
891 }
892
893 /***
894 * Add the specified entry to the destination cache, if it doesn't
895 * already exist.
896 *
897 * @param destination - destination to add
898 */
899 void addToDestinationCache(JmsDestination destination) {
900 synchronized (_destinationCache) {
901 if (!_destinationCache.containsKey(destination.getName())) {
902 _destinationCache.put(destination.getName(), destination);
903
904 // check whether it is a wildcard destination
905 if (((destination instanceof JmsTopic) &&
906 (((JmsTopic) destination).isWildCard()))) {
907 _wildcardDestinations.add(destination);
908 }
909 }
910 }
911 }
912
913 /***
914 * Remove the specified destination from the cache
915 *
916 * @param destination - the destination to remove
917 */
918 void removeFromDestinationCache(JmsDestination destination) {
919 synchronized (_destinationCache) {
920 if (_destinationCache.remove(destination.getName()) != null) {
921
922 // check whether we also need to delete it from the
923 // list of wildcard subscriptions
924 if (((destination instanceof JmsTopic) &&
925 (((JmsTopic) destination).isWildCard()))) {
926 _wildcardDestinations.remove(destination);
927 }
928 }
929
930 }
931 }
932
933 /***
934 * Check if the specified destination exists.
935 *
936 * @param destination - the destination to check
937 * @return boolean - true if it exists and false otherwise
938 */
939 public boolean destinationExists(JmsDestination destination) {
940 return _destinationCache.containsKey(destination.getName());
941 }
942
943 /***
944 * Initialises the destination manager.
945 *
946 * @throws ServiceException if the service cannot be initialised
947 */
948 protected void init() throws ServiceException {
949 Connection connection = null;
950 TransactionManager tm = null;
951 try {
952 connection = DatabaseService.getConnection();
953
954 // return a list of JmsDestination objects.
955 Enumeration iter =
956 DatabaseService.getAdapter().getAllDestinations(connection);
957 connection.commit();
958
959 while (iter.hasMoreElements()) {
960 // add each destination to the cache and also bind
961 // it to the context
962 JmsDestination dest = (JmsDestination) iter.nextElement();
963 addToDestinationCache(dest);
964 try {
965 // for each of the administered destinations rebind it to
966 // the jndi context
967 dest.setPersistent(true);
968 ContextHelper.rebind(getContext(), dest.getName(), dest);
969 } catch (NamingException error) {
970 throw new ServiceException(
971 "Failed to add destination " + dest.getName()
972 + " to JNDI", error);
973 }
974 }
975 } catch (PersistenceException exception) {
976 SQLHelper.rollback(connection);
977 String msg = "Failed to initialise DestinationManager";
978 _log.error(msg, exception);
979 throw exception;
980 } catch (SQLException exception) {
981 SQLHelper.rollback(connection);
982 String msg = "Failed to initialise DestinationManager";
983 _log.error(msg, exception);
984 throw new ServiceException(msg, exception);
985 } finally {
986 SQLHelper.close(connection);
987 }
988 }
989
990 /***
991 * Notyify the list of DestinationEventListener objects that the specified
992 * destination has been added
993 *
994 * @param dest - the destination that was added
995 * @param cache - the corresponding cache
996 */
997 private void notifyDestinationAdded(JmsDestination dest,
998 DestinationCache cache) {
999 synchronized (_listeners) {
1000 Iterator iter = _listeners.iterator();
1001 while (iter.hasNext()) {
1002 ((DestinationEventListener) iter.next()).destinationAdded(dest, cache);
1003 }
1004 }
1005 }
1006
1007 /***
1008 * Notyify the list of DestinationEventListener objects that the specified
1009 * destination has been removed
1010 *
1011 * @param dest - the destination that was removed
1012 * @param cache - the corresponding cache
1013 */
1014 private void notifyDestinationRemoved(JmsDestination dest,
1015 DestinationCache cache) {
1016 synchronized (_listeners) {
1017 Iterator iter = _listeners.iterator();
1018 while (iter.hasNext()) {
1019 ((DestinationEventListener) iter.next()).destinationRemoved(dest, cache);
1020 }
1021 }
1022 }
1023
1024 /***
1025 * Return the initial context using the JndiHelper. It assumes that the
1026 * configuration manager has been successfully initialized. If the context
1027 * has been retrieved it is cached so subsequent gets will return the
1028 * cached instance
1029 *
1030 * @return the root context
1031 */
1032 private static Context getContext() throws NamingException {
1033 return NamingHelper.getInitialContext(
1034 ConfigurationManager.getConfig());
1035 }
1036
1037 /***
1038 * This static class is used to maintain information about a
1039 * destination
1040 */
1041 private static class DestinationEntry {
1042
1043 /***
1044 * The destination that this entry pertains too
1045 */
1046 public JmsDestination _destination = null;
1047
1048 /***
1049 * Indicates whether the destination is administered
1050 */
1051 public boolean _administered = false;
1052
1053
1054 /***
1055 * Construct an instance of the entry using the specified
1056 * parameters
1057 *
1058 * @param dest - the destination
1059 * @param administered - true if the destination is administered
1060 */
1061 DestinationEntry(JmsDestination dest, boolean administered) {
1062 _destination = dest;
1063 _administered = administered;
1064 }
1065
1066 // override Object.equals
1067 public boolean equals(Object obj) {
1068
1069 boolean result = false;
1070
1071 if ((obj != null) &&
1072 (obj instanceof DestinationEntry)) {
1073
1074 result = _destination.equals(((DestinationEntry) obj)._destination);
1075 }
1076
1077 return result;
1078 }
1079 }
1080 }
This page was automatically generated by Maven