001 /** 002 * 003 * Copyright 2004 Protique Ltd 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 **/ 018 019 package org.activemq; 020 import java.io.IOException; 021 import java.io.Serializable; 022 import java.util.Iterator; 023 import java.util.LinkedList; 024 import java.util.ListIterator; 025 import java.util.Map; 026 027 import javax.jms.BytesMessage; 028 import javax.jms.DeliveryMode; 029 import javax.jms.Destination; 030 import javax.jms.IllegalStateException; 031 import javax.jms.InvalidDestinationException; 032 import javax.jms.InvalidSelectorException; 033 import javax.jms.JMSException; 034 import javax.jms.MapMessage; 035 import javax.jms.Message; 036 import javax.jms.MessageConsumer; 037 import javax.jms.MessageListener; 038 import javax.jms.MessageProducer; 039 import javax.jms.ObjectMessage; 040 import javax.jms.Queue; 041 import javax.jms.QueueBrowser; 042 import javax.jms.QueueReceiver; 043 import javax.jms.QueueSender; 044 import javax.jms.QueueSession; 045 import javax.jms.Session; 046 import javax.jms.StreamMessage; 047 import javax.jms.TemporaryQueue; 048 import javax.jms.TemporaryTopic; 049 import javax.jms.TextMessage; 050 import javax.jms.Topic; 051 import javax.jms.TopicPublisher; 052 import javax.jms.TopicSession; 053 import javax.jms.TopicSubscriber; 054 import javax.jms.TransactionRolledBackException; 055 056 import org.activemq.io.util.ByteArray; 057 import org.activemq.io.util.ByteArrayCompression; 058 import org.activemq.io.util.ByteArrayFragmentation; 059 import org.activemq.management.JMSSessionStatsImpl; 060 import org.activemq.management.StatsCapable; 061 import org.activemq.management.StatsImpl; 062 import org.activemq.message.ActiveMQBytesMessage; 063 import org.activemq.message.ActiveMQDestination; 064 import org.activemq.message.ActiveMQMapMessage; 065 import org.activemq.message.ActiveMQMessage; 066 import org.activemq.message.ActiveMQObjectMessage; 067 import org.activemq.message.ActiveMQQueue; 068 import org.activemq.message.ActiveMQStreamMessage; 069 import org.activemq.message.ActiveMQTemporaryQueue; 070 import org.activemq.message.ActiveMQTemporaryTopic; 071 import org.activemq.message.ActiveMQTextMessage; 072 import org.activemq.message.ActiveMQTopic; 073 import org.activemq.message.ConsumerInfo; 074 import org.activemq.message.DurableUnsubscribe; 075 import org.activemq.message.MessageAck; 076 import org.activemq.message.MessageAcknowledge; 077 import org.activemq.message.ProducerInfo; 078 import org.activemq.service.impl.DefaultQueueList; 079 import org.activemq.util.IdGenerator; 080 import org.apache.commons.logging.Log; 081 import org.apache.commons.logging.LogFactory; 082 083 import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap; 084 import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArrayList; 085 import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean; 086 087 /** 088 * <P> 089 * A <CODE>Session</CODE> object is a single-threaded context for producing and consuming messages. Although it may 090 * allocate provider resources outside the Java virtual machine (JVM), it is considered a lightweight JMS object. 091 * <P> 092 * A session serves several purposes: 093 * <UL> 094 * <LI>It is a factory for its message producers and consumers. 095 * <LI>It supplies provider-optimized message factories. 096 * <LI>It is a factory for <CODE>TemporaryTopics</CODE> and <CODE>TemporaryQueues</CODE>. 097 * <LI>It provides a way to create <CODE>Queue</CODE> or <CODE>Topic</CODE> objects for those clients that need to 098 * dynamically manipulate provider-specific destination names. 099 * <LI>It supports a single series of transactions that combine work spanning its producers and consumers into atomic 100 * units. 101 * <LI>It defines a serial order for the messages it consumes and the messages it produces. 102 * <LI>It retains messages it consumes until they have been acknowledged. 103 * <LI>It serializes execution of message listeners registered with its message consumers. 104 * <LI>It is a factory for <CODE>QueueBrowsers</CODE>. 105 * </UL> 106 * <P> 107 * A session can create and service multiple message producers and consumers. 108 * <P> 109 * One typical use is to have a thread block on a synchronous <CODE>MessageConsumer</CODE> until a message arrives. 110 * The thread may then use one or more of the <CODE>Session</CODE>'s<CODE>MessageProducer</CODE>s. 111 * <P> 112 * If a client desires to have one thread produce messages while others consume them, the client should use a separate 113 * session for its producing thread. 114 * <P> 115 * Once a connection has been started, any session with one or more registered message listeners is dedicated to the 116 * thread of control that delivers messages to it. It is erroneous for client code to use this session or any of its 117 * constituent objects from another thread of control. The only exception to this rule is the use of the session or 118 * connection <CODE>close</CODE> method. 119 * <P> 120 * It should be easy for most clients to partition their work naturally into sessions. This model allows clients to 121 * start simply and incrementally add message processing complexity as their need for concurrency grows. 122 * <P> 123 * The <CODE>close</CODE> method is the only session method that can be called while some other session method is 124 * being executed in another thread. 125 * <P> 126 * A session may be specified as transacted. Each transacted session supports a single series of transactions. Each 127 * transaction groups a set of message sends and a set of message receives into an atomic unit of work. In effect, 128 * transactions organize a session's input message stream and output message stream into series of atomic units. When a 129 * transaction commits, its atomic unit of input is acknowledged and its associated atomic unit of output is sent. If a 130 * transaction rollback is done, the transaction's sent messages are destroyed and the session's input is automatically 131 * recovered. 132 * <P> 133 * The content of a transaction's input and output units is simply those messages that have been produced and consumed 134 * within the session's current transaction. 135 * <P> 136 * A transaction is completed using either its session's <CODE>commit</CODE> method or its session's <CODE>rollback 137 * </CODE> method. The completion of a session's current transaction automatically begins the next. The result is that a 138 * transacted session always has a current transaction within which its work is done. 139 * <P> 140 * The Java Transaction Service (JTS) or some other transaction monitor may be used to combine a session's transaction 141 * with transactions on other resources (databases, other JMS sessions, etc.). Since Java distributed transactions are 142 * controlled via the Java Transaction API (JTA), use of the session's <CODE>commit</CODE> and <CODE>rollback</CODE> 143 * methods in this context is prohibited. 144 * <P> 145 * The JMS API does not require support for JTA; however, it does define how a provider supplies this support. 146 * <P> 147 * Although it is also possible for a JMS client to handle distributed transactions directly, it is unlikely that many 148 * JMS clients will do this. Support for JTA in the JMS API is targeted at systems vendors who will be integrating the 149 * JMS API into their application server products. 150 * 151 * @version $Revision: 1.1.1.1 $ 152 * @see javax.jms.Session 153 * @see javax.jms.QueueSession 154 * @see javax.jms.TopicSession 155 * @see javax.jms.XASession 156 */ 157 public class ActiveMQSession 158 implements 159 Session, 160 QueueSession, 161 TopicSession, 162 ActiveMQMessageDispatcher, 163 MessageAcknowledge, 164 StatsCapable { 165 166 public static interface DeliveryListener { 167 public void beforeDelivery(ActiveMQSession session, Message msg); 168 public void afterDelivery(ActiveMQSession session, Message msg); 169 } 170 171 protected static final int CONSUMER_DISPATCH_UNSET = 1; 172 protected static final int CONSUMER_DISPATCH_ASYNC = 2; 173 protected static final int CONSUMER_DISPATCH_SYNC = 3; 174 private static final Log log = LogFactory.getLog(ActiveMQSession.class); 175 protected ActiveMQConnection connection; 176 protected int acknowledgeMode; 177 protected CopyOnWriteArrayList consumers; 178 protected CopyOnWriteArrayList producers; 179 private IdGenerator temporaryDestinationGenerator; 180 private MessageListener messageListener; 181 protected boolean closed; 182 private SynchronizedBoolean started; 183 private short sessionId; 184 private long startTime; 185 private DefaultQueueList deliveredMessages; 186 private ActiveMQSessionExecutor messageExecutor; 187 private JMSSessionStatsImpl stats; 188 private int consumerDispatchState; 189 private ByteArrayCompression compression; 190 private ByteArrayFragmentation fragmentation; 191 private Map assemblies; //used for assembling message fragments 192 private TransactionContext transactionContext; 193 private boolean internalSession; 194 private DeliveryListener deliveryListener; 195 196 /** 197 * Construct the Session 198 * 199 * @param theConnection 200 * @param theAcknowledgeMode n.b if transacted - the acknowledgeMode == Session.SESSION_TRANSACTED 201 * @throws JMSException on internal error 202 */ 203 protected ActiveMQSession(ActiveMQConnection theConnection, int theAcknowledgeMode) throws JMSException { 204 this(theConnection, theAcknowledgeMode,theConnection.isOptimizedMessageDispatch()); 205 } 206 207 /** 208 * Construct the Session 209 * 210 * @param theConnection 211 * @param theAcknowledgeMode n.b if transacted - the acknowledgeMode == Session.SESSION_TRANSACTED 212 * @param optimizedDispatch 213 * @throws JMSException on internal error 214 */ 215 protected ActiveMQSession(ActiveMQConnection theConnection, int theAcknowledgeMode,boolean optimizedDispatch) throws JMSException { 216 this.connection = theConnection; 217 this.acknowledgeMode = theAcknowledgeMode; 218 setTransactionContext(new TransactionContext(theConnection)); 219 this.consumers = new CopyOnWriteArrayList(); 220 this.producers = new CopyOnWriteArrayList(); 221 this.temporaryDestinationGenerator = new IdGenerator(); 222 this.started = new SynchronizedBoolean(false); 223 this.sessionId = connection.generateSessionId(); 224 this.startTime = System.currentTimeMillis(); 225 this.deliveredMessages = new DefaultQueueList(); 226 this.messageExecutor = new ActiveMQSessionExecutor(this, connection.getMemoryBoundedQueue("Session(" 227 + sessionId + ")")); 228 this.messageExecutor.setOptimizedMessageDispatch(optimizedDispatch); 229 connection.addSession(this); 230 stats = new JMSSessionStatsImpl(producers, consumers); 231 this.consumerDispatchState = CONSUMER_DISPATCH_UNSET; 232 this.compression = new ByteArrayCompression(); 233 this.compression.setCompressionLevel(theConnection.getMessageCompressionLevel()); 234 this.compression.setCompressionStrategy(theConnection.getMessageCompressionStrategy()); 235 this.compression.setCompressionLimit(theConnection.getMessageCompressionLimit()); 236 237 this.fragmentation = new ByteArrayFragmentation(); 238 this.fragmentation.setFragmentationLimit(theConnection.getMessageFragmentationLimit()); 239 this.assemblies = new ConcurrentHashMap(); 240 this.internalSession = theConnection.isInternalConnection(); 241 } 242 243 public void setTransactionContext(TransactionContext transactionContext) { 244 if( this.transactionContext!=null ) { 245 this.transactionContext.removeSession(this); 246 } 247 this.transactionContext = transactionContext; 248 this.transactionContext.addSession(this); 249 } 250 251 public TransactionContext getTransactionContext() { 252 return transactionContext; 253 } 254 255 public StatsImpl getStats() { 256 return stats; 257 } 258 259 public JMSSessionStatsImpl getSessionStats() { 260 return stats; 261 } 262 263 /** 264 * Creates a <CODE>BytesMessage</CODE> object. A <CODE>BytesMessage</CODE> object is used to send a message 265 * containing a stream of uninterpreted bytes. 266 * 267 * @return the an ActiveMQBytesMessage 268 * @throws JMSException if the JMS provider fails to create this message due to some internal error. 269 */ 270 public BytesMessage createBytesMessage() throws JMSException { 271 checkClosed(); 272 return new ActiveMQBytesMessage(); 273 } 274 275 /** 276 * Creates a <CODE>MapMessage</CODE> object. A <CODE>MapMessage</CODE> object is used to send a self-defining 277 * set of name-value pairs, where names are <CODE>String</CODE> objects and values are primitive values in the 278 * Java programming language. 279 * 280 * @return an ActiveMQMapMessage 281 * @throws JMSException if the JMS provider fails to create this message due to some internal error. 282 */ 283 public MapMessage createMapMessage() throws JMSException { 284 checkClosed(); 285 return new ActiveMQMapMessage(); 286 } 287 288 /** 289 * Creates a <CODE>Message</CODE> object. The <CODE>Message</CODE> interface is the root interface of all JMS 290 * messages. A <CODE>Message</CODE> object holds all the standard message header information. It can be sent when 291 * a message containing only header information is sufficient. 292 * 293 * @return an ActiveMQMessage 294 * @throws JMSException if the JMS provider fails to create this message due to some internal error. 295 */ 296 public Message createMessage() throws JMSException { 297 checkClosed(); 298 return new ActiveMQMessage(); 299 } 300 301 /** 302 * Creates an <CODE>ObjectMessage</CODE> object. An <CODE>ObjectMessage</CODE> object is used to send a message 303 * that contains a serializable Java object. 304 * 305 * @return an ActiveMQObjectMessage 306 * @throws JMSException if the JMS provider fails to create this message due to some internal error. 307 */ 308 public ObjectMessage createObjectMessage() throws JMSException { 309 checkClosed(); 310 return new ActiveMQObjectMessage(); 311 } 312 313 /** 314 * Creates an initialized <CODE>ObjectMessage</CODE> object. An <CODE>ObjectMessage</CODE> object is used to 315 * send a message that contains a serializable Java object. 316 * 317 * @param object the object to use to initialize this message 318 * @return an ActiveMQObjectMessage 319 * @throws JMSException if the JMS provider fails to create this message due to some internal error. 320 */ 321 public ObjectMessage createObjectMessage(Serializable object) throws JMSException { 322 checkClosed(); 323 ActiveMQObjectMessage msg = new ActiveMQObjectMessage(); 324 msg.setObject(object); 325 return msg; 326 } 327 328 /** 329 * Creates a <CODE>StreamMessage</CODE> object. A <CODE>StreamMessage</CODE> object is used to send a 330 * self-defining stream of primitive values in the Java programming language. 331 * 332 * @return an ActiveMQStreamMessage 333 * @throws JMSException if the JMS provider fails to create this message due to some internal error. 334 */ 335 public StreamMessage createStreamMessage() throws JMSException { 336 checkClosed(); 337 return new ActiveMQStreamMessage(); 338 } 339 340 /** 341 * Creates a <CODE>TextMessage</CODE> object. A <CODE>TextMessage</CODE> object is used to send a message 342 * containing a <CODE>String</CODE> object. 343 * 344 * @return an ActiveMQTextMessage 345 * @throws JMSException if the JMS provider fails to create this message due to some internal error. 346 */ 347 public TextMessage createTextMessage() throws JMSException { 348 checkClosed(); 349 return new ActiveMQTextMessage(); 350 } 351 352 /** 353 * Creates an initialized <CODE>TextMessage</CODE> object. A <CODE>TextMessage</CODE> object is used to send a 354 * message containing a <CODE>String</CODE>. 355 * 356 * @param text the string used to initialize this message 357 * @return an ActiveMQTextMessage 358 * @throws JMSException if the JMS provider fails to create this message due to some internal error. 359 */ 360 public TextMessage createTextMessage(String text) throws JMSException { 361 checkClosed(); 362 ActiveMQTextMessage msg = new ActiveMQTextMessage(); 363 msg.setText(text); 364 return msg; 365 } 366 367 /** 368 * Indicates whether the session is in transacted mode. 369 * 370 * @return true if the session is in transacted mode 371 * @throws JMSException if there is some internal error. 372 */ 373 public boolean getTransacted() throws JMSException { 374 checkClosed(); 375 return this.acknowledgeMode == Session.SESSION_TRANSACTED || transactionContext.isInXATransaction(); 376 } 377 378 /** 379 * Returns the acknowledgement mode of the session. The acknowledgement mode is set at the time that the session is 380 * created. If the session is transacted, the acknowledgement mode is ignored. 381 * 382 * @return If the session is not transacted, returns the current acknowledgement mode for the session. If the 383 * session is transacted, returns SESSION_TRANSACTED. 384 * @throws JMSException 385 * @see javax.jms.Connection#createSession(boolean,int) 386 * @since 1.1 exception JMSException if there is some internal error. 387 */ 388 public int getAcknowledgeMode() throws JMSException { 389 checkClosed(); 390 return this.acknowledgeMode; 391 } 392 393 /** 394 * Commits all messages done in this transaction and releases any locks currently held. 395 * 396 * @throws JMSException if the JMS provider fails to commit the transaction due to some internal error. 397 * @throws TransactionRolledBackException if the transaction is rolled back due to some internal error during 398 * commit. 399 * @throws javax.jms.IllegalStateException if the method is not called by a transacted session. 400 */ 401 public void commit() throws JMSException { 402 checkClosed(); 403 if (!getTransacted()) { 404 throw new javax.jms.IllegalStateException("Not a transacted session"); 405 } 406 transactionContext.commit(); 407 } 408 409 /** 410 * Rolls back any messages done in this transaction and releases any locks currently held. 411 * 412 * @throws JMSException if the JMS provider fails to roll back the transaction due to some internal error. 413 * @throws javax.jms.IllegalStateException if the method is not called by a transacted session. 414 */ 415 public void rollback() throws JMSException { 416 checkClosed(); 417 if (!getTransacted()) { 418 throw new javax.jms.IllegalStateException("Not a transacted session"); 419 } 420 transactionContext.rollback(); 421 } 422 423 public void clearDeliveredMessages() { 424 deliveredMessages.clear(); 425 } 426 427 /** 428 * Closes the session. 429 * <P> 430 * Since a provider may allocate some resources on behalf of a session outside the JVM, clients should close the 431 * resources when they are not needed. Relying on garbage collection to eventually reclaim these resources may not 432 * be timely enough. 433 * <P> 434 * There is no need to close the producers and consumers of a closed session. 435 * <P> 436 * This call will block until a <CODE>receive</CODE> call or message listener in progress has completed. A blocked 437 * message consumer <CODE>receive</CODE> call returns <CODE>null</CODE> when this session is closed. 438 * <P> 439 * Closing a transacted session must roll back the transaction in progress. 440 * <P> 441 * This method is the only <CODE>Session</CODE> method that can be called concurrently. 442 * <P> 443 * Invoking any other <CODE>Session</CODE> method on a closed session must throw a <CODE> 444 * JMSException.IllegalStateException</CODE>. Closing a closed session must <I>not </I> throw an exception. 445 * 446 * @throws JMSException if the JMS provider fails to close the session due to some internal error. 447 */ 448 public void close() throws JMSException { 449 if (!this.closed) { 450 if (getTransactionContext().isInLocalTransaction()) { 451 rollback(); 452 } 453 doClose(); 454 closed = true; 455 } 456 } 457 458 protected void doClose() throws JMSException { 459 doAcknowledge(true); 460 deliveredMessages.clear(); 461 for (Iterator i = consumers.iterator();i.hasNext();) { 462 ActiveMQMessageConsumer consumer = (ActiveMQMessageConsumer) i.next(); 463 consumer.close(); 464 } 465 for (Iterator i = producers.iterator();i.hasNext();) { 466 ActiveMQMessageProducer producer = (ActiveMQMessageProducer) i.next(); 467 producer.close(); 468 } 469 consumers.clear(); 470 producers.clear(); 471 this.connection.removeSession(this); 472 messageExecutor.close(); 473 } 474 475 /** 476 * @throws IllegalStateException if the Session is closed 477 */ 478 protected void checkClosed() throws IllegalStateException { 479 if (this.closed) { 480 throw new IllegalStateException("The Session is closed"); 481 } 482 } 483 484 /** 485 * Stops message delivery in this session, and restarts message delivery with the oldest unacknowledged message. 486 * <P> 487 * All consumers deliver messages in a serial order. Acknowledging a received message automatically acknowledges all 488 * messages that have been delivered to the client. 489 * <P> 490 * Restarting a session causes it to take the following actions: 491 * <UL> 492 * <LI>Stop message delivery 493 * <LI>Mark all messages that might have been delivered but not acknowledged as "redelivered" 494 * <LI>Restart the delivery sequence including all unacknowledged messages that had been previously delivered. 495 * Redelivered messages do not have to be delivered in exactly their original delivery order. 496 * </UL> 497 * 498 * @throws JMSException if the JMS provider fails to stop and restart message delivery due to some internal error. 499 * @throws IllegalStateException if the method is called by a transacted session. 500 */ 501 public void recover() throws JMSException { 502 checkClosed(); 503 if (getTransacted()) { 504 throw new IllegalStateException("This session is transacted"); 505 } 506 redeliverUnacknowledgedMessages(); 507 } 508 509 /** 510 * Returns the session's distinguished message listener (optional). 511 * 512 * @return the message listener associated with this session 513 * @throws JMSException if the JMS provider fails to get the message listener due to an internal error. 514 * @see javax.jms.Session#setMessageListener(javax.jms.MessageListener) 515 * @see javax.jms.ServerSessionPool 516 * @see javax.jms.ServerSession 517 */ 518 public MessageListener getMessageListener() throws JMSException { 519 checkClosed(); 520 return this.messageListener; 521 } 522 523 /** 524 * Sets the session's distinguished message listener (optional). 525 * <P> 526 * When the distinguished message listener is set, no other form of message receipt in the session can be used; 527 * however, all forms of sending messages are still supported. 528 * <P> 529 * This is an expert facility not used by regular JMS clients. 530 * 531 * @param listener the message listener to associate with this session 532 * @throws JMSException if the JMS provider fails to set the message listener due to an internal error. 533 * @see javax.jms.Session#getMessageListener() 534 * @see javax.jms.ServerSessionPool 535 * @see javax.jms.ServerSession 536 */ 537 public void setMessageListener(MessageListener listener) throws JMSException { 538 checkClosed(); 539 this.messageListener = listener; 540 if (listener != null) { 541 messageExecutor.setDispatchedBySessionPool(true); 542 } 543 } 544 545 /** 546 * Optional operation, intended to be used only by Application Servers, not by ordinary JMS clients. 547 * 548 * @see javax.jms.ServerSession 549 */ 550 public void run() { 551 ActiveMQMessage message; 552 while ((message = messageExecutor.dequeueNoWait()) != null) { 553 if( deliveryListener!=null ) 554 deliveryListener.beforeDelivery(this, message); 555 beforeMessageDelivered(message); 556 deliver(message); 557 if( deliveryListener!=null ) 558 deliveryListener.afterDelivery(this, message); 559 } 560 } 561 562 /** 563 * Delivers a message to the messageListern 564 * @param message The message to deliver 565 */ 566 private void deliver(ActiveMQMessage message) { 567 message = assembleMessage(message); 568 if (message != null && !message.isExpired() && this.messageListener != null) { 569 try { 570 571 if( log.isDebugEnabled() ) { 572 log.debug("Message delivered to session message listener: "+message); 573 } 574 575 this.messageListener.onMessage(message); 576 this.afterMessageDelivered(true, message, true, false, true); 577 } 578 catch (Throwable t) { 579 log.info("Caught :" + t, t); 580 this.afterMessageDelivered(true, message, false, false, true); 581 } 582 } 583 else { 584 this.afterMessageDelivered(true, message, false, message.isExpired(), true); 585 } 586 } 587 588 /** 589 * Creates a <CODE>MessageProducer</CODE> to send messages to the specified destination. 590 * <P> 591 * A client uses a <CODE>MessageProducer</CODE> object to send messages to a destination. Since <CODE>Queue 592 * </CODE> and <CODE>Topic</CODE> both inherit from <CODE>Destination</CODE>, they can be used in the 593 * destination parameter to create a <CODE>MessageProducer</CODE> object. 594 * 595 * @param destination the <CODE>Destination</CODE> to send to, or null if this is a producer which does not have a 596 * specified destination. 597 * @return the MessageProducer 598 * @throws JMSException if the session fails to create a MessageProducer due to some internal error. 599 * @throws InvalidDestinationException if an invalid destination is specified. 600 * @since 1.1 601 */ 602 public MessageProducer createProducer(Destination destination) throws JMSException { 603 checkClosed(); 604 return new ActiveMQMessageProducer(this, ActiveMQMessageTransformation.transformDestination(destination)); 605 } 606 607 /** 608 * Creates a <CODE>MessageConsumer</CODE> for the specified destination. Since <CODE>Queue</CODE> and <CODE> 609 * Topic</CODE> both inherit from <CODE>Destination</CODE>, they can be used in the destination parameter to 610 * create a <CODE>MessageConsumer</CODE>. 611 * 612 * @param destination the <CODE>Destination</CODE> to access. 613 * @return the MessageConsumer 614 * @throws JMSException if the session fails to create a consumer due to some internal error. 615 * @throws InvalidDestinationException if an invalid destination is specified. 616 * @since 1.1 617 */ 618 public MessageConsumer createConsumer(Destination destination) throws JMSException { 619 checkClosed(); 620 int prefetch = destination instanceof Topic ? connection.getPrefetchPolicy().getTopicPrefetch() : connection 621 .getPrefetchPolicy().getQueuePrefetch(); 622 return new ActiveMQMessageConsumer(this, ActiveMQMessageTransformation.transformDestination(destination), "", 623 "", this.connection.getNextConsumerNumber(), prefetch, false, false); 624 } 625 626 /** 627 * Creates a <CODE>MessageConsumer</CODE> for the specified destination, using a message selector. Since <CODE> 628 * Queue</CODE> and <CODE>Topic</CODE> both inherit from <CODE>Destination</CODE>, they can be used in the 629 * destination parameter to create a <CODE>MessageConsumer</CODE>. 630 * <P> 631 * A client uses a <CODE>MessageConsumer</CODE> object to receive messages that have been sent to a destination. 632 * 633 * @param destination the <CODE>Destination</CODE> to access 634 * @param messageSelector only messages with properties matching the message selector expression are delivered. A 635 * value of null or an empty string indicates that there is no message selector for the message consumer. 636 * @return the MessageConsumer 637 * @throws JMSException if the session fails to create a MessageConsumer due to some internal error. 638 * @throws InvalidDestinationException if an invalid destination is specified. 639 * @throws InvalidSelectorException if the message selector is invalid. 640 * @since 1.1 641 */ 642 public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException { 643 checkClosed(); 644 int prefetch = destination instanceof Topic ? connection.getPrefetchPolicy().getTopicPrefetch() : connection 645 .getPrefetchPolicy().getQueuePrefetch(); 646 return new ActiveMQMessageConsumer(this, ActiveMQMessageTransformation.transformDestination(destination), "", 647 messageSelector, this.connection.getNextConsumerNumber(), prefetch, false, false); 648 } 649 650 /** 651 * Creates <CODE>MessageConsumer</CODE> for the specified destination, using a message selector. This method can 652 * specify whether messages published by its own connection should be delivered to it, if the destination is a 653 * topic. 654 * <P> 655 * Since <CODE>Queue</CODE> and <CODE>Topic</CODE> both inherit from <CODE>Destination</CODE>, they can be 656 * used in the destination parameter to create a <CODE>MessageConsumer</CODE>. 657 * <P> 658 * A client uses a <CODE>MessageConsumer</CODE> object to receive messages that have been published to a 659 * destination. 660 * <P> 661 * In some cases, a connection may both publish and subscribe to a topic. The consumer <CODE>NoLocal</CODE> 662 * attribute allows a consumer to inhibit the delivery of messages published by its own connection. The default 663 * value for this attribute is False. The <CODE>noLocal</CODE> value must be supported by destinations that are 664 * topics. 665 * 666 * @param destination the <CODE>Destination</CODE> to access 667 * @param messageSelector only messages with properties matching the message selector expression are delivered. A 668 * value of null or an empty string indicates that there is no message selector for the message consumer. 669 * @param NoLocal - if true, and the destination is a topic, inhibits the delivery of messages published by its own 670 * connection. The behavior for <CODE>NoLocal</CODE> is not specified if the destination is a queue. 671 * @return the MessageConsumer 672 * @throws JMSException if the session fails to create a MessageConsumer due to some internal error. 673 * @throws InvalidDestinationException if an invalid destination is specified. 674 * @throws InvalidSelectorException if the message selector is invalid. 675 * @since 1.1 676 */ 677 public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean NoLocal) 678 throws JMSException { 679 checkClosed(); 680 int prefetch = connection.getPrefetchPolicy().getTopicPrefetch(); 681 return new ActiveMQMessageConsumer(this, ActiveMQMessageTransformation.transformDestination(destination), "", 682 messageSelector, this.connection.getNextConsumerNumber(), prefetch, NoLocal, false); 683 } 684 685 /** 686 * Creates a queue identity given a <CODE>Queue</CODE> name. 687 * <P> 688 * This facility is provided for the rare cases where clients need to dynamically manipulate queue identity. It 689 * allows the creation of a queue identity with a provider-specific name. Clients that depend on this ability are 690 * not portable. 691 * <P> 692 * Note that this method is not for creating the physical queue. The physical creation of queues is an 693 * administrative task and is not to be initiated by the JMS API. The one exception is the creation of temporary 694 * queues, which is accomplished with the <CODE>createTemporaryQueue</CODE> method. 695 * 696 * @param queueName the name of this <CODE>Queue</CODE> 697 * @return a <CODE>Queue</CODE> with the given name 698 * @throws JMSException if the session fails to create a queue due to some internal error. 699 * @since 1.1 700 */ 701 public Queue createQueue(String queueName) throws JMSException { 702 checkClosed(); 703 return new ActiveMQQueue(queueName); 704 } 705 706 /** 707 * Creates a topic identity given a <CODE>Topic</CODE> name. 708 * <P> 709 * This facility is provided for the rare cases where clients need to dynamically manipulate topic identity. This 710 * allows the creation of a topic identity with a provider-specific name. Clients that depend on this ability are 711 * not portable. 712 * <P> 713 * Note that this method is not for creating the physical topic. The physical creation of topics is an 714 * administrative task and is not to be initiated by the JMS API. The one exception is the creation of temporary 715 * topics, which is accomplished with the <CODE>createTemporaryTopic</CODE> method. 716 * 717 * @param topicName the name of this <CODE>Topic</CODE> 718 * @return a <CODE>Topic</CODE> with the given name 719 * @throws JMSException if the session fails to create a topic due to some internal error. 720 * @since 1.1 721 */ 722 public Topic createTopic(String topicName) throws JMSException { 723 checkClosed(); 724 return new ActiveMQTopic(topicName); 725 } 726 727 /** 728 * Creates a <CODE>QueueBrowser</CODE> object to peek at the messages on the specified queue. 729 * 730 * @param queue the <CODE>queue</CODE> to access 731 * @exception InvalidDestinationException if an invalid destination is specified 732 * @since 1.1 733 */ 734 /** 735 * Creates a durable subscriber to the specified topic. 736 * <P> 737 * If a client needs to receive all the messages published on a topic, including the ones published while the 738 * subscriber is inactive, it uses a durable <CODE>TopicSubscriber</CODE>. The JMS provider retains a record of 739 * this durable subscription and insures that all messages from the topic's publishers are retained until they are 740 * acknowledged by this durable subscriber or they have expired. 741 * <P> 742 * Sessions with durable subscribers must always provide the same client identifier. In addition, each client must 743 * specify a name that uniquely identifies (within client identifier) each durable subscription it creates. Only one 744 * session at a time can have a <CODE>TopicSubscriber</CODE> for a particular durable subscription. 745 * <P> 746 * A client can change an existing durable subscription by creating a durable <CODE>TopicSubscriber</CODE> with 747 * the same name and a new topic and/or message selector. Changing a durable subscriber is equivalent to 748 * unsubscribing (deleting) the old one and creating a new one. 749 * <P> 750 * In some cases, a connection may both publish and subscribe to a topic. The subscriber <CODE>NoLocal</CODE> 751 * attribute allows a subscriber to inhibit the delivery of messages published by its own connection. The default 752 * value for this attribute is false. 753 * 754 * @param topic the non-temporary <CODE>Topic</CODE> to subscribe to 755 * @param name the name used to identify this subscription 756 * @return the TopicSubscriber 757 * @throws JMSException if the session fails to create a subscriber due to some internal error. 758 * @throws InvalidDestinationException if an invalid topic is specified. 759 * @since 1.1 760 */ 761 public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException { 762 checkClosed(); 763 return new ActiveMQTopicSubscriber(this, ActiveMQMessageTransformation.transformDestination(topic), name, "", 764 this.connection.getNextConsumerNumber(), this.connection.getPrefetchPolicy().getDurableTopicPrefetch(), 765 false, false); 766 } 767 768 /** 769 * Creates a durable subscriber to the specified topic, using a message selector and specifying whether messages 770 * published by its own connection should be delivered to it. 771 * <P> 772 * If a client needs to receive all the messages published on a topic, including the ones published while the 773 * subscriber is inactive, it uses a durable <CODE>TopicSubscriber</CODE>. The JMS provider retains a record of 774 * this durable subscription and insures that all messages from the topic's publishers are retained until they are 775 * acknowledged by this durable subscriber or they have expired. 776 * <P> 777 * Sessions with durable subscribers must always provide the same client identifier. In addition, each client must 778 * specify a name which uniquely identifies (within client identifier) each durable subscription it creates. Only 779 * one session at a time can have a <CODE>TopicSubscriber</CODE> for a particular durable subscription. An 780 * inactive durable subscriber is one that exists but does not currently have a message consumer associated with it. 781 * <P> 782 * A client can change an existing durable subscription by creating a durable <CODE>TopicSubscriber</CODE> with 783 * the same name and a new topic and/or message selector. Changing a durable subscriber is equivalent to 784 * unsubscribing (deleting) the old one and creating a new one. 785 * 786 * @param topic the non-temporary <CODE>Topic</CODE> to subscribe to 787 * @param name the name used to identify this subscription 788 * @param messageSelector only messages with properties matching the message selector expression are delivered. A 789 * value of null or an empty string indicates that there is no message selector for the message consumer. 790 * @param noLocal if set, inhibits the delivery of messages published by its own connection 791 * @return the Queue Browser 792 * @throws JMSException if the session fails to create a subscriber due to some internal error. 793 * @throws InvalidDestinationException if an invalid topic is specified. 794 * @throws InvalidSelectorException if the message selector is invalid. 795 * @since 1.1 796 */ 797 public TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal) 798 throws JMSException { 799 checkClosed(); 800 return new ActiveMQTopicSubscriber(this, ActiveMQMessageTransformation.transformDestination(topic), name, 801 messageSelector, this.connection.getNextConsumerNumber(), this.connection.getPrefetchPolicy() 802 .getDurableTopicPrefetch(), noLocal, false); 803 } 804 805 /** 806 * Creates a <CODE>QueueBrowser</CODE> object to peek at the messages on the specified queue. 807 * 808 * @param queue the <CODE>queue</CODE> to access 809 * @return the Queue Browser 810 * @throws JMSException if the session fails to create a browser due to some internal error. 811 * @throws InvalidDestinationException if an invalid destination is specified 812 * @since 1.1 813 */ 814 public QueueBrowser createBrowser(Queue queue) throws JMSException { 815 checkClosed(); 816 return new ActiveMQQueueBrowser(this, (ActiveMQQueue) ActiveMQMessageTransformation.transformDestination(queue), "", 817 this.connection.getNextConsumerNumber()); 818 } 819 820 /** 821 * Creates a <CODE>QueueBrowser</CODE> object to peek at the messages on the specified queue using a message 822 * selector. 823 * 824 * @param queue the <CODE>queue</CODE> to access 825 * @param messageSelector only messages with properties matching the message selector expression are delivered. A 826 * value of null or an empty string indicates that there is no message selector for the message consumer. 827 * @return the Queue Browser 828 * @throws JMSException if the session fails to create a browser due to some internal error. 829 * @throws InvalidDestinationException if an invalid destination is specified 830 * @throws InvalidSelectorException if the message selector is invalid. 831 * @since 1.1 832 */ 833 public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException { 834 checkClosed(); 835 return new ActiveMQQueueBrowser(this, (ActiveMQQueue) ActiveMQMessageTransformation.transformDestination(queue), 836 messageSelector, this.connection.getNextConsumerNumber()); 837 } 838 839 /** 840 * Creates a <CODE>TemporaryQueue</CODE> object. Its lifetime will be that of the <CODE>Connection</CODE> unless 841 * it is deleted earlier. 842 * 843 * @return a temporary queue identity 844 * @throws JMSException if the session fails to create a temporary queue due to some internal error. 845 * @since 1.1 846 */ 847 public TemporaryQueue createTemporaryQueue() throws JMSException { 848 checkClosed(); 849 String tempQueueName = "TemporaryQueue-" 850 + ActiveMQDestination.createTemporaryName(this.connection.getInitializedClientID()); 851 tempQueueName += this.temporaryDestinationGenerator.generateId(); 852 ActiveMQTemporaryQueue tempQueue = new ActiveMQTemporaryQueue(tempQueueName); 853 tempQueue.setSessionCreatedBy(this); 854 this.connection.startTemporaryDestination(tempQueue); 855 return tempQueue; 856 } 857 858 /** 859 * Creates a <CODE>TemporaryTopic</CODE> object. Its lifetime will be that of the <CODE>Connection</CODE> unless 860 * it is deleted earlier. 861 * 862 * @return a temporary topic identity 863 * @throws JMSException if the session fails to create a temporary topic due to some internal error. 864 * @since 1.1 865 */ 866 public TemporaryTopic createTemporaryTopic() throws JMSException { 867 checkClosed(); 868 String tempTopicName = "TemporaryTopic-" 869 + ActiveMQDestination.createTemporaryName(this.connection.getInitializedClientID()); 870 tempTopicName += this.temporaryDestinationGenerator.generateId(); 871 ActiveMQTemporaryTopic tempTopic = new ActiveMQTemporaryTopic(tempTopicName); 872 tempTopic.setSessionCreatedBy(this); 873 this.connection.startTemporaryDestination(tempTopic); 874 return tempTopic; 875 } 876 877 /** 878 * Creates a <CODE>QueueReceiver</CODE> object to receive messages from the specified queue. 879 * 880 * @param queue the <CODE>Queue</CODE> to access 881 * @return @throws JMSException if the session fails to create a receiver due to some internal error. 882 * @throws JMSException 883 * @throws InvalidDestinationException if an invalid queue is specified. 884 */ 885 public QueueReceiver createReceiver(Queue queue) throws JMSException { 886 checkClosed(); 887 return new ActiveMQQueueReceiver(this, ActiveMQDestination.transformDestination(queue), "", this.connection 888 .getNextConsumerNumber(), this.connection.getPrefetchPolicy().getQueuePrefetch()); 889 } 890 891 /** 892 * Creates a <CODE>QueueReceiver</CODE> object to receive messages from the specified queue using a message 893 * selector. 894 * 895 * @param queue the <CODE>Queue</CODE> to access 896 * @param messageSelector only messages with properties matching the message selector expression are delivered. A 897 * value of null or an empty string indicates that there is no message selector for the message consumer. 898 * @return QueueReceiver 899 * @throws JMSException if the session fails to create a receiver due to some internal error. 900 * @throws InvalidDestinationException if an invalid queue is specified. 901 * @throws InvalidSelectorException if the message selector is invalid. 902 */ 903 public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException { 904 checkClosed(); 905 return new ActiveMQQueueReceiver(this, ActiveMQMessageTransformation.transformDestination(queue), 906 messageSelector, this.connection.getNextConsumerNumber(), this.connection.getPrefetchPolicy() 907 .getQueuePrefetch()); 908 } 909 910 /** 911 * Creates a <CODE>QueueSender</CODE> object to send messages to the specified queue. 912 * 913 * @param queue the <CODE>Queue</CODE> to access, or null if this is an unidentified producer 914 * @return QueueSender 915 * @throws JMSException if the session fails to create a sender due to some internal error. 916 * @throws InvalidDestinationException if an invalid queue is specified. 917 */ 918 public QueueSender createSender(Queue queue) throws JMSException { 919 checkClosed(); 920 return new ActiveMQQueueSender(this, ActiveMQMessageTransformation.transformDestination(queue)); 921 } 922 923 /** 924 * Creates a nondurable subscriber to the specified topic. <p/> 925 * <P> 926 * A client uses a <CODE>TopicSubscriber</CODE> object to receive messages that have been published to a topic. 927 * <p/> 928 * <P> 929 * Regular <CODE>TopicSubscriber</CODE> objects are not durable. They receive only messages that are published 930 * while they are active. <p/> 931 * <P> 932 * In some cases, a connection may both publish and subscribe to a topic. The subscriber <CODE>NoLocal</CODE> 933 * attribute allows a subscriber to inhibit the delivery of messages published by its own connection. The default 934 * value for this attribute is false. 935 * 936 * @param topic the <CODE>Topic</CODE> to subscribe to 937 * @return TopicSubscriber 938 * @throws JMSException if the session fails to create a subscriber due to some internal error. 939 * @throws InvalidDestinationException if an invalid topic is specified. 940 */ 941 public TopicSubscriber createSubscriber(Topic topic) throws JMSException { 942 checkClosed(); 943 return new ActiveMQTopicSubscriber(this, ActiveMQMessageTransformation.transformDestination(topic), null, null, 944 this.connection.getNextConsumerNumber(), this.connection.getPrefetchPolicy().getTopicPrefetch(), false, 945 false); 946 } 947 948 /** 949 * Creates a nondurable subscriber to the specified topic, using a message selector or specifying whether messages 950 * published by its own connection should be delivered to it. <p/> 951 * <P> 952 * A client uses a <CODE>TopicSubscriber</CODE> object to receive messages that have been published to a topic. 953 * <p/> 954 * <P> 955 * Regular <CODE>TopicSubscriber</CODE> objects are not durable. They receive only messages that are published 956 * while they are active. <p/> 957 * <P> 958 * Messages filtered out by a subscriber's message selector will never be delivered to the subscriber. From the 959 * subscriber's perspective, they do not exist. <p/> 960 * <P> 961 * In some cases, a connection may both publish and subscribe to a topic. The subscriber <CODE>NoLocal</CODE> 962 * attribute allows a subscriber to inhibit the delivery of messages published by its own connection. The default 963 * value for this attribute is false. 964 * 965 * @param topic the <CODE>Topic</CODE> to subscribe to 966 * @param messageSelector only messages with properties matching the message selector expression are delivered. A 967 * value of null or an empty string indicates that there is no message selector for the message consumer. 968 * @param noLocal if set, inhibits the delivery of messages published by its own connection 969 * @return TopicSubscriber 970 * @throws JMSException if the session fails to create a subscriber due to some internal error. 971 * @throws InvalidDestinationException if an invalid topic is specified. 972 * @throws InvalidSelectorException if the message selector is invalid. 973 */ 974 public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) throws JMSException { 975 checkClosed(); 976 return new ActiveMQTopicSubscriber(this, ActiveMQMessageTransformation.transformDestination(topic), null, 977 messageSelector, this.connection.getNextConsumerNumber(), this.connection.getPrefetchPolicy() 978 .getTopicPrefetch(), noLocal, false); 979 } 980 981 /** 982 * Creates a publisher for the specified topic. <p/> 983 * <P> 984 * A client uses a <CODE>TopicPublisher</CODE> object to publish messages on a topic. Each time a client creates a 985 * <CODE>TopicPublisher</CODE> on a topic, it defines a new sequence of messages that have no ordering 986 * relationship with the messages it has previously sent. 987 * 988 * @param topic the <CODE>Topic</CODE> to publish to, or null if this is an unidentified producer 989 * @return TopicPublisher 990 * @throws JMSException if the session fails to create a publisher due to some internal error. 991 * @throws InvalidDestinationException if an invalid topic is specified. 992 */ 993 public TopicPublisher createPublisher(Topic topic) throws JMSException { 994 checkClosed(); 995 return new ActiveMQTopicPublisher(this, ActiveMQMessageTransformation.transformDestination(topic)); 996 } 997 998 /** 999 * Unsubscribes a durable subscription that has been created by a client. 1000 * <P> 1001 * This method deletes the state being maintained on behalf of the subscriber by its provider. 1002 * <P> 1003 * It is erroneous for a client to delete a durable subscription while there is an active <CODE>MessageConsumer 1004 * </CODE> or <CODE>TopicSubscriber</CODE> for the subscription, or while a consumed message is part of a pending 1005 * transaction or has not been acknowledged in the session. 1006 * 1007 * @param name the name used to identify this subscription 1008 * @throws JMSException if the session fails to unsubscribe to the durable subscription due to some internal error. 1009 * @throws InvalidDestinationException if an invalid subscription name is specified. 1010 * @since 1.1 1011 */ 1012 public void unsubscribe(String name) throws JMSException { 1013 checkClosed(); 1014 DurableUnsubscribe ds = new DurableUnsubscribe(); 1015 ds.setClientId(this.connection.getClientID()); 1016 ds.setSubscriberName(name); 1017 this.connection.syncSendPacket(ds); 1018 } 1019 1020 /** 1021 * Tests to see if the Message Dispatcher is a target for this message 1022 * 1023 * @param message the message to test 1024 * @return true if the Message Dispatcher can dispatch the message 1025 */ 1026 public boolean isTarget(ActiveMQMessage message) { 1027 for (Iterator i = this.consumers.iterator();i.hasNext();) { 1028 ActiveMQMessageConsumer consumer = (ActiveMQMessageConsumer) i.next(); 1029 if (message.isConsumerTarget(consumer.getConsumerNumber())) { 1030 return true; 1031 } 1032 } 1033 return false; 1034 } 1035 1036 /** 1037 * Dispatch an ActiveMQMessage 1038 * 1039 * @param message 1040 */ 1041 public void dispatch(ActiveMQMessage message) { 1042 message = assembleMessage(message); 1043 if (message != null){ 1044 message.setMessageAcknowledge(this); 1045 messageExecutor.execute(message); 1046 } 1047 } 1048 1049 /** 1050 * Acknowledges all consumed messages of the session of this consumed message. 1051 * <P> 1052 * All consumed JMS messages support the <CODE>acknowledge</CODE> method for use when a client has specified that 1053 * its JMS session's consumed messages are to be explicitly acknowledged. By invoking <CODE>acknowledge</CODE> on 1054 * a consumed message, a client acknowledges all messages consumed by the session that the message was delivered to. 1055 * <P> 1056 * Calls to <CODE>acknowledge</CODE> are ignored for both transacted sessions and sessions specified to use 1057 * implicit acknowledgement modes. 1058 * <P> 1059 * A client may individually acknowledge each message as it is consumed, or it may choose to acknowledge messages as 1060 * an application-defined group (which is done by calling acknowledge on the last received message of the group, 1061 * thereby acknowledging all messages consumed by the session.) 1062 * <P> 1063 * Messages that have been received but not acknowledged may be redelivered. 1064 * @param caller - the message calling acknowledge on the session 1065 * 1066 * @throws JMSException if the JMS provider fails to acknowledge the messages due to some internal error. 1067 * @throws javax.jms.IllegalStateException if this method is called on a closed session. 1068 * @see javax.jms.Session#CLIENT_ACKNOWLEDGE 1069 */ 1070 public void acknowledge(ActiveMQMessage caller) throws JMSException { 1071 checkClosed(); 1072 /** 1073 * Find the caller and ensure it is marked as consumed 1074 * This is to ensure acknowledge called by a 1075 * MessageListener works correctly 1076 */ 1077 ActiveMQMessage msg = (ActiveMQMessage)deliveredMessages.get(caller); 1078 if (msg != null){ 1079 msg.setMessageConsumed(true); 1080 } 1081 1082 doAcknowledge(false); 1083 } 1084 1085 protected void doAcknowledge(boolean isClosing) throws JMSException { 1086 if (!closed) { 1087 if (this.acknowledgeMode == Session.CLIENT_ACKNOWLEDGE) { 1088 ActiveMQMessage msg = null; 1089 while((msg = (ActiveMQMessage)deliveredMessages.removeFirst())!=null){ 1090 boolean messageConsumed = isClosing ? false : msg.isMessageConsumed(); 1091 if (!msg.isTransientConsumed()){ 1092 sendMessageAck(msg, messageConsumed, false); 1093 }else { 1094 if (!messageConsumed){ 1095 connection.addToTransientConsumedRedeliverCache(msg); 1096 } 1097 } 1098 } 1099 deliveredMessages.clear(); 1100 } 1101 } 1102 } 1103 1104 protected void beforeMessageDelivered(ActiveMQMessage message) { 1105 if (message != null && !closed) { 1106 deliveredMessages.add(message); 1107 } 1108 } 1109 1110 protected void afterMessageDelivered(boolean sendAcknowledge, ActiveMQMessage message, boolean messageConsumed, 1111 boolean messageExpired, boolean beforeCalled) { 1112 if (message != null && !closed) { 1113 if ((isClientAcknowledge() && !messageExpired) || (isTransacted() && message.isTransientConsumed())) { 1114 message.setMessageConsumed(messageConsumed); 1115 if (!beforeCalled) { 1116 deliveredMessages.add(message); 1117 } 1118 } 1119 else { 1120 if (beforeCalled) { 1121 deliveredMessages.remove(message); 1122 } 1123 } 1124 //don't send acks for expired messages unless sendAcknowledge is set 1125 //the sendAcknowledge flag is set for all messages expect those destined 1126 //for transient Topic subscribers 1127 if (sendAcknowledge && !isClientAcknowledge()) { 1128 try { 1129 doStartTransaction(); 1130 sendMessageAck(message,messageConsumed,messageExpired); 1131 } 1132 catch (JMSException e) { 1133 log.warn("failed to notify Broker that message is delivered", e); 1134 } 1135 } 1136 } 1137 } 1138 1139 /** 1140 * remove a temporary destination 1141 * @param destination 1142 * @throws JMSException if active subscribers already exist 1143 */ 1144 public void removeTemporaryDestination(ActiveMQDestination destination) throws JMSException{ 1145 this.connection.stopTemporaryDestination(destination); 1146 } 1147 1148 private void sendMessageAck(ActiveMQMessage message, boolean messageConsumed, boolean messageExpired) 1149 throws JMSException { 1150 if (message.isMessagePart()) { 1151 ActiveMQMessage[] parts = (ActiveMQMessage[]) assemblies.remove(message.getParentMessageID()); 1152 if (parts != null) { 1153 for (int i = 0;i < parts.length;i++) { 1154 parts[i].setConsumerIdentifer(message.getConsumerIdentifer()); 1155 doSendMessageAck(parts[i], messageConsumed, messageExpired); 1156 } 1157 } 1158 else { 1159 JMSException jmsEx = new JMSException("Could not find parts for fragemented message: " + message); 1160 connection.onException(jmsEx); 1161 } 1162 } 1163 else { 1164 doSendMessageAck(message, messageConsumed, messageExpired); 1165 } 1166 } 1167 1168 private void doSendMessageAck(ActiveMQMessage message, boolean messageConsumed, boolean messageExpired) 1169 throws JMSException { 1170 if (message != null && !message.isAdvisory()) { 1171 MessageAck ack = new MessageAck(); 1172 ack.setConsumerId(message.getConsumerIdentifer()); 1173 ack.setTransactionId(transactionContext.getTransactionId()); 1174 ack.setExternalMessageId(message.isExternalMessageId()); 1175 ack.setMessageID(message.getJMSMessageID()); 1176 ack.setSequenceNumber(message.getSequenceNumber()); 1177 ack.setProducerKey(message.getProducerKey()); 1178 ack.setMessageRead(messageConsumed); 1179 ack.setDestination(message.getJMSActiveMQDestination()); 1180 ack.setPersistent(message.getJMSDeliveryMode() == DeliveryMode.PERSISTENT); 1181 ack.setExpired(messageExpired); 1182 ack.setSessionId(getSessionId()); 1183 this.connection.asyncSendPacket(ack); 1184 } 1185 } 1186 1187 /** 1188 * @param consumer 1189 * @throws JMSException 1190 */ 1191 protected void addConsumer(ActiveMQMessageConsumer consumer) throws JMSException { 1192 // ensure that the connection info is sent to the broker 1193 connection.sendConnectionInfoToBroker(); 1194 // lets add the stat 1195 if (consumer.isDurableSubscriber()) { 1196 stats.onCreateDurableSubscriber(); 1197 } 1198 ConsumerInfo info = createConsumerInfo(consumer); 1199 info.setStarted(true); 1200 //we add before notifying the server - as messages could 1201 //start to be dispatched before receipt from syncSend() 1202 //is returned 1203 this.consumers.add(consumer); 1204 if (started.get()){ 1205 connection.replayTransientConsumedRedeliveredMessages(this,consumer); 1206 } 1207 try { 1208 this.connection.syncSendPacket(info); 1209 } 1210 catch (JMSException jmsEx) { 1211 this.consumers.remove(consumer); 1212 throw jmsEx; 1213 } 1214 } 1215 1216 /** 1217 * @param consumer 1218 * @throws JMSException 1219 */ 1220 protected void removeConsumer(ActiveMQMessageConsumer consumer) throws JMSException { 1221 this.consumers.remove(consumer); 1222 // lets remove the stat 1223 if (consumer.isDurableSubscriber()) { 1224 stats.onRemoveDurableSubscriber(); 1225 } 1226 if (!closed) { 1227 ConsumerInfo info = createConsumerInfo(consumer); 1228 info.setStarted(false); 1229 this.connection.asyncSendPacket(info, false); 1230 } 1231 } 1232 1233 protected ConsumerInfo createConsumerInfo(ActiveMQMessageConsumer consumer) throws JMSException { 1234 ConsumerInfo info = new ConsumerInfo(); 1235 info.setConsumerId(consumer.consumerIdentifier); 1236 info.setClientId(connection.clientID); 1237 info.setSessionId(this.sessionId); 1238 info.setConsumerNo(consumer.consumerNumber); 1239 info.setPrefetchNumber(consumer.prefetchNumber); 1240 info.setDestination(consumer.destination); 1241 info.setNoLocal(consumer.noLocal); 1242 info.setBrowser(consumer.browser); 1243 info.setSelector(consumer.messageSelector); 1244 info.setStartTime(consumer.startTime); 1245 info.setConsumerName(consumer.consumerName); 1246 return info; 1247 } 1248 1249 /** 1250 * @param producer 1251 * @throws JMSException 1252 */ 1253 protected void addProducer(ActiveMQMessageProducer producer) throws JMSException { 1254 // ensure that the connection info is sent to the broker 1255 connection.sendConnectionInfoToBroker(); 1256 //start listening for advisories if the destination is temporary 1257 this.connection.startAdvisoryForTempDestination(producer.defaultDestination); 1258 producer.setProducerId(connection.handleIdGenerator.getNextShortSequence()); 1259 ProducerInfo info = createProducerInfo(producer); 1260 info.setStarted(true); 1261 this.connection.asyncSendPacket(info); 1262 this.producers.add(producer); 1263 } 1264 1265 /** 1266 * @param producer 1267 * @throws JMSException 1268 */ 1269 protected void removeProducer(ActiveMQMessageProducer producer) throws JMSException { 1270 this.producers.remove(producer); 1271 if (!closed) { 1272 this.connection.stopAdvisoryForTempDestination(producer.defaultDestination); 1273 ProducerInfo info = createProducerInfo(producer); 1274 info.setStarted(false); 1275 this.connection.asyncSendPacket(info, false); 1276 } 1277 } 1278 1279 protected ProducerInfo createProducerInfo(ActiveMQMessageProducer producer) throws JMSException { 1280 ProducerInfo info = new ProducerInfo(); 1281 info.setProducerId(producer.getProducerId()); 1282 info.setClientId(connection.clientID); 1283 info.setSessionId(this.sessionId); 1284 info.setDestination(producer.defaultDestination); 1285 info.setStartTime(producer.getStartTime()); 1286 return info; 1287 } 1288 1289 /** 1290 * Start this Session 1291 * @throws JMSException 1292 */ 1293 protected void start() throws JMSException { 1294 started.set(true); 1295 for (Iterator i = consumers.iterator(); i.hasNext();){ 1296 ActiveMQMessageConsumer consumer = (ActiveMQMessageConsumer)i.next(); 1297 connection.replayTransientConsumedRedeliveredMessages(this,consumer); 1298 } 1299 messageExecutor.start(); 1300 } 1301 1302 /** 1303 * Stop this Session 1304 */ 1305 protected void stop() { 1306 started.set(false); 1307 messageExecutor.stop(); 1308 } 1309 1310 /** 1311 * @return Returns the sessionId. 1312 */ 1313 protected short getSessionId() { 1314 return sessionId; 1315 } 1316 1317 /** 1318 * @param sessionId The sessionId to set. 1319 */ 1320 protected void setSessionId(short sessionId) { 1321 this.sessionId = sessionId; 1322 } 1323 1324 /** 1325 * @return Returns the startTime. 1326 */ 1327 protected long getStartTime() { 1328 return startTime; 1329 } 1330 1331 /** 1332 * @param startTime The startTime to set. 1333 */ 1334 protected void setStartTime(long startTime) { 1335 this.startTime = startTime; 1336 } 1337 1338 /** 1339 * send the message for dispatch by the broker 1340 * 1341 * @param producer 1342 * @param destination 1343 * @param message 1344 * @param deliveryMode 1345 * @param priority 1346 * @param timeToLive 1347 * @throws JMSException 1348 */ 1349 protected void send(ActiveMQMessageProducer producer, Destination destination, Message message, int deliveryMode, 1350 int priority, long timeToLive, boolean reuseMessageId) throws JMSException { 1351 checkClosed(); 1352 // ensure that the connection info is sent to the broker 1353 connection.sendConnectionInfoToBroker(); 1354 // tell the Broker we are about to start a new transaction 1355 doStartTransaction(); 1356 message.setJMSDestination(destination); 1357 message.setJMSDeliveryMode(deliveryMode); 1358 message.setJMSPriority(priority); 1359 long expiration = 0L; 1360 if (!producer.getDisableMessageTimestamp()) { 1361 long timeStamp = System.currentTimeMillis(); 1362 message.setJMSTimestamp(timeStamp); 1363 if (timeToLive > 0) { 1364 expiration = timeToLive + timeStamp; 1365 } 1366 } 1367 message.setJMSExpiration(expiration); 1368 String id = message.getJMSMessageID(); 1369 String producerKey = producer.getProducerMessageKey(); 1370 long sequenceNumber = producer.getIdGenerator().getNextSequence(); 1371 1372 if ((id == null || id.length() == 0) || !producer.getDisableMessageID() && !reuseMessageId) { 1373 message.setJMSMessageID(producerKey + sequenceNumber); 1374 } 1375 //transform to our own message format here 1376 ActiveMQMessage msg = ActiveMQMessageTransformation.transformMessage(message); 1377 if (connection.isCopyMessageOnSend()){ 1378 msg = msg.shallowCopy(); 1379 } 1380 //clear identity - incase forwared on 1381 msg.setJMSMessageIdentity(null); 1382 msg.setExternalMessageId(id != null); 1383 msg.setSequenceNumber(sequenceNumber); 1384 msg.setProducerKey(producerKey); 1385 msg.setTransactionId(transactionContext.getTransactionId()); 1386 msg.setJMSClientID(this.connection.clientID); 1387 msg.setMesssageHandle(producer.getProducerId()); 1388 //reset state as could be forwarded on 1389 msg.setJMSRedelivered(false); 1390 if (!connection.isInternalConnection()){ 1391 msg.clearBrokersVisited(); 1392 connection.validateDestination(msg.getJMSActiveMQDestination()); 1393 } 1394 1395 if (this.connection.isPrepareMessageBodyOnSend()){ 1396 msg.prepareMessageBody(); 1397 } 1398 //do message payload compression 1399 if (connection.isDoMessageCompression()){ 1400 try { 1401 msg.getBodyAsBytes(compression); 1402 } 1403 catch (IOException e) { 1404 JMSException jmsEx = new JMSException("Failed to compress message payload"); 1405 jmsEx.setLinkedException(e); 1406 throw jmsEx; 1407 } 1408 } 1409 boolean fragmentedMessage = connection.isDoMessageFragmentation(); 1410 if (fragmentedMessage && !msg.isMessagePart()){ 1411 try { 1412 fragmentedMessage = fragmentation.doFragmentation(msg.getBodyAsBytes()); 1413 if (fragmentedMessage){ 1414 ByteArray[] array = fragmentation.fragment(msg.getBodyAsBytes()); 1415 String parentMessageId = msg.getJMSMessageID(); 1416 for (int i = 0; i < array.length; i++){ 1417 ActiveMQMessage fragment = msg.shallowCopy(); 1418 fragment.setJMSMessageID(null); 1419 fragment.setMessagePart(true); 1420 fragment.setParentMessageID(parentMessageId); 1421 fragment.setNumberOfParts((short)array.length); 1422 fragment.setPartNumber((short)i); 1423 if (i != 0){ 1424 fragment.setSequenceNumber(producer.getIdGenerator().getNextSequence()); 1425 } 1426 fragment.setBodyAsBytes(array[i]); 1427 if (this.connection.isUseAsyncSend()) { 1428 this.connection.asyncSendPacket(fragment); 1429 } 1430 else { 1431 this.connection.syncSendPacket(fragment); 1432 } 1433 1434 } 1435 } 1436 }catch (IOException e) { 1437 JMSException jmsEx = new JMSException("Failed to fragment message payload"); 1438 jmsEx.setLinkedException(e); 1439 throw jmsEx; 1440 } 1441 } 1442 if (log.isDebugEnabled()) { 1443 log.debug("Sending message: " + msg); 1444 } 1445 1446 if (!fragmentedMessage){ 1447 if (this.connection.isUseAsyncSend() || acknowledgeMode == Session.DUPS_OK_ACKNOWLEDGE) { 1448 this.connection.asyncSendPacket(msg); 1449 } 1450 else { 1451 this.connection.syncSendPacket(msg); 1452 } 1453 } 1454 } 1455 1456 /** 1457 * Send TransactionInfo to indicate transaction has started 1458 * 1459 * @throws JMSException if some internal error occurs 1460 */ 1461 protected void doStartTransaction() throws JMSException { 1462 if (getTransacted() && !transactionContext.isInXATransaction()) { 1463 transactionContext.begin(); 1464 } 1465 } 1466 1467 protected void setSessionConsumerDispatchState(int value) throws JMSException { 1468 if (consumerDispatchState != ActiveMQSession.CONSUMER_DISPATCH_UNSET && value != consumerDispatchState) { 1469 String errorStr = "Cannot mix consumer dispatching on a session - already: "; 1470 if (value == ActiveMQSession.CONSUMER_DISPATCH_SYNC) { 1471 errorStr += "synchronous"; 1472 } 1473 else { 1474 errorStr += "asynchronous"; 1475 } 1476 throw new IllegalStateException(errorStr); 1477 } 1478 consumerDispatchState = value; 1479 } 1480 1481 protected void redeliverUnacknowledgedMessages() { 1482 redeliverUnacknowledgedMessages(false); 1483 } 1484 1485 protected void redeliverUnacknowledgedMessages(boolean onlyDeliverTransientConsumed) { 1486 messageExecutor.stop(); 1487 LinkedList replay = new LinkedList(); 1488 Object obj = null; 1489 while ((obj = deliveredMessages.removeFirst()) != null) { 1490 replay.add(obj); 1491 } 1492 1493 deliveredMessages.clear(); 1494 if (!replay.isEmpty()) { 1495 for (ListIterator i = replay.listIterator(replay.size());i.hasPrevious();) { 1496 ActiveMQMessage msg = (ActiveMQMessage) i.previous(); 1497 if (!onlyDeliverTransientConsumed || msg.isTransientConsumed()) { 1498 msg.setJMSRedelivered(true); 1499 msg.incrementDeliveryCount(); 1500 messageExecutor.executeFirst(msg); 1501 } 1502 } 1503 } 1504 replay.clear(); 1505 messageExecutor.start(); 1506 } 1507 1508 protected void clearMessagesInProgress() { 1509 messageExecutor.clearMessagesInProgress(); 1510 for (Iterator i = consumers.iterator();i.hasNext();) { 1511 ActiveMQMessageConsumer consumer = (ActiveMQMessageConsumer) i.next(); 1512 consumer.clearMessagesInProgress(); 1513 } 1514 } 1515 1516 public boolean hasUncomsumedMessages() { 1517 return messageExecutor.hasUncomsumedMessages(); 1518 } 1519 1520 public boolean isTransacted() { 1521 return this.acknowledgeMode == Session.SESSION_TRANSACTED; 1522 } 1523 1524 protected boolean isClientAcknowledge() { 1525 return this.acknowledgeMode == Session.CLIENT_ACKNOWLEDGE; 1526 } 1527 1528 /** 1529 * @return Returns the internalSession. 1530 */ 1531 public boolean isInternalSession() { 1532 return internalSession; 1533 } 1534 /** 1535 * @param internalSession The internalSession to set. 1536 */ 1537 public void setInternalSession(boolean internalSession) { 1538 this.internalSession = internalSession; 1539 } 1540 1541 1542 private final ActiveMQMessage assembleMessage(ActiveMQMessage message) { 1543 ActiveMQMessage result = message; 1544 if (message != null && !connection.isInternalConnection() && message.isMessagePart()) { 1545 if (message.getNumberOfParts() == 1) { 1546 //passed though from another session - i.e. 1547 //a network or remote connection and now assembled 1548 message.resetMessagePart(); 1549 result = message; 1550 } 1551 else { 1552 result = null; 1553 String parentId = message.getParentMessageID(); 1554 ActiveMQMessage[] array = (ActiveMQMessage[]) assemblies.get(parentId); 1555 if (array == null) { 1556 array = new ActiveMQMessage[message.getNumberOfParts()]; 1557 assemblies.put(parentId, array); 1558 } 1559 array[message.getPartNumber()] = message; 1560 boolean complete = true; 1561 for (int i = 0;i < array.length;i++) { 1562 complete &= array[i] != null; 1563 } 1564 if (complete) { 1565 result = array[0]; 1566 ByteArray[] bas = new ByteArray[array.length]; 1567 try { 1568 for (int i = 0;i < bas.length;i++) { 1569 bas[i] = array[i].getBodyAsBytes(); 1570 if (i >= 1){ 1571 array[i].clearBody(); 1572 } 1573 } 1574 ByteArray ba = fragmentation.assemble(bas); 1575 result.setBodyAsBytes(ba); 1576 } 1577 catch (IOException ioe) { 1578 JMSException jmsEx = new JMSException("Failed to assemble fragment message: " + parentId); 1579 jmsEx.setLinkedException(ioe); 1580 this.connection.onException(jmsEx); 1581 }catch(JMSException jmsEx){ 1582 this.connection.onException(jmsEx); 1583 } 1584 } 1585 } 1586 } 1587 return result; 1588 } 1589 1590 public DeliveryListener getDeliveryListener() { 1591 return deliveryListener; 1592 } 1593 1594 1595 public void setDeliveryListener(DeliveryListener deliveryListener) { 1596 this.deliveryListener = deliveryListener; 1597 } 1598 1599 }