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 2000-2003 (C) Exoffice Technologies Inc. All Rights Reserved.
42 *
43 * $Id: JmsServerSession.java,v 1.89 2003/08/17 01:32:26 tanderson Exp $
44 *
45 * Date Author Changes
46 * 04/07/2000 jima Created
47 * 04/25/2000 jima Changes to the interface
48 */
49 package org.exolab.jms.server;
50
51 import java.util.HashMap;
52 import java.util.Iterator;
53 import java.util.Vector;
54
55 import javax.jms.DeliveryMode;
56 import javax.jms.Destination;
57 import javax.jms.InvalidDestinationException;
58 import javax.jms.InvalidSelectorException;
59 import javax.jms.JMSException;
60 import javax.jms.Message;
61 import javax.jms.MessageConsumer;
62 import javax.jms.Session;
63 import javax.transaction.TransactionManager;
64 import javax.transaction.xa.XAException;
65 import javax.transaction.xa.XAResource;
66 import javax.transaction.xa.Xid;
67
68 import org.apache.commons.logging.Log;
69 import org.apache.commons.logging.LogFactory;
70
71 import org.exolab.jms.JMSErrorCodes;
72 import org.exolab.jms.client.JmsMessageListener;
73 import org.exolab.jms.client.JmsQueue;
74 import org.exolab.jms.client.JmsTopic;
75 import org.exolab.jms.message.MessageHandle;
76 import org.exolab.jms.message.MessageId;
77 import org.exolab.jms.message.MessageImpl;
78 import org.exolab.jms.messagemgr.ConsumerEndpoint;
79 import org.exolab.jms.messagemgr.ConsumerManager;
80 import org.exolab.jms.messagemgr.DestinationManager;
81 import org.exolab.jms.messagemgr.InternalMessageListener;
82 import org.exolab.jms.messagemgr.MessageMgr;
83 import org.exolab.jms.messagemgr.QueueBrowserEndpoint;
84 import org.exolab.jms.messagemgr.ResourceManager;
85 import org.exolab.jms.messagemgr.ResourceManagerException;
86
87
88 /***
89 * A session represents a server side endpoint to the JMSServer. A client can
90 * create producers, consumers and destinations through the session in addi-
91 * tion to other functions. A session has a unique identifer which is a comb-
92 * ination of clientId-connectionId-sessionId.
93 * <p>
94 * A session represents a single-threaded context which implies that it cannot
95 * be used with more than one thread concurrently. Threads registered with this
96 * session are synchronized.
97 * <p>
98 * Finally, instances of this object can only be created by classes within the
99 * same package.
100 *
101 * @version $Revision: 1.89 $ $Date: 2003/08/17 01:32:26 $
102 * @author <a href="mailto:jima@exoffice.com">Jim Alateras</a>
103 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
104 * @see JmsServerConnection
105 */
106 public class JmsServerSession
107 implements InternalMessageListener, XAResource {
108
109 /***
110 * The client identifies the owner of the session.
111 */
112 private String _clientId = null;
113
114 /***
115 * The session identifier uniquely distinguishes this session from any
116 * other session. No two sessions can have the same session identifier.
117 * This is allocated by the connection that created this session
118 */
119 private String _sessionId = null;
120
121 /***
122 * Back pointer to the connection that created this session. This
123 * is set during object creation time
124 */
125 private JmsServerConnection _connection = null;
126
127 /***
128 * Maintain a list of consumers along with their associated destinations
129 * that have been created through this session
130 */
131 private HashMap _consumers = new HashMap();
132
133 /***
134 * The message listener is the reference to a remote client that wull
135 * receive the messages
136 */
137 private JmsMessageListener _listener = null;
138
139 /***
140 * This is the acknowledgement mode for the session
141 */
142 private int _ackMode = Session.AUTO_ACKNOWLEDGE;
143
144 /***
145 * Indicates whether the session is transactional
146 */
147 private boolean _transacted = false;
148
149 /***
150 * Holds the current xid that this session is associated with. A session
151 * can olny be associated with one xid at any one time.
152 */
153 private Xid _xid = null;
154
155 /***
156 * Indicates if the underlying connection of this session has been stopped
157 */
158 private boolean _stopped = true;
159
160 /***
161 * Indicated that the session has been closed
162 */
163 private boolean _closed = false;
164
165 /***
166 * Holds the number of messages published by this session
167 */
168 private long _publishCount;
169
170 /***
171 * Holds the number of messages consumed by this session
172 */
173 private long _consumeCount;
174
175 /***
176 * Caches all sent messages
177 */
178 private SentMessageCache _sentMessageCache;
179
180 /***
181 * The logger
182 */
183 private static final Log _log = LogFactory.getLog(JmsServerSession.class);
184
185
186 /***
187 * Create an instance of the session using the specified client identifier.
188 * also pass a back pointer to the connection that created this session.
189 * By default, the session is stopped. The start() method must be invoked
190 * before messages will be dispatched to consumers.
191 *
192 * @param connection connection that created this session
193 * @param id client identity
194 * @param int acknowledgement mode for the session
195 * @param transacted true if the session is transactional
196 */
197 JmsServerSession(JmsServerConnection connection, String id, int ackmode,
198 boolean transacted) {
199 _connection = connection;
200 _clientId = id;
201 _ackMode = ackmode;
202 _transacted = transacted;
203 _stopped = true;
204 _sentMessageCache = new SentMessageCache(this);
205 }
206
207 /***
208 * Set the session id for this object. The Connection is responsible for
209 * setting the session id once it has been created
210 *
211 * @param id session id
212 */
213 void setSessionId(String id) {
214 _sessionId = id;
215 }
216
217 /***
218 * Return a reference to the client id
219 *
220 * @return String client id
221 */
222 public String getClientId() {
223 return _clientId;
224 }
225
226 /***
227 * Return a reference to the session id
228 *
229 * @return String session id
230 */
231 public String getSessionId() {
232 return _sessionId;
233 }
234
235 /***
236 * Start the message delivery for the session. If session delivery has
237 * already started then treat the operation as an no-op
238 */
239 public void start() {
240 if (_log.isDebugEnabled()) {
241 _log.debug("start() [sessionId=" + _sessionId + "]");
242 }
243
244 if (_stopped) {
245 pause(false);
246 _stopped = false;
247 }
248 }
249
250 /***
251 * Stop message delivery for the session
252 */
253 public void stop() {
254 if (_log.isDebugEnabled()) {
255 _log.debug("stop() [sessionId=" + _sessionId + "]");
256 }
257 if (!_stopped) {
258 pause(true);
259 _stopped = true;
260 }
261 }
262
263 /***
264 * Close and release any resource allocated to this session.
265 * This method may be called by multiple threads.
266 *
267 * @throws JMSException if the session cannot be closed
268 */
269 public void close() throws JMSException {
270 boolean closed = false;
271
272 synchronized (this) {
273 closed = _closed;
274 if (!closed) {
275 _closed = true;
276 }
277 }
278
279 if (!closed) {
280 if (_log.isDebugEnabled()) {
281 _log.debug("close() [sessionId=" + _sessionId + "]");
282 }
283
284 // reset the listener
285 setMessageListener(null);
286
287 // iterate over the list of consumers and deregister the
288 // associated endpoints and then remove all the entries
289 Iterator consumers = _consumers.values().iterator();
290 while (consumers.hasNext()) {
291 ConsumerEndpoint consumer =
292 (ConsumerEndpoint) consumers.next();
293 ConsumerManager.instance().deleteConsumerEndpoint(consumer);
294 }
295
296 // clear the unacked message cache
297 _sentMessageCache.clear();
298
299 // clear the consumers
300 _consumers.clear();
301
302 // de-register the session from the connection
303 _connection.deleteSession(this);
304 } else {
305 if (_log.isDebugEnabled()) {
306 _log.debug("close() [sessionId=" + _sessionId +
307 "]: session already closed");
308 }
309 }
310 }
311
312 /***
313 * Acknowledge that the message with the following id has been processed
314 *
315 * @param clientId the clientId that sent the message to the client
316 * @param id the message to ack
317 * @throws JMSException if message cannot be acknowledged
318 */
319 public void acknowledgeMessage(long clientId, String id)
320 throws JMSException {
321 _sentMessageCache.acknowledgeMessage(new MessageId(id), clientId);
322 }
323
324 /***
325 * Send the specified message to the server
326 *
327 * @param message the message to send
328 * @throws JMSException if the message can't be sent
329 */
330 public void sendMessage(Message message) throws JMSException {
331 if (message == null) {
332 throw new JMSException("Message is null");
333 }
334
335 try {
336 // check the delivery mode of the message
337 checkDeliveryMode((MessageImpl) message);
338
339 // set the connection identity and then let the message meanager
340 // process it
341 ((MessageImpl) message).setConnectionId(_connection.hashCode());
342
343 // if there is a global transaction currently in process then
344 // we must send the message to the resource manager, otherwise
345 // send it directly to the message manager
346 if (_xid != null) {
347 ResourceManager.instance().logPublishedMessage(_xid,
348 (MessageImpl) message);
349 } else {
350 MessageMgr.instance().add((MessageImpl) message);
351 _publishCount++;
352 }
353 } catch (JMSException exception) {
354 _log.error("Failed to process message", exception);
355 throw exception;
356 } catch (OutOfMemoryError exception) {
357 String msg =
358 "Failed to process message due to out-of-memory error";
359 _log.error(msg, exception);
360 throw new JMSException(msg);
361 } catch (Exception exception) {
362 String msg = "Failed to process message";
363 _log.error(msg, exception);
364 throw new JMSException(msg);
365 }
366 }
367
368 /***
369 * Send the specified messages to the server.
370 *
371 * @param messages the messages to send
372 * @throws JMSException if the messages can't be sent
373 */
374 public void sendMessages(Vector messages) throws JMSException {
375 if (messages == null) {
376 throw new JMSException("No messages to send");
377 }
378
379 MessageImpl message = null;
380 while ((messages.size() > 0) &&
381 ((message = (MessageImpl) messages.remove(0)) != null)) {
382 try {
383 // check the delivery mode of the message
384 checkDeliveryMode((MessageImpl) message);
385
386 // set the connection identity and then let the message manager
387 // process it
388 message.setConnectionId(_connection.hashCode());
389
390 // if there is a global transaction in progress then send the
391 // message to the resource manager, otherwise send it to the
392 // message manager
393 if (_xid != null) {
394 ResourceManager.instance().logPublishedMessage(_xid,
395 message);
396 } else {
397 MessageMgr.instance().add(message);
398 _publishCount++;
399 }
400 } catch (JMSException exception) {
401 _log.error("Failed to process message", exception);
402 throw exception;
403 } catch (OutOfMemoryError exception) {
404 String msg =
405 "Failed to process message due to out-of-memory error";
406 _log.error(msg, exception);
407 throw new JMSException(msg);
408 } catch (Exception exception) {
409 String msg = "Failed to process messages";
410 _log.error(msg, exception);
411 throw new JMSException(msg);
412 }
413 }
414 }
415
416 /***
417 * Return the next message for the specified client. The <code>wait</code>
418 * parameter indicates how long many milliseconds to wait for a message
419 * before returning. If <code>wait</code> is 0 then do not wait at all. If
420 * <code>wait</code> is -1 then wait indefinitely for the next message
421 *
422 * @param clientId the client identity
423 * @param wait number of ms to wait
424 * @return the next message or <code>null</code> if there is no message
425 * @throws JMSException if the message can't be received
426 */
427 public Message receiveMessage(long clientId, long wait)
428 throws JMSException {
429 MessageImpl message = null;
430 ConsumerEndpoint consumer = getConsumerEndpoint(clientId);
431 if (consumer == null) {
432 throw new JMSException(
433 "Can't receive message: no consumer registered with "
434 + "identifier " + clientId + " on session " + _sessionId);
435 }
436
437 // we have a valid consumer, now we need retrieve a handle.
438 MessageHandle handle = consumer.receiveMessage(wait);
439 if (handle != null) {
440 // if we get a non-null handle the retrieve the message,
441 // clone
442 MessageImpl orig = handle.getMessage();
443 if (orig != null) {
444 try {
445 message = (MessageImpl) orig.clone();
446 message.setJMSRedelivered(handle.getDelivered());
447 message.setClientId(handle.getClientId());
448 _consumeCount++;
449 } catch (Exception exception) {
450 _log.error(exception);
451 message = null;
452 }
453 }
454 }
455
456 // if we have a non-null message then add it to the sent message
457 // cache. Additionally, if we are part of a global transaction then
458 // we must also sent it to the ResourceManager for recovery.
459 if (handle != null) {
460 _sentMessageCache.process(handle);
461
462 if (_xid != null) {
463 try {
464 ResourceManager.instance().logReceivedMessage(
465 _xid, consumer.getId(), handle);
466 } catch (Exception exception) {
467 _log.error(exception);
468 JMSException jms_exception = new JMSException(
469 "Error in receiveMessage");
470 jms_exception.setLinkedException(exception);
471 throw jms_exception;
472 }
473 }
474 }
475
476 return message;
477 }
478
479 /***
480 * Return up to count messages from the endpoint with the specified
481 * client identity. The client must be a QueueBrowser.
482 *
483 * @param clientId the client identity
484 * @param count the maximum number of messages retrieve
485 * @return Message the next message or null
486 * @throws JMSException if the endpoint does not exist, or is not a
487 * {@link QueueBrowserEndpoint}
488 */
489 public Vector receiveMessages(long clientId, int count)
490 throws JMSException {
491
492 ConsumerEndpoint consumer = getConsumerEndpoint(clientId);
493 if (consumer == null) {
494 throw new JMSException(
495 "Can't receive messages: no consumer registered with "
496 + "identifier " + clientId + " on session " + _sessionId);
497 }
498
499 if (!(consumer instanceof QueueBrowserEndpoint)) {
500 throw new JMSException(
501 "Can't receive messages: consumer with identifier "
502 + "identifier " + clientId + " is not a QueueBrowser");
503 }
504
505 // we have a valid consumer, now we need retrieve upto count
506 // handles
507 Vector handles = ((QueueBrowserEndpoint) consumer).receiveMessages(
508 count);
509 Vector messages = new Vector();
510 if (handles.size() > 0) {
511
512 // process the handles
513 int max = handles.size();
514 for (int index = 0; index < max; index++) {
515 MessageHandle handle = (MessageHandle) handles.elementAt(index);
516 MessageImpl orig = handle.getMessage();
517 MessageImpl message = null;
518 if (orig != null) {
519 try {
520 message = (MessageImpl) orig.clone();
521 message.setJMSRedelivered(handle.getDelivered());
522 message.setClientId(handle.getClientId());
523 messages.addElement(message);
524 } catch (Exception exception) {
525 _log.error(exception);
526 message = null;
527 }
528 }
529 }
530 }
531
532 return messages;
533 }
534
535 /***
536 * Create an amdinistered queue, through the message manager admin
537 * interface.
538 *
539 * @param queue administered queue to create
540 * @throws JMSException if the queue can't be created
541 */
542 public void createQueue(JmsQueue queue) throws JMSException {
543 if (!DestinationManager.instance().createAdministeredDestination(
544 queue)) {
545 throw new JMSException("Failed to create queue: " +
546 queue.getName());
547 }
548 }
549
550 /***
551 * Create an administered topic, through the message manager admin
552 * interface.
553 *
554 * @param topic administered topic to create
555 * @throws JMSException if the topic can't be created
556 */
557 public void createTopic(JmsTopic topic) throws JMSException {
558 if (!DestinationManager.instance().createAdministeredDestination(
559 topic)) {
560 throw new JMSException("Failed to create topic: " +
561 topic.getName());
562 }
563 }
564
565 /***
566 * Create a receiver endpoint for this session. A receiver is a message
567 * consumer specific to the queue message model. The receiver is
568 * associated with a queue.
569 * <p>
570 * You cannot create more than one receiver for the same destination
571 *
572 * @param queue the receiver destination
573 * @param consumerId the client session allocated identifier of the
574 * consumer
575 * @param selector the selector to filter messages. May be
576 * <code>null</code>
577 * @throws JMSException if the receiver can't be created
578 */
579 public void createReceiver(JmsQueue queue, long clientId, String selector)
580 throws JMSException {
581 if (_log.isDebugEnabled()) {
582 _log.debug("createReceiver(queue=" + queue + ", clientId="
583 + clientId + ", selector=" + selector
584 + ") [sessionId=" + _sessionId + "]");
585 }
586
587 if (queue == null) {
588 throw new JMSException("Cannot create receiver for null queue");
589 }
590
591 // Retrieve the destination from the destination manager and use
592 // it to create the consumer
593 ConsumerEndpoint consumer =
594 ConsumerManager.instance().createConsumerEndpoint(this,
595 clientId, queue, selector);
596 consumer.setAckMode(_ackMode);
597 consumer.setConnectionId(_connection.hashCode());
598 consumer.setTransacted(_transacted);
599 // if the session is stopped then we should also stop the
600 // consumer, so that it doesn't deliver messages and then
601 // cache it for future reference.
602 consumer.setStopped(_stopped);
603 _consumers.put(Long.toString(clientId), consumer);
604 }
605
606 /***
607 * This is a no-op
608 */
609 public void createSender(JmsQueue queue) throws JMSException {
610 }
611
612 /***
613 * Create a queue browser for this session. This allows clients to browse
614 * a queue without removing any messages.
615 * <p>
616 *
617 * You cannot create more than one queue browser for the same queue
618 * in a single session.
619 *
620 * @param queue queue to browse
621 * @param clientId the client identity
622 * @param selector message selector. This may be null
623 * @throws JMSException.
624 */
625 public void createBrowser(JmsQueue queue, long clientId, String selector)
626 throws JMSException {
627 if (_log.isDebugEnabled()) {
628 _log.debug("createBrowser(queue=" + queue + ", clientId="
629 + clientId + ", selector=" + selector
630 + ") [sessionId=" + _sessionId + "]");
631 }
632
633 // check to see that we have a valid queue
634 if (queue == null) {
635 throw new JMSException("Cannot create browser for null queue");
636 }
637
638 // Retrieve the destination from the destination manager and use
639 // it to create the consumer
640 ConsumerEndpoint consumer =
641 ConsumerManager.instance().createQueueBrowserEndpoint(this,
642 clientId, queue, selector);
643
644 // if the session is stopped then we should also stop the
645 // consumer, so that it doesn't deliver messages and then
646 // cache it for future reference.
647 consumer.setStopped(_stopped);
648 _consumers.put(Long.toString(clientId), consumer);
649 }
650
651 /***
652 * Delete the receiver with the specified identity and clean up all
653 * associated resources.
654 *
655 * @param clientId the identity of the receiver
656 * @throws JMSException if the receiver cannot be deleted
657 */
658 public void deleteReceiver(long clientId) throws JMSException {
659 if (_log.isDebugEnabled()) {
660 _log.debug("deleteReceiver(clientId=" + clientId + ") [sessionId="
661 + _sessionId + "]");
662 }
663
664 ConsumerEndpoint consumer =
665 (ConsumerEndpoint) _consumers.remove(Long.toString(clientId));
666 if (consumer == null) {
667 throw new JMSException("No receiver with id " + clientId);
668 }
669
670 // destroy the consumer endpoint
671 ConsumerManager.instance().deleteConsumerEndpoint(consumer);
672 }
673
674 /***
675 * Delete the sender associated with the specified queue from the session
676 * If the corresponding sender does not exist or it cannot delete it then
677 * throw the JMSException.
678 *
679 * @param clientId the identity of the sender
680 * @throws JMSException if the sender cannot be deleted
681 */
682 public void deleteSender(long clientId) throws JMSException {
683 // no-op
684 }
685
686 /***
687 * Delete the queue browser associated with the specified queue from
688 * the session.
689 *
690 * @param clientId the identity of the browser
691 * @throws JMSException if the browser cannot be deleted
692 */
693 public void deleteBrowser(long clientId) throws JMSException {
694 ConsumerEndpoint consumer =
695 (ConsumerEndpoint) _consumers.remove(Long.toString(clientId));
696 if (consumer == null) {
697 throw new JMSException("No browser with id " + clientId);
698 }
699 // destroy the consumer endpoint
700 ConsumerManager.instance().deleteConsumerEndpoint(consumer);
701 }
702
703 /***
704 * Create a subscriber endpoint for this session. A subscriber is a message
705 * consumer specific to the topic message model. The subscriber is
706 * associated with a topic. Register the consumer with the message
707 * manager so that a queue can be set up for it. Finally add the consumer
708 * to the list of consumers managed by this session.
709 * <p>
710 * Note that the message manager manages consumers for all server sessions
711 * <p>
712 * You cannot create more than one subscriber for the same destination.
713 * Currently we don't check this
714 *
715 * @param topic subscriber destination
716 * @param name consumer name
717 * @param clientId the client session allocated
718 * identifier of the consumer
719 * @param selector the selector to filter messages.
720 * This may be null.
721 * @param noLocal true to inhibit consumption of messages
722 * published on this connection.
723 * @throws JMSException.
724 */
725 public void createSubscriber(JmsTopic topic, String name, long clientId,
726 String selector, boolean noLocal)
727 throws JMSException {
728
729 if (_log.isDebugEnabled()) {
730 _log.debug("createSubscriber(topic=" + topic + ", name=" + name
731 + ", clientId=" + clientId + ", selector=" + selector
732 + ", noLocal=" + noLocal + ") [sessionId="
733 + _sessionId + "]");
734 }
735
736 // check to ensure that the methods preconditions have been met
737 if (topic == null) {
738 throw new JMSException("Cannot create subscriber for null topic");
739 }
740
741 // Retrieve the destination from the destination manager and
742 // use it to create the consumer through the consumer manager.
743 ConsumerEndpoint consumer = null;
744
745 if (name != null) {
746 if (name.length() > 0) {
747
748 // for a durable consumer the topic must be
749 ConsumerManager manager = ConsumerManager.instance();
750
751 if (manager.durableConsumerExists(name)) {
752 // if the durable consumer exists then validate that
753 // it was the specified topic that it was registered
754 // under. If it is not registered for the topic then
755 // we must delete the existing entry and recreate it
756 // against the new topic
757 if (!manager.validSubscription(topic.getName(), name)) {
758 unsubscribe(name);
759 manager.createDurableConsumer(topic, name);
760 }
761 } else {
762 // if the durable consumer does not exist then create
763 // it
764 manager.createDurableConsumer(topic, name);
765 }
766
767 // if a durable subscriber with the specified name is
768 // alreayd active then this method will throw an
769 // exception.
770 // attempt to create a durable consuinmer
771 consumer = manager.createDurableConsumerEndpoint(this,
772 topic, name, clientId, selector);
773 consumer.setConnectionId(_connection.hashCode());
774 consumer.setTransacted(_transacted);
775 consumer.setAckMode(_ackMode);
776 consumer.setNoLocal(noLocal);
777 } else {
778 throw new JMSException("Name in createSubscriber was null");
779 }
780 } else {
781 // Create a non-durable subscriber for the specified destination
782 // and using the required selector.
783 consumer = ConsumerManager.instance().createConsumerEndpoint(this,
784 clientId, topic, selector);
785 consumer.setConnectionId(_connection.hashCode());
786 consumer.setTransacted(_transacted);
787 consumer.setAckMode(_ackMode);
788 consumer.setNoLocal(noLocal);
789 }
790
791 // once the consumer has been created then set it to the same state
792 // as the session and add it to the list on consumers to manage
793 consumer.setStopped(_stopped);
794 _consumers.put(Long.toString(clientId), consumer);
795 }
796
797 /***
798 * This should be a no operation. Do we need to maintain state information
799 * that a publisher has been created.
800 *
801 * @param topic receiver destination
802 * @throws JMSException.
803 */
804 public void createPublisher(JmsTopic topic)
805 throws JMSException {
806 }
807
808 /***
809 * This function deletes a persistent subsrciber and its history from
810 * the database. It his subscriber re-connects it get everything available
811 * for the queue topic. If the subscriber is reliable, this is a no op.
812 * See UnregisterSubscriber below for just unregistering the subscriber
813 * but leaving its persistent data in the db.
814 * <p>
815 * The data contains information necessary to delete the subscriber
816 *
817 * @param clientId the client identity
818 * @throws JMSException.
819 */
820 public void deleteSubscriber(long clientId) throws JMSException {
821 if (_log.isDebugEnabled()) {
822 _log.debug("deleteSubscriber(clientId=" + clientId
823 + ") [sessionId=" + _sessionId + "]");
824 }
825
826 // retrieve the endpoint corresponding to the client id and
827 // then acknowledge the messsage
828 ConsumerEndpoint consumer =
829 (ConsumerEndpoint) _consumers.remove(Long.toString(clientId));
830 if (consumer == null) {
831 throw new JMSException("Failed to close consumer with id " +
832 "[" + hashCode() + ":" + clientId + "]");
833 }
834
835 ConsumerManager.instance().deleteConsumerEndpoint(consumer);
836 }
837
838 /***
839 * Delete the publisher associated with the specified topic from the
840 * session. If the corresponding publisher does not exist or it cannot
841 * delete it then throw the JMSException.
842 *
843 * @param topic sender destination
844 * @throws JMSException.
845 */
846 public void deletePublisher(JmsTopic topic) throws JMSException {
847 // no-op
848 }
849
850 /***
851 * Unsubscribe a durable subscription. This will delete the state of
852 * the durable subscriber maintained by the server. A durable subscriber
853 * is uniquely identifiable and the same subscriber cannot be associated
854 * with more than topic.
855 *
856 * @param name the name used to uniquely identify the
857 * subscription
858 * @throws JMSException if the subscription cannot be removed
859 * or any other problem.
860 */
861 public void unsubscribe(String name) throws JMSException {
862 if (_log.isDebugEnabled()) {
863 _log.debug("unsubscribe(name=" + name + ") [sessionId="
864 + _sessionId + "]");
865 }
866
867 ConsumerManager manager = ConsumerManager.instance();
868
869 // check that the durable consumer actually exists. If it doesn't then
870 // throw an exception
871 if (!manager.durableConsumerExists(name)) {
872 throw new InvalidDestinationException(name +
873 " is not a durable subscriber name");
874 }
875
876 // check that the durable consumer is not active before removing it. If
877 // it is then throw an exception
878 if (!manager.isDurableConsumerActive(name)) {
879 manager.removeDurableConsumer(name);
880 } else {
881 throw new JMSException("Failed to unsubscribe subscriber "
882 + name + " since is still active");
883 }
884 }
885
886 /***
887 * Stop message delivery to this session. If there are any problems
888 * completing the request then throw the JMSException exception
889 *
890 * @throws JMSException
891 */
892 public void stopMessageDelivery() throws JMSException {
893 stop();
894 }
895
896 /***
897 * Start message delivery to this session. If there are any problems
898 * completing this request then throw the JMSException exception
899 *
900 * @throws JMSException
901 */
902 public void startMessageDelivery() throws JMSException {
903 start();
904 }
905
906 /***
907 * Check if the specified message handle is in the session's list
908 * of unacked messages
909 *
910 * @param handle - the handle to query
911 * @return boolean - true if it is and false otherwise
912 */
913 public boolean containsUnackedHandle(MessageHandle handle) {
914 return _sentMessageCache.handleInCache(handle);
915 }
916
917 // implementation of InternalMessageListener.onMessage
918 public void onMessage(MessageHandle handle, boolean ignore)
919 throws Exception {
920
921 if ((handle != null) &&
922 (_listener != null)) {
923 MessageImpl message = handle.getMessage();
924 MessageImpl m = null;
925 if (message != null) {
926 m = (MessageImpl) message.clone();
927 m.setClientId(handle.getClientId());
928 m.setJMSRedelivered(handle.getDelivered());
929
930 // if we are acking the message and the session is
931 // transacted and the acknowledge mode is
932 // CLIENT_ACKNOWLEDGE then send it to the cache before
933 // we send it to the listener. This will enable clients
934 // to ack the message while in the onMessage method
935 if ((_transacted) ||
936 (_ackMode == Session.CLIENT_ACKNOWLEDGE)) {
937 _sentMessageCache.process(handle);
938 }
939
940 try {
941 // send the message to the listener.
942 _listener.onMessage(m);
943
944 // if the session is not transacted or the acknowledge mode
945 // is not CLIENT_ACKNOWLEDGE then process it through the
946 // sent message cache now.
947 if ((!_transacted) &&
948 (_ackMode != Session.CLIENT_ACKNOWLEDGE)) {
949 _sentMessageCache.process(handle);
950 }
951 } catch (ClientDisconnectionException exception) {
952 // close all resources and rethrow it
953 close();
954 throw exception;
955 }
956 } else {
957 throw new JMSException(
958 "Could not get message for handle " + handle,
959 JMSErrorCodes.FailedToResolveHandle);
960 }
961 }
962 }
963
964 // implementation of InternalMessageListener.onMessage
965 public void onMessages(Vector handles) throws Exception {
966 _log.error("Illegal to call onMessage");
967 Thread.currentThread().dumpStack();
968 }
969
970 // implementation of InternalMessageListener.onMessageAvailable
971 public void onMessageAvailable(long clientId) throws Exception {
972 _listener.onMessageAvailable(clientId);
973 }
974
975 /***
976 * This will send a null message down the connection to the client to
977 * test whether the client endpoint is alive.
978 *
979 * @return <code>true</code> if it is active, otherwise <code>false</code>
980 */
981 public boolean isClientEndpointActive() {
982 boolean active = true;
983 if (_listener != null) {
984 try {
985 // send the message to the listener.
986 _listener.onMessage(null);
987 } catch (ClientDisconnectionException exception) {
988 _log.info("Failed to verify that session " + _sessionId
989 + " is active.");
990 active = false;
991 // ignore the exception
992 }
993 }
994
995 return active;
996 }
997
998 /***
999 * Set a message listener for the session. This is the channel used
1000 * to asynchronously deliver messages to consumers created on this
1001 * session.
1002 *
1003 * @param listener the message listener
1004 */
1005 public void setMessageListener(JmsMessageListener listener) {
1006 _listener = listener;
1007 }
1008
1009 /***
1010 * Check whether to enable asynchronous message delivery for a particular
1011 * consumer
1012 *
1013 * @param clientId the id of the client to check
1014 * @param id the last processed message
1015 * @param enable <code>true</code> to enable; <code>false</code> to disable
1016 */
1017 public void enableAsynchronousDelivery(long clientId, String id,
1018 boolean enable)
1019 throws JMSException {
1020 ConsumerEndpoint consumer = getConsumerEndpoint(clientId);
1021 if (consumer == null) {
1022 throw new JMSException(clientId + " is not registered");
1023 }
1024
1025 if (enable) {
1026 consumer.setMessageListener(this);
1027 } else {
1028 consumer.setMessageListener(null);
1029 }
1030 }
1031
1032 /***
1033 * Call recover on all registered consumers. This will cause all
1034 * unacknowledged messages to be redelivered. Before we recover we
1035 * need to stop messages delivery. We then need to start redelivery
1036 * when the recovery has been completed
1037 *
1038 * @throws JMSException if the session can't be recovered
1039 */
1040 public void recover() throws JMSException {
1041 // stop message delivery
1042 stop();
1043
1044 // iterate over the list of consumers recover them
1045 Iterator consumers = _consumers.values().iterator();
1046 while (consumers.hasNext()) {
1047 ((ConsumerEndpoint) consumers.next()).recover();
1048 }
1049
1050 // clear the messages in the sent message cache
1051 _sentMessageCache.clear();
1052
1053 // restart message delivery
1054 start();
1055
1056
1057 }
1058
1059 /***
1060 * Commit this session, which will acknowledge all sent messages for
1061 * all consumers.
1062 *
1063 * @throws JMSException - if there are any problems
1064 */
1065 public void commit() throws JMSException {
1066 try {
1067 _sentMessageCache.acknowledgeAllMessages();
1068 } catch (OutOfMemoryError exception) {
1069 String msg =
1070 "Failed to commit transaction due to out-of-memory error";
1071 _log.error(msg, exception);
1072 throw new JMSException(msg);
1073 }
1074 }
1075
1076 /***
1077 * Abort, will return all unacked messages to their respective endpoints,
1078 * if they are still active.
1079 *
1080 * @throws JMSException - if there are any problems
1081 */
1082 public void rollback()
1083 throws JMSException {
1084 _sentMessageCache.clear();
1085 }
1086
1087 // implementation XAResource.commit
1088 public void commit(Xid xid, boolean onePhase) throws XAException {
1089 try {
1090 ResourceManager.instance().commit(xid, onePhase);
1091 } catch (ResourceManagerException exception) {
1092 throw new XAException("Failed in commit " + exception);
1093 } finally {
1094 _xid = null;
1095 }
1096 }
1097
1098 // implementation of XAResource.end
1099 public void end(Xid xid, int flags) throws XAException {
1100 try {
1101 ResourceManager.instance().end(xid, flags);
1102 } catch (ResourceManagerException exception) {
1103 throw new XAException("Failed in end " + exception);
1104 } finally {
1105 _xid = null;
1106 }
1107 }
1108
1109 // implementation of XAResource.forget
1110 public void forget(Xid xid) throws XAException {
1111 try {
1112 ResourceManager.instance().forget(xid);
1113 } catch (ResourceManagerException exception) {
1114 throw new XAException("Failed in forget " + exception);
1115 } finally {
1116 _xid = null;
1117 }
1118 }
1119
1120 // implementation of XAResource.getTransactionTimeout
1121 public int getTransactionTimeout() throws XAException {
1122 try {
1123 return ResourceManager.instance().getTransactionTimeout();
1124 } catch (ResourceManagerException exception) {
1125 throw new XAException("Failed in getTransactionTimeout " +
1126 exception);
1127 }
1128 }
1129
1130 // implementation of XAResource.isSameRM
1131 public boolean isSameRM(XAResource xares) throws XAException {
1132 return true;
1133 }
1134
1135 // implementation XAResource.isSame
1136 public int prepare(Xid xid) throws XAException {
1137 try {
1138 return ResourceManager.instance().prepare(xid);
1139 } catch (ResourceManagerException exception) {
1140 throw new XAException("Failed in prepare " + exception);
1141 }
1142 }
1143
1144 // implementation of XAResource.prepare
1145 public Xid[] recover(int flag) throws XAException {
1146 try {
1147 return ResourceManager.instance().recover(flag);
1148 } catch (ResourceManagerException exception) {
1149 throw new XAException("Failed in recover " + exception);
1150 }
1151 }
1152
1153 // implementation of XAResource.recover
1154 public void rollback(Xid xid) throws XAException {
1155 try {
1156 ResourceManager.instance().rollback(xid);
1157 } catch (ResourceManagerException exception) {
1158 throw new XAException("Failed in rollback " + exception);
1159 } finally {
1160 // clear the current xid
1161 _xid = null;
1162 }
1163 }
1164
1165 // implementation of XAResource.rollback
1166 public boolean setTransactionTimeout(int seconds) throws XAException {
1167 try {
1168 return ResourceManager.instance().setTransactionTimeout(seconds);
1169 } catch (ResourceManagerException exception) {
1170 throw new XAException("Failed in setTransactionTimeout "
1171 + exception);
1172 }
1173 }
1174
1175 // implementation of XAResource.setTransactionTimeout
1176 public void start(Xid xid, int flags) throws XAException {
1177 try {
1178 ResourceManager.instance().start(xid, flags);
1179
1180 // set this as the current xid for this session
1181 _xid = xid;
1182 } catch (ResourceManagerException exception) {
1183 throw new XAException("Failed in start " + exception);
1184 }
1185 }
1186
1187 /***
1188 * Return the xid that is currently associated with this session or null
1189 * if this session is currently not part of a global transactions
1190 *
1191 * @return Xid
1192 */
1193 public Xid getXid() {
1194 return _xid;
1195 }
1196
1197 /***
1198 * Return the identity of the {@link ResourceManager}. The transaction
1199 * manager should be the only one to initiating this call.
1200 *
1201 * @return the identity of the resource manager
1202 * @throws XAException - if it cannot retrieve the rid.
1203 */
1204 public String getResourceManagerId() throws XAException {
1205 try {
1206 return ResourceManager.instance().getResourceManagerId();
1207 } catch (ResourceManagerException exception) {
1208 throw new XAException("Failed in getResourceManagerId "
1209 + exception);
1210 }
1211 }
1212
1213 /***
1214 * Determines if the session is transacted
1215 *
1216 * @return <code>true</code> if the session is transacted
1217 */
1218 public boolean isTransacted() {
1219 return _transacted;
1220 }
1221
1222 /***
1223 * Returns the message acknowledgement mode for the session
1224 */
1225 public int getAckMode() {
1226 return _ackMode;
1227 }
1228
1229 /***
1230 * Returns the consumer endpoint for the supplied client id
1231 *
1232 * @param clientId the identity of the consumer endpoint
1233 * @return the consumer endpoint corresponding to <code>clientId</code>,
1234 * or <code>null</code> if none exists
1235 */
1236 public ConsumerEndpoint getConsumerEndpoint(long clientId) {
1237 String identity = Long.toString(clientId);
1238 return (ConsumerEndpoint) _consumers.get(identity);
1239 }
1240
1241 /***
1242 * This method is used to stop and restart the session. Stopping the
1243 * session should stop all message delivery to session consumers
1244 *
1245 * @param stop - true if we need to stop the session, false otherwise
1246 */
1247 private void pause(boolean stop) {
1248 Iterator iter = _consumers.values().iterator();
1249 while (iter.hasNext()) {
1250 ((ConsumerEndpoint) iter.next()).setStopped(stop);
1251 }
1252 }
1253
1254 /***
1255 * Check the delivery mode of the message. If the delivery mode is
1256 * persistent and the destination is non-administered then change the
1257 * delivery mode to non-persistent so that it can be processed correctly
1258 * by the server
1259 *
1260 * @param message - the message to check
1261 * @throws JMSException - propagate JMSException to client
1262 */
1263 private void checkDeliveryMode(MessageImpl message) throws JMSException {
1264 if ((message.getJMSDeliveryMode() == DeliveryMode.PERSISTENT) &&
1265 (!DestinationManager.instance().isMessageForAdministeredDestination(message))) {
1266 message.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
1267 }
1268 }
1269
1270 } //-- JmsServerSession
This page was automatically generated by Maven