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: ConsumerEndpoint.java,v 1.36 2003/09/25 11:23:13 tanderson Exp $ 44 * 45 * Date Author Changes 46 * 3/1/2001 jima Created 47 */ 48 package org.exolab.jms.messagemgr; 49 50 import java.io.Serializable; 51 import java.sql.Connection; 52 import java.util.Enumeration; 53 import java.util.Vector; 54 55 import javax.jms.InvalidSelectorException; 56 import javax.jms.JMSException; 57 import javax.jms.Session; 58 import javax.transaction.TransactionManager; 59 60 import org.apache.commons.logging.Log; 61 import org.apache.commons.logging.LogFactory; 62 63 import org.exolab.jms.Identifiable; 64 import org.exolab.jms.client.JmsDestination; 65 import org.exolab.jms.message.MessageHandle; 66 import org.exolab.jms.message.MessageImpl; 67 import org.exolab.jms.persistence.PersistenceException; 68 import org.exolab.jms.scheduler.Scheduler; 69 import org.exolab.jms.selector.Selector; 70 import org.exolab.jms.server.JmsServerSession; 71 import org.exolab.jms.util.UUID; 72 73 74 /*** 75 * A Consumer is a message subscriber with a unique identity 76 * 77 * @version $Revision: 1.36 $ $Date: 2003/09/25 11:23:13 $ 78 * @author <a href="mailto:jima@exoffice.com">Jim Alateras</a> 79 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a> 80 */ 81 abstract public class ConsumerEndpoint 82 implements Serializable, Identifiable, DestinationCacheEventListener, 83 Runnable { 84 85 /*** 86 * The identity of the consumer 87 */ 88 private String _id; 89 90 /*** 91 * The client identity, which uniquely identifies the remote client 92 * within a session. This is used to tag messages when they are 93 * asynchronously delivered to the client 94 */ 95 private long _clientId = -1; 96 97 /*** 98 * The selector assoicated with this consumer. A selector is used 99 * to filter messages. 100 */ 101 protected Selector _selector = null; 102 103 /*** 104 * Serializes access to the {@link #_waitingForMessage} flag. This is 105 * only required when it is changed 106 */ 107 protected final Object _waitingForMessageMonitor = new Object(); 108 109 /*** 110 * Used to block consumer until there is a message available 111 */ 112 protected boolean _waitingForMessage = false;; 113 114 /*** 115 * Holds the consumer's message listener. This means that messages 116 * will be pushed down 117 */ 118 protected InternalMessageListener _listener = null; 119 120 /*** 121 * Maintains the maximum size of this cache 122 */ 123 protected int _size = 1000; 124 125 /*** 126 * This is the scheduler that is used to deliver messages if a consumer 127 * has a registered listener 128 */ 129 protected transient Scheduler _scheduler = null; 130 131 /*** 132 * The acknowledgement mode for this endpoint. 133 */ 134 protected transient int _ackMode = Session.AUTO_ACKNOWLEDGE; 135 136 /*** 137 * The nolocal indicator, if set, inhibits consuming messages that have 138 * been published on the same connection 139 */ 140 protected transient boolean _nolocal = false; 141 142 /*** 143 * Indicates whether this endpoint belongs to a transacted session 144 */ 145 protected transient boolean _transacted = false; 146 147 /*** 148 * Holds the connection id of the connection that the endpoint belongs too 149 */ 150 protected transient int _connectionId = -1; 151 152 /*** 153 * caches the session that created this endpoint 154 */ 155 protected JmsServerSession _session = null; 156 157 /*** 158 * This determines whether message delivery to the registered listener 159 * is enabled or disabled. 160 */ 161 private volatile boolean _stopped = true; 162 163 /*** 164 * Identifies this endpoint as being closed 165 */ 166 private volatile boolean _closed = false; 167 168 /*** 169 * Indicates whether the this cache has been scheduled with the dispatcher 170 * for asynchronous message delivery. 171 */ 172 private boolean _scheduled = false; 173 174 /*** 175 * Cache of all messages and handles for this consumer. 176 */ 177 private MessageCache _cache = new MessageCache(); 178 179 /*** 180 * Synchronization helper for close() and deliverMessages() 181 */ 182 private final Object _lock = new Object(); 183 184 /*** 185 * The logger 186 */ 187 private static final Log _log = LogFactory.getLog(ConsumerEndpoint.class); 188 189 190 /*** 191 * Construct a <code>ConsumerEndpoint</code>.</p> 192 * The destination and selector determine where it will be sourcing 193 * its messages from, and scheduler is used to asynchronously deliver 194 * messages to the consumer. 195 * 196 * @param session - the owning session 197 * @param clientId - uniquely identifies the remote client within session 198 * @param selector - the selector attached to the consumer, if any. 199 * @param scheduler - used to schedule async message delivery. 200 * @throws InvalidSelectorException if the selector is not well formed 201 */ 202 ConsumerEndpoint(JmsServerSession session, long clientId, 203 String selector, Scheduler scheduler) 204 throws InvalidSelectorException { 205 _id = UUID.next(); 206 _selector = (selector != null) ? new Selector(selector) : null; 207 _clientId = clientId; 208 _scheduler = scheduler; 209 _session = session; 210 } 211 212 /*** 213 * Return the destination that this consumer is subscribed to 214 * 215 * @return the destination that this consumer is subscribed to 216 */ 217 public abstract JmsDestination getDestination(); 218 219 // implementation of Identifiable.getId 220 public String getId() { 221 return _id; 222 } 223 224 /*** 225 * Returns the persistent identifier for this consumer.<p/> 226 * This is the identity of the consumer which is persistent across 227 * subscriptions and server restarts. <p/> 228 * 229 * This implementation simply returns the transient identifier, i.e, 230 * {@link #getId} 231 * 232 * @return the persistent identifier for this consumer 233 */ 234 public String getPersistentId() { 235 return getId(); 236 } 237 238 // implementation of Object.hashCode 239 public int hashCode() { 240 return _id.hashCode(); 241 } 242 243 /*** 244 * Return a stringified version of the consumer 245 * 246 * @return String 247 */ 248 public String toString() { 249 return _id + ":" + getDestination(); 250 } 251 252 /*** 253 * Unregister this consumer for the specified destination cache, so that it 254 * will stop receiving messages from it. 255 */ 256 public abstract void unregister(); 257 258 /*** 259 * Return a reference to the client identity. This is an indirect reference 260 * back to the remote client, which can asynchronously receive messages 261 * 262 * @return identity of the client scoped to the session 263 */ 264 public long getClientId() { 265 return _clientId; 266 } 267 268 /*** 269 * Return the selector for this endpoint or null if one is not specified 270 * 271 * @return String - the endpoint's selector 272 */ 273 public Selector getSelector() { 274 return _selector; 275 } 276 277 /*** 278 * Set the selector for this endpoint 279 * 280 * @param selector - message selector as a string 281 * @exception InvalidSelectorException - raised when selector is not 282 * well-formed 283 */ 284 public void setSelector(String selector) 285 throws InvalidSelectorException { 286 _selector = (selector != null) ? new Selector(selector) : null; 287 } 288 289 /*** 290 * Return the ackmode for this endpoint 291 * 292 * @return int - acknowledgement mode 293 */ 294 public int getAckMode() { 295 return _ackMode; 296 } 297 298 /*** 299 * Set the ackMode for this endpoint 300 * 301 * @param ackmode - the new ack mode for the endpoint 302 */ 303 public void setAckMode(int ackmode) { 304 _ackMode = ackmode; 305 } 306 307 /*** 308 * Return the connection id that this endpoint belongs to 309 * 310 * @return the connection id 311 */ 312 public int getConnectionId() { 313 return _connectionId; 314 } 315 316 /*** 317 * Set the connection that this endpooint belongs too 318 * 319 * @param id - connection identity 320 */ 321 public void setConnectionId(int id) { 322 _connectionId = id; 323 } 324 325 /*** 326 * Return the value of the nolocal indicator 327 * 328 * @return boolean 329 */ 330 public boolean getNoLocal() { 331 return _nolocal; 332 } 333 334 /*** 335 * Set the no local indicator for this endpoint 336 * 337 * @param nolocal - true to inhibit messages published on this connection 338 */ 339 public void setNoLocal(boolean nolocal) { 340 _nolocal = nolocal; 341 } 342 343 /*** 344 * Check whether this endpoint belongs to a transacted session 345 * 346 * @return boolean - true if it does 347 */ 348 public boolean getTransacted() { 349 return _transacted; 350 } 351 352 /*** 353 * Set the state if the transacted flag for this endpoint 354 * 355 * @param transacted - true if it is transacted 356 */ 357 public void setTransacted(boolean transacted) { 358 _transacted = transacted; 359 } 360 361 /*** 362 * Set the maximum size of the cache. If there are more than this number 363 * of messages in the cache the {@link CacheEvictionPolicy} is enforced 364 * to remove messages. 365 * 366 * @param size - maximum number of messages a destination can hold 367 */ 368 public void setMaximumSize(int size) { 369 _size = size; 370 } 371 372 /*** 373 * Return the cache's maximum size 374 * 375 * @return int - size of cache 376 */ 377 public int getMaximumSize() { 378 return _size; 379 } 380 381 /*** 382 * Set the {@link CacheEvictionPolicy} for this object. This determines 383 * how messages are removed when the cache's upper limit is reached. 384 * 385 * @param policy the eviction policy 386 */ 387 public void setCacheEvictionPolicy(CacheEvictionPolicy policy) { 388 // not implemented 389 } 390 391 /*** 392 * Return the number of unsent messages in the cache for this consumer 393 * 394 * @return the number of unsent messages 395 */ 396 public int getMessageCount() { 397 return _cache.getHandleCount(); 398 } 399 400 /*** 401 * Return a reference to the session owning this endpoint 402 * 403 * @return JmsServerSession - the owning session 404 */ 405 public JmsServerSession getSession() { 406 return _session; 407 } 408 409 /*** 410 * Deliver messages in the cache to the consumer 411 * 412 * @return <code>true</code> if the endpoint should be rescheduled 413 */ 414 public abstract boolean deliverMessages(); 415 416 /*** 417 * The run method is used to asynchronously deliver the messages in the 418 * cache to the consumer, by invoking {@link #deliverMessages}. 419 * <p> 420 * It is scheduled by the {@link Scheduler}. 421 */ 422 public void run() { 423 synchronized (_lock) { 424 if (!_closed) { 425 boolean reschedule = deliverMessages(); 426 _scheduled = false; 427 if (reschedule) { 428 schedule(); 429 } 430 } 431 } 432 } 433 434 // implementation of DestinationCacheEventListener.messageAdded 435 public synchronized boolean messageAdded(MessageImpl message) { 436 boolean added = false; 437 438 // create a message handle 439 try { 440 // if the nolocal indicator is set and the message arrived on 441 // the same connection, add this consumer then mark it as 442 // received, but do not add it to the queue 443 if (getNoLocal() 444 && message.getConnectionId() == getConnectionId()) { 445 // inform them that we have processed the message 446 return true; 447 } 448 449 MessageHandle handle = 450 MessageHandleFactory.getHandle(this, message); 451 452 if (!_cache.containsHandle(handle)) { 453 // if the message is not already in the cache then add it 454 // and flag that we have added the message to the cache 455 addMessage(handle, message); 456 added = true; 457 458 schedule(); 459 } 460 } catch (JMSException exception) { 461 _log.error("Failed to add message to endpoint", exception); 462 } 463 464 return added; 465 } 466 467 // implementation of DestinationCacheEventListener.messageRemoved 468 public synchronized boolean messageRemoved(MessageImpl message) { 469 boolean removed = false; 470 471 try { 472 //retrieve the message handle 473 MessageHandle handle = 474 MessageHandleFactory.getHandle(this, message); 475 476 if (_cache.containsHandle(handle)) { 477 // call remove regardless whether it exists 478 removeMessage(handle); 479 removed = true; 480 } 481 } catch (JMSException exception) { 482 _log.error("Failed to remove message from endpoint", exception); 483 } 484 485 return removed; 486 } 487 488 // implementation of DestinationCacheEventListener.persistentMessageAdded 489 public synchronized boolean persistentMessageAdded(Connection connection, 490 MessageImpl message) 491 throws PersistenceException { 492 493 return messageAdded(message); 494 } 495 496 // implementation of DestinationCacheEventListener.persistentMessageRemoved 497 public synchronized boolean persistentMessageRemoved(Connection connection, 498 MessageImpl message) 499 throws PersistenceException { 500 501 return messageRemoved(message); 502 } 503 504 /*** 505 * Stop/start message delivery 506 * 507 * @param stop if <code>true</code> to stop message delivery, otherwise 508 * start it 509 */ 510 public synchronized void setStopped(boolean stop) { 511 if (stop) { 512 _stopped = true; 513 } else { 514 _stopped = false; 515 // schedule message delivery if needed 516 schedule(); 517 } 518 } 519 520 /*** 521 * This message will return all unacked messages to the queue and allow 522 * them to be resent to the consumer with the redelivery flag on. 523 */ 524 public synchronized void recover() { 525 // default behaviour is to do nothing 526 } 527 528 /*** 529 * Close this endpoint. 530 * <p> 531 * This synchronizes with {@link #deliverMessages} before invoking 532 * @link {#doClose} 533 */ 534 public final void close() { 535 _stopped = true; 536 537 synchronized (_lock) { 538 // synchronize with deliverMessages() 539 _scheduler.remove(this); // remove this, if it is scheduled 540 _scheduled = false; 541 542 } 543 544 synchronized (this) { 545 doClose(); 546 547 // clear all messages in the cache 548 if (_cache != null) { 549 _cache.clear(); 550 } 551 552 _closed = true; 553 } 554 } 555 556 /*** 557 * Set the message listener for this consmer. If a message listener is set 558 * then messages will be scheduled to be sent to it when they are available 559 * <p> 560 * Each consumer cache can only have a single message listener. To remove 561 * the message listener call this method with null argument 562 * 563 * @param listener - the message listener to add. 564 */ 565 public synchronized void setMessageListener( 566 InternalMessageListener listener) { 567 _listener = listener; 568 if (listener == null) { 569 // remove this from the scheduler 570 _scheduler.remove(this); 571 _scheduled = false; 572 } else { 573 // scheduler for it to run 574 schedule(); 575 } 576 } 577 578 /*** 579 * Return the specified message to the cache. 580 * 581 * @param handle - handle to return 582 */ 583 public synchronized void returnMessage(MessageHandle handle) { 584 if (_cache != null) { 585 addMessage(handle); 586 schedule(); 587 } 588 } 589 590 /*** 591 * Return the next message to the client. This will also mark the message as 592 * sent and move it to the sent queue 593 * 594 * @param wait - the number of milliseconds to wait 595 * @return MessageHandle - handle to the next message in the list 596 */ 597 abstract public MessageHandle receiveMessage(long wait); 598 599 // implementation of GarbageCollectable.collectGarbage 600 public void collectGarbage(boolean aggressive) { 601 if (aggressive) { 602 // clear all persistent messages in the cache 603 _cache.clearPersistentMessages(); 604 if (_log.isDebugEnabled()) { 605 _log.debug("Evicted all persistent messages from dest " 606 + getDestination().getName() + " and name " 607 + getId()); 608 } 609 } 610 611 if (_log.isDebugEnabled()) { 612 _log.debug("ENDPOINT- " + getDestination().getName() + ":" 613 + getPersistentId() + " Messages: P[" 614 + _cache.getPersistentCount() + "] T[" 615 + _cache.getTransientCount() + "] Handles: [" 616 + _cache.getHandleCount() + "]"); 617 } 618 } 619 620 /*** 621 * Closes the endpoint 622 */ 623 protected abstract void doClose(); 624 625 /*** 626 * Schedule asynchronouse message delivery 627 */ 628 protected void schedule() { 629 if (!_stopped && !_closed && _listener != null && !_scheduled) { 630 _scheduled = true; 631 _scheduler.add(this); 632 } 633 } 634 635 /*** 636 * Clear all messages in the cache, regardless of whether they are 637 * persistent or non-persistent 638 */ 639 protected void clearMessages() { 640 _cache.clear(); 641 } 642 643 /*** 644 * Check whether the vector of handles contains one or more persistent 645 * handles 646 * 647 * @param handles - collection of {@link MessageHandle} objects 648 * @return true if there is one or more persistent handles 649 */ 650 protected boolean collectionHasPersistentHandles(Vector collection) { 651 boolean result = false; 652 Enumeration enum = collection.elements(); 653 654 while (enum.hasMoreElements()) { 655 if (enum.nextElement() instanceof PersistentMessageHandle) { 656 result = true; 657 break; 658 } 659 } 660 661 return result; 662 } 663 664 /*** 665 * Add the handle to the cache 666 * 667 * @param handle - the message handle to add 668 */ 669 protected void addMessage(MessageHandle handle) { 670 handle.setConsumerName(getPersistentId()); 671 _cache.addHandle(handle); 672 673 // check to see whether the consumer is waiting to 674 // be notified 675 if (isWaitingForMessage()) { 676 notifyMessageAvailable(); 677 } 678 } 679 680 /*** 681 * Cache a handle and its corresponding message 682 * 683 * @param handle the handle to cache 684 * @param message the corresponding message to cache 685 */ 686 protected void addMessage(MessageHandle handle, MessageImpl message) { 687 handle.setConsumerName(getPersistentId()); 688 _cache.addMessage(handle, message); 689 690 // check to see whether the consumer is waiting to 691 // be notified 692 if (isWaitingForMessage()) { 693 notifyMessageAvailable(); 694 } 695 } 696 697 /*** 698 * Return the message for the specified handle 699 * 700 * @param handle - the handle 701 * @return MessageImpl - the associated message 702 */ 703 protected MessageImpl getMessage(MessageHandle handle) { 704 return _cache.getMessage(handle); 705 } 706 707 /*** 708 * Remove the handle from the cache 709 * 710 * @param handle the handle to remove 711 * @return <code>true</code> if the message was removed 712 */ 713 protected boolean removeMessage(MessageHandle handle) { 714 return _cache.removeHandle(handle); 715 } 716 717 /*** 718 * Determines if a message handle is already cached 719 * 720 * @return <code>true</code> if it is cached 721 */ 722 protected boolean containsMessage(MessageHandle handle) { 723 return _cache.containsHandle(handle); 724 } 725 726 /*** 727 * Return the first message handle in the cache 728 * 729 * @return the first message or null if cache is empty 730 */ 731 protected MessageHandle removeFirstMessage() { 732 return _cache.removeFirstHandle(); 733 } 734 735 /*** 736 * Delete the message with the specified handle from the cache 737 * 738 * @param handle the handle 739 */ 740 protected void deleteMessage(MessageHandle handle) { 741 _cache.removeMessage(handle.getMessageId()); 742 } 743 744 /*** 745 * Determines if this endpoint has been stopped 746 * 747 * @return <code>true</code> if this endpoint has been stopped 748 */ 749 protected final boolean isStopped() { 750 return _stopped; 751 } 752 753 /*** 754 * Check if the consumer is waiting for a message. If it is then 755 * notify it that a message has arrived 756 */ 757 protected void notifyMessageAvailable() { 758 // if we need to notify then send out the request 759 if (isWaitingForMessage()) { 760 clearWaitingForMessage(); 761 762 try { 763 _session.onMessageAvailable(getClientId()); 764 } catch (Exception exception) { 765 //getLogger().logError("Error in notifyMessageAvailable " + 766 // getDestination().getName() + " " + exception.toString()); 767 } 768 } 769 } 770 771 /*** 772 * Check whether the endpoint is waiting for a message 773 * 774 * @return boolean 775 */ 776 protected final boolean isWaitingForMessage() { 777 return _waitingForMessage; 778 } 779 780 /*** 781 * Set the waiting for message flag 782 */ 783 protected final void setWaitingForMessage() { 784 _waitingForMessage = true; 785 } 786 787 /*** 788 * Clear the waiting for message flag 789 */ 790 protected final void clearWaitingForMessage() { 791 _waitingForMessage = false; 792 } 793 794 /*** 795 * Helper for {@link #deliverMessages} implementations, to determines if 796 * asynchronous message delivery should be stopped 797 * 798 * @return <code>true</code> if asynchronous message delivery should be 799 * stopped 800 */ 801 protected boolean stopDelivery() { 802 return (_stopped || getMessageCount() == 0 || _listener == null); 803 } 804 805 }

This page was automatically generated by Maven