View Javadoc
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: QueueDestinationCache.java,v 1.34 2003/12/29 13:09:25 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.Enumeration; 53 import java.util.Iterator; 54 import java.util.LinkedList; 55 import java.util.List; 56 import java.util.Vector; 57 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.jms.client.JmsDestination; 65 import org.exolab.jms.client.JmsQueue; 66 import org.exolab.jms.client.JmsTemporaryDestination; 67 import org.exolab.jms.message.MessageHandle; 68 import org.exolab.jms.message.MessageImpl; 69 import org.exolab.jms.persistence.DatabaseService; 70 import org.exolab.jms.persistence.PersistenceException; 71 import org.exolab.jms.selector.Selector; 72 import org.exolab.jms.server.JmsServerConnectionManager; 73 74 75 /*** 76 * A DestinationCache for Queues 77 * 78 * @version $Revision: 1.34 $ $Date: 2003/12/29 13:09:25 $ 79 * @author <a href="mailto:jima@exoffice.com">Jim Alateras</a> 80 */ 81 public class QueueDestinationCache 82 extends DestinationCache { 83 84 /*** 85 * Maintains a list of queue listeners for this cache 86 */ 87 protected List _queueListeners = 88 Collections.synchronizedList(new LinkedList()); 89 90 /*** 91 * Underlying destination 92 */ 93 private JmsQueue _queue = null; 94 95 /*** 96 * Index of the last listener that received a message from this 97 * destination. If multiple listeners are attached to this queue then 98 * messages will be sent to each in a round robin fashion 99 */ 100 private int _lastConsumerIndex = 0; 101 102 /*** 103 * Tracks the number of messages added to the destination cache 104 */ 105 private long _publishCount; 106 107 /*** 108 * Tracks the number of messages consumed from the destination cache 109 */ 110 private long _consumeCount; 111 112 /*** 113 * The logger 114 */ 115 private static final Log _log = LogFactory.getLog( 116 QueueDestinationCache.class); 117 118 119 /*** 120 * Construct a message cache for a queue destination. This cache will 121 * receive all messages published under the specified destination. 122 * <p> 123 * the constructor will also attempt to load any persistent messages 124 * from the database. 125 * <p> 126 * 127 * @param destination the queue 128 * @throws FailedToInitializeException 129 */ 130 QueueDestinationCache(JmsQueue destination) 131 throws FailedToInitializeException { 132 133 _queue = destination; 134 // call init on the base class 135 init(); 136 if (DestinationManager.instance().isAdministeredDestination(destination.getName())) { 137 // call the persistence adapter to determine if we have unacknowledged 138 // messages for this queue. The persistent handles are keyed on the 139 // queue name 140 Connection connection = null; 141 TransactionManager tm = null; 142 try { 143 connection = DatabaseService.getConnection(); 144 145 // initialise the cache 146 init(destination, connection); 147 148 // commit the work 149 connection.commit(); 150 } catch (PersistenceException exception) { 151 if (connection != null) { 152 try { 153 connection.rollback(); 154 } catch (Exception nested) { 155 // ignore 156 } 157 } 158 throw new FailedToInitializeException( 159 "QueueDestinationCache init failed " + exception); 160 } catch (Exception exception) { 161 // rethrow as a JMSException 162 throw new FailedToInitializeException( 163 "QueueDestinationCache init failed " + exception); 164 } finally { 165 if (connection != null) { 166 try { 167 connection.close(); 168 } catch (Exception nested) { 169 // ignore 170 } 171 } 172 } 173 } 174 } 175 176 177 /*** 178 * Construct a message cache for a queue destination using the specified 179 * database connection. This cache will receive all messages published 180 * under the destination. 181 * <p> 182 * the constructor will also attempt to load any persistent messages 183 * from the database using the specified connection 184 * <p> 185 * If there is any problem during construction a FailedToInitializeException 186 * will be raised. 187 * 188 * @param destination - the queue 189 * @throws FialedToInitializeException 190 */ 191 QueueDestinationCache(Connection connection, JmsQueue destination) 192 throws FailedToInitializeException { 193 super(); 194 _queue = destination; 195 196 // call init on the base class 197 init(connection); 198 if (DestinationManager.instance().isAdministeredDestination(destination.getName())) { 199 // call the persistence adapter to determine if we have unacknowledged 200 // messages for this queue. The persistent handles are keyed on the 201 // queue name 202 try { 203 // initialise the cache 204 init(destination, connection); 205 } catch (Exception exception) { 206 // rethrow as a JMSException 207 throw new FailedToInitializeException( 208 "QueueDestinationCache init failed " + exception); 209 } 210 } 211 } 212 213 214 /*** 215 * This common method is used to help initialise the cache. It basically 216 * removes all the expired messages and then retrieves all unacked messages 217 * from the database and stores them locally. 218 * <p> 219 * It will throw a PersistenceException if there is a database related 220 * problem. 221 * 222 * @param destination - the queue 223 * @param connection - the database connection to use 224 * @throws PersistenceException 225 */ 226 void init(JmsQueue destination, Connection connection) 227 throws PersistenceException { 228 229 DatabaseService.getAdapter().removeExpiredMessageHandles( 230 connection, destination.getName()); 231 Vector handles = DatabaseService.getAdapter().getMessageHandles( 232 connection, destination, destination.getName()); 233 if (handles != null) { 234 Enumeration iter = handles.elements(); 235 while (iter.hasMoreElements()) { 236 addMessage((MessageHandle) iter.nextElement()); 237 } 238 } 239 } 240 241 // implementation of DestinationCache.getDestination 242 public JmsDestination getDestination() { 243 return _queue; 244 } 245 246 /*** 247 * A Queue can also hav a queue listener, which simply gets informed of all 248 * messages that arrive at this destination 249 * 250 * @param listener - queue listener 251 */ 252 public void addQueueListener(QueueListener listener) { 253 // add if not present 254 if (!_queueListeners.contains(listener)) { 255 _queueListeners.add(listener); 256 } 257 } 258 259 /*** 260 * Remove the queue listener associated with this cache 261 * 262 * @param listener - queue listener to remove 263 */ 264 public void removeQueueListener(QueueListener listener) { 265 // add if not present 266 if (_queueListeners.contains(listener)) { 267 _queueListeners.remove(listener); 268 } 269 } 270 271 // implementation of MessageMgr.messageAdded 272 public boolean messageAdded(JmsDestination destination, 273 MessageImpl message) { 274 boolean processed = false; 275 276 if ((destination != null) && 277 (message != null)) { 278 279 // check that the message is for this queue 280 if (destination.equals(_queue)) { 281 282 // create a handle for the message 283 try { 284 MessageHandle handle = 285 MessageHandleFactory.createHandle(this, message); 286 287 288 // all messages are added to this queue. Receivers will 289 // then pick messages of it as required. 290 addMessage(handle, message); 291 292 // update the publishedCount 293 _publishCount++; 294 295 // if we have any registered consumers then we need to 296 // send the message to one of them first. If none are 297 // registered then cache it. 298 QueueConsumerEndpoint endpoint = 299 getEndpointForMessage(message); 300 if (endpoint != null) { 301 endpoint.messageAdded(message); 302 } 303 304 // notify any queue listeners that a message has arrived 305 notifyQueueListeners(message); 306 307 // create a lease iff one is required 308 checkMessageExpiry(message); 309 310 // check the message as processed 311 processed = true; 312 } catch (JMSException exception) { 313 _log.error("Failed to add message", exception); 314 } 315 } else { 316 // need to notify someone or something that we are 317 // dropping messages. Do we throw an exception 318 _log.error("Dropping message " + message.getMessageId() 319 + " for destination " + destination.getName()); 320 } 321 } 322 323 return processed; 324 } 325 326 /*** 327 * This method is called when the {@link MessageMgr} removes a message 328 * from the cache. 329 * 330 * @param destination the message destination 331 * @param message the message removed from cache 332 */ 333 public void messageRemoved(JmsDestination destination, 334 MessageImpl message) { 335 336 if ((destination != null) && 337 (message != null)) { 338 339 try { 340 MessageHandle handle = 341 MessageHandleFactory.getHandle(this, message); 342 343 // call remove regardless whether it exists 344 if (destination.equals(_queue)) { 345 removeMessage(handle); 346 notifyOnRemoveMessage(message); 347 handle.destroy(); 348 } 349 } catch (JMSException exception) { 350 _log.error("Failed to remove message", exception); 351 } 352 } 353 } 354 355 // implementation of MessageMgr.persistentMessageAdded 356 public boolean persistentMessageAdded(Connection connection, 357 JmsDestination destination, 358 MessageImpl message) 359 throws PersistenceException { 360 361 boolean processed = false; 362 363 if ((destination != null) && 364 (message != null)) { 365 366 // check that it is not already present before adding it. 367 if (destination.equals(_queue)) { 368 369 // create a handle for the message 370 try { 371 372 // all messages are added to this queue. Receivers will 373 // then pick messages of it as required. 374 MessageHandle handle = 375 MessageHandleFactory.getHandle(this, message); 376 addMessage(handle, message); 377 378 // increment the number of messages received 379 _publishCount++; 380 381 // if we have any registered consumers then we need to 382 // send the message to one of them first. If none are 383 // registered then cache it. 384 QueueConsumerEndpoint endpoint = 385 getEndpointForMessage(message); 386 if (endpoint != null) { 387 endpoint.persistentMessageAdded(connection, message); 388 } 389 390 // notify any queue listeners that a message has arrived 391 notifyQueueListeners(message); 392 393 // create a lease iff one is required 394 checkMessageExpiry(message); 395 396 // check the message as processed 397 processed = true; 398 } catch (JMSException exception) { 399 _log.error("Failed to add persistent message", 400 exception); 401 } 402 } else { 403 // need to notify someone or something that we are 404 // dropping messages. Do we throw an exception 405 } 406 } 407 408 return processed; 409 } 410 411 // implementation of MessageMgr.persistentMessageAdded 412 public synchronized void persistentMessageRemoved( 413 Connection connection, JmsDestination destination, 414 MessageImpl message) 415 throws PersistenceException { 416 417 if ((destination != null) && 418 (message != null)) { 419 420 try { 421 PersistentMessageHandle handle = (PersistentMessageHandle) 422 MessageHandleFactory.getHandle(this, message); 423 424 // call remove regardless whether it exists 425 if (destination.equals(_queue)) { 426 removeMessage(handle); 427 notifyOnRemovePersistentMessage(connection, message); 428 MessageHandleFactory.destroyPersistentHandle(connection, 429 handle); 430 } 431 } catch (JMSException exception) { 432 _log.error("Failed to remove persistent message", exception); 433 } 434 } 435 } 436 437 /*** 438 * Return the next {@link ConsumerEndpoint} that can consume the specified 439 * message or null if there is none. 440 * 441 * @param message - the message to consume 442 * @return the consumer who should receive this message or null 443 */ 444 private synchronized QueueConsumerEndpoint getEndpointForMessage( 445 MessageImpl message) { 446 QueueConsumerEndpoint selectedEndpoint = null; 447 448 if (_consumers.size() > 0) { 449 // roll over the consumer index if it is greater 450 // than the number of registered consumers 451 if ((_lastConsumerIndex + 1) > _consumers.size()) { 452 _lastConsumerIndex = 0; 453 } 454 455 // look over the list of consumers and return the 456 // first endpoint that can process this message 457 int index = _lastConsumerIndex; 458 do { 459 QueueConsumerEndpoint endpoint = 460 (QueueConsumerEndpoint) _consumers.get(index); 461 Selector selector = endpoint.getSelector(); 462 463 // if the endpoint has a message listener registered 464 // or the endpoint is waiting for a message and the 465 // message satisfies the selector then return it to 466 // the client. 467 if (((endpoint.hasMessageListener()) || 468 (endpoint.isWaitingForMessage())) && 469 ((selector == null) || 470 (selector.selects(message)))) { 471 _lastConsumerIndex = ++index; 472 selectedEndpoint = endpoint; 473 break; 474 } 475 476 // advance to the next consumer 477 if (++index >= _consumers.size()) { 478 index = 0; 479 } 480 } while (index != _lastConsumerIndex); 481 } 482 483 return selectedEndpoint; 484 } 485 486 /*** 487 * Return the first message of the queue or null if there are no messages 488 * in the cache 489 * 490 * @param QueueConsumerEndpoint - the consumer who will receive the message 491 * @return MessageHandle - handle to the first message 492 */ 493 public synchronized MessageHandle getMessage( 494 QueueConsumerEndpoint endpoint) { 495 MessageHandle handle = null; 496 // do not return a message is the endpoint is null; 497 if ((endpoint != null) && 498 (getMessageCount() > 0)) { 499 Selector selector = endpoint.getSelector(); 500 if (selector == null) { 501 // if no selector has been specified then remove and return 502 // the first message 503 handle = removeFirstMessage(); 504 _consumeCount++; 505 } else { 506 // for non null selector we must find the first matching 507 Object[] handles = toMessageArray(); 508 for (int i = 0; i < handles.length; ++i) { 509 MessageHandle hdl = (MessageHandle) handles[i]; 510 if (selector.selects(hdl.getMessage())) { 511 handle = hdl; 512 removeMessage(hdl); 513 _consumeCount++; 514 break; 515 } 516 } 517 } 518 } 519 520 return handle; 521 } 522 523 /*** 524 * Playback all the messages in the cache to the specified 525 * {@link QueueListener} 526 * 527 * @param listener - the queue listener 528 */ 529 public void playbackMessages(QueueListener listener) { 530 531 Object[] messages = toMessageArray(); 532 if ((listener != null) && 533 (messages.length > 0)) { 534 try { 535 for (int index = 0; index < messages.length; index++) { 536 listener.onMessage(((MessageHandle) messages[index]).getMessage()); 537 } 538 } catch (IndexOutOfBoundsException exception) { 539 // ignore the exception since the list is dynamic and may 540 // be modified while it is being processed. 541 } 542 } 543 } 544 545 /*** 546 * Return the specified message to top of the queue. This is called to 547 * recover unsent or unacked messages 548 * 549 * @param message - message to return 550 */ 551 public synchronized void returnMessage(MessageHandle handle) { 552 553 // add the message to the destination cache 554 addMessage(handle); 555 556 // if there are registered consumers then check whether 557 // any of them have registered message listeners 558 if (_consumers.size() > 0) { 559 // roll over the consumer index if it is greater 560 // than the number of registered consumers 561 if ((_lastConsumerIndex + 1) > _consumers.size()) { 562 _lastConsumerIndex = 0; 563 } 564 565 int index = 566 (_lastConsumerIndex >= _consumers.size()) ? 567 0 : _lastConsumerIndex; 568 569 do { 570 571 QueueConsumerEndpoint endpoint = 572 (QueueConsumerEndpoint) _consumers.get(index); 573 574 // if we find an endpoint with a listener then 575 // we should reschedule it. 576 if (endpoint.hasMessageListener()) { 577 endpoint.schedule(); 578 _lastConsumerIndex = ++index; 579 break; 580 } 581 582 // advance to the next consumer 583 if (++index >= _consumers.size()) { 584 index = 0; 585 } 586 } while (index != _lastConsumerIndex); 587 } 588 } 589 590 /*** 591 * Notify all the queue listeners, that this message has arrived. This is 592 * ideal for browsers and iterators 593 * 594 * @param message - message to deliver 595 */ 596 void notifyQueueListeners(MessageImpl message) { 597 if (!_queueListeners.isEmpty()) { 598 QueueListener[] listeners = 599 (QueueListener[]) _queueListeners.toArray( 600 new QueueListener[0]); 601 602 int size = listeners.length; 603 for (int index = 0; index < size; ++index) { 604 QueueListener listener = listeners[index]; 605 if (listener instanceof QueueBrowserEndpoint) { 606 QueueBrowserEndpoint browser = 607 (QueueBrowserEndpoint) listener; 608 Selector selector = browser.getSelector(); 609 610 // if a selector has been specified then apply the filter 611 // before sending down the message 612 if ((selector == null) || 613 (selector.selects(message))) { 614 browser.onMessage(message); 615 } 616 } else { 617 // if there is any other type of subscriber then just 618 // send the message to it. 619 listener.onMessage(message); 620 } 621 } 622 } 623 } 624 625 // implementation of DestinationCache.notifyOnAddMessage 626 boolean notifyOnAddMessage(MessageImpl message) { 627 return true; 628 } 629 630 // implementation of DestinationCache.notifyOnRemoveMessage 631 void notifyOnRemoveMessage(MessageImpl message) { 632 } 633 634 // implementation of DestinationCache.hasActiveConsumers 635 boolean hasActiveConsumers() { 636 boolean active = true; 637 if (_queueListeners.isEmpty() && _consumers.isEmpty()) { 638 active = false; 639 } 640 if (_log.isDebugEnabled()) { 641 _log.debug("hasActiveConsumers()[queue=" + _queue + "]=" + active); 642 } 643 return active; 644 } 645 646 /*** 647 * Determines if this cache can be destroyed. 648 * A <code>QueueDestinationCache</code> can be destroyed if there 649 * are no active consumers and: 650 * <ul> 651 * <li>the queue is persistent and there are no messages</li> 652 * <li> 653 * the queue is temporary and the corresponding connection is closed 654 * </li> 655 * </ul> 656 * 657 * @return <code>true</code> if the cache can be destroyed, otherwise 658 * <code>false</code> 659 */ 660 public boolean canDestroy() { 661 boolean destroy = false; 662 if (!hasActiveConsumers()) { 663 JmsDestination queue = getDestination(); 664 if (queue.getPersistent() && getMessageCount() == 0) { 665 destroy = true; 666 } else if (queue.isTemporaryDestination()) { 667 // check if there is a corresponding connection. If 668 // not, it has been closed, and the cache can be removed 669 String connectionId = 670 ((JmsTemporaryDestination) queue).getConnectionId(); 671 JmsServerConnectionManager manager = 672 JmsServerConnectionManager.instance(); 673 if (manager.getConnection(connectionId) == null) { 674 destroy = true; 675 } 676 } 677 } 678 return destroy; 679 } 680 681 /*** 682 * Destroy this object 683 */ 684 synchronized void destroy() { 685 super.destroy(); 686 _queueListeners.clear(); 687 } 688 689 // override Object.toString 690 public String toString() { 691 return _queue.toString(); 692 } 693 694 // override Object.hashCode 695 public int hashCode() { 696 return _queue.hashCode(); 697 } 698 699 } //-- QueueDestinationCache

This page was automatically generated by Maven