1 /***
2 *
3 * Copyright 2004 Protique Ltd
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 **/
18
19 package org.codehaus.activemq;
20
21 import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArrayList;
22 import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
23 import EDU.oswego.cs.dl.util.concurrent.SynchronizedInt;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.codehaus.activemq.capacity.CapacityMonitorEvent;
27 import org.codehaus.activemq.capacity.CapacityMonitorEventListener;
28 import org.codehaus.activemq.management.JMSConnectionStatsImpl;
29 import org.codehaus.activemq.management.JMSStatsImpl;
30 import org.codehaus.activemq.management.StatsCapable;
31 import org.codehaus.activemq.message.ActiveMQMessage;
32 import org.codehaus.activemq.message.CapacityInfo;
33 import org.codehaus.activemq.message.ConnectionInfo;
34 import org.codehaus.activemq.message.ConsumerInfo;
35 import org.codehaus.activemq.message.Packet;
36 import org.codehaus.activemq.message.PacketListener;
37 import org.codehaus.activemq.message.ProducerInfo;
38 import org.codehaus.activemq.message.Receipt;
39 import org.codehaus.activemq.message.SessionInfo;
40 import org.codehaus.activemq.message.util.MemoryBoundedQueue;
41 import org.codehaus.activemq.message.util.MemoryBoundedQueueManager;
42 import org.codehaus.activemq.transport.TransportChannel;
43 import org.codehaus.activemq.transport.TransportStatusEvent;
44 import org.codehaus.activemq.transport.TransportStatusEventListener;
45 import org.codehaus.activemq.util.IdGenerator;
46
47 import javax.jms.*;
48 import javax.jms.IllegalStateException;
49 import javax.management.j2ee.statistics.Stats;
50 import java.util.Iterator;
51
52 /***
53 * A <CODE>Connection</CODE> object is a client's active connection to its JMS provider. It typically allocates
54 * provider resources outside the Java virtual machine (JVM).
55 * <P>
56 * Connections support concurrent use.
57 * <P>
58 * A connection serves several purposes:
59 * <UL>
60 * <LI>It encapsulates an open connection with a JMS provider. It typically represents an open TCP/IP socket between a
61 * client and the service provider software.
62 * <LI>Its creation is where client authentication takes place.
63 * <LI>It can specify a unique client identifier.
64 * <LI>It provides a <CODE>ConnectionMetaData</CODE> object.
65 * <LI>It supports an optional <CODE>ExceptionListener</CODE> object.
66 * </UL>
67 * <P>
68 * Because the creation of a connection involves setting up authentication and communication, a connection is a
69 * relatively heavyweight object. Most clients will do all their messaging with a single connection. Other more advanced
70 * applications may use several connections. The JMS API does not architect a reason for using multiple connections;
71 * however, there may be operational reasons for doing so.
72 * <P>
73 * A JMS client typically creates a connection, one or more sessions, and a number of message producers and consumers.
74 * When a connection is created, it is in stopped mode. That means that no messages are being delivered.
75 * <P>
76 * It is typical to leave the connection in stopped mode until setup is complete (that is, until all message consumers
77 * have been created). At that point, the client calls the connection's <CODE>start</CODE> method, and messages begin
78 * arriving at the connection's consumers. This setup convention minimizes any client confusion that may result from
79 * asynchronous message delivery while the client is still in the process of setting itself up.
80 * <P>
81 * A connection can be started immediately, and the setup can be done afterwards. Clients that do this must be prepared
82 * to handle asynchronous message delivery while they are still in the process of setting up.
83 * <P>
84 * A message producer can send messages while a connection is stopped. <p/>This class is also a <CODE>TopicConnection
85 * </CODE>. A <CODE>TopicConnection</CODE> object is an active connection to a publish/subscribe JMS provider. A
86 * client uses a <CODE>TopicConnection</CODE> object to create one or more <CODE>TopicSession</CODE> objects for
87 * producing and consuming messages.
88 * <P>
89 * A <CODE>TopicConnection</CODE> can be used to create a <CODE>TopicSession</CODE>, from which specialized
90 * topic-related objects can be created. A more general, and recommended approach is to use the <CODE>Connection
91 * </CODE> object.
92 * <P>
93 * <p/><p/>This class is also a <CODE>QueueConnection</CODE>. A A <CODE>QueueConnection</CODE> object is an active
94 * connection to a point-to-point JMS provider. A client uses a <CODE>QueueConnection</CODE> object to create one or
95 * more <CODE>QueueSession</CODE> objects for producing and consuming messages.
96 * <P>
97 * A <CODE>QueueConnection</CODE> can be used to create a <CODE>QueueSession</CODE>, from which specialized
98 * queue-related objects can be created. A more general, and recommended, approach is to use the <CODE>Connection
99 * </CODE> object.
100 * <P>
101 * A <CODE>QueueConnection</CODE> cannot be used to create objects specific to the publish/subscribe domain. The
102 * <CODE>createDurableConnectionConsumer</CODE> method inherits from <CODE>Connection</CODE>, but must throw an
103 * <CODE>IllegalStateException</CODE> if used from <CODE>QueueConnection</CODE>. // *
104 *
105 * @version $Revision: 1.50 $
106 * @see javax.jms.Connection
107 * @see javax.jms.ConnectionFactory
108 * @see javax.jms.QueueConnection
109 * @see javax.jms.TopicConnection
110 * @see javax.jms.TopicConnectionFactory
111 * @see javax.jms.QueueConnection
112 * @see javax.jms.QueueConnectionFactory
113 */
114 public class ActiveMQConnection
115 implements
116 Connection,
117 PacketListener,
118 ExceptionListener,
119 TopicConnection,
120 QueueConnection,
121 StatsCapable,
122 CapacityMonitorEventListener,
123 TransportStatusEventListener {
124 private static final Log log = LogFactory.getLog(ActiveMQConnection.class);
125 private static final int DEFAULT_CONNECTION_MEMORY_LIMIT = 10 * 1024 * 1024;
126 private ActiveMQConnectionFactory factory;
127 private String userName;
128 private String password;
129 protected String clientID;
130 protected IdGenerator consumerIdGenerator;
131 private IdGenerator clientIdGenerator;
132 protected IdGenerator packetIdGenerator;
133 private IdGenerator sessionIdGenerator;
134 private TransportChannel transportChannel;
135 private ExceptionListener exceptionListener;
136 private CopyOnWriteArrayList sessions;
137 private CopyOnWriteArrayList messageDispatchers;
138 private CopyOnWriteArrayList connectionConsumers;
139 private SynchronizedInt consumerNumberGenerator;
140 private ActiveMQConnectionMetaData connectionMetaData;
141 private SynchronizedBoolean closed;
142 private SynchronizedBoolean started;
143 private boolean clientIDSet;
144 private boolean isConnectionInfoSentToBroker;
145 private boolean isTransportOK;
146 private long startTime;
147 private ActiveMQPrefetchPolicy prefetchPolicy;
148 private JMSConnectionStatsImpl stats;
149 private JMSStatsImpl factoryStats;
150 private MemoryBoundedQueueManager boundedQueueManager;
151 private long flowControlSleepTime = 0;
152 /***
153 * Default UserName for the Connection
154 */
155 public static final String DEFAULT_USER = "defaultUser";
156 /***
157 * Default URL for the ActiveMQ Broker
158 */
159 public static final String DEFAULT_URL = "tcp://localhost:61616";
160 /***
161 * Default Password for the Connection
162 */
163 public static final String DEFAULT_PASSWORD = "defaultPassword";
164 private boolean userSpecifiedClientID;
165
166 /***
167 * A static helper method to create a new connection
168 *
169 * @return an ActiveMQConnection
170 * @throws JMSException
171 */
172 public static ActiveMQConnection makeConnection() throws JMSException {
173 ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
174 return (ActiveMQConnection) factory.createConnection();
175 }
176
177 /***
178 * A static helper method to create a new connection
179 *
180 * @param uri
181 * @return and ActiveMQConnection
182 * @throws JMSException
183 */
184 public static ActiveMQConnection makeConnection(String uri) throws JMSException {
185 ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(uri);
186 return (ActiveMQConnection) factory.createConnection();
187 }
188
189 /***
190 * A static helper method to create a new connection
191 *
192 * @param user
193 * @param password
194 * @param uri
195 * @return an ActiveMQConnection
196 * @throws JMSException
197 */
198 public static ActiveMQConnection makeConnection(String user, String password, String uri) throws JMSException {
199 ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(user, password, uri);
200 return (ActiveMQConnection) factory.createConnection();
201 }
202
203 /***
204 * Constructs a connection from an existing TransportChannel and user/password.
205 *
206 * @param factory
207 * @param theUserName the users name
208 * @param thePassword the password
209 * @param transportChannel the transport channel to communicate with the server
210 * @throws JMSException
211 */
212 public ActiveMQConnection(ActiveMQConnectionFactory factory, String theUserName, String thePassword,
213 TransportChannel transportChannel) throws JMSException {
214 this(factory, theUserName, thePassword);
215 this.transportChannel = transportChannel;
216 this.transportChannel.setPacketListener(this);
217 this.transportChannel.setExceptionListener(this);
218 this.transportChannel.addTransportStatusEventListener(this);
219 this.transportChannel.start();
220 this.isTransportOK = true;
221 }
222
223 protected ActiveMQConnection(ActiveMQConnectionFactory factory, String theUserName, String thePassword) {
224 this.factory = factory;
225 this.userName = theUserName;
226 this.password = thePassword;
227 this.clientIdGenerator = new IdGenerator();
228 this.packetIdGenerator = new IdGenerator();
229 this.consumerIdGenerator = new IdGenerator();
230 this.sessionIdGenerator = new IdGenerator();
231 this.consumerNumberGenerator = new SynchronizedInt(0);
232 this.sessions = new CopyOnWriteArrayList();
233 this.messageDispatchers = new CopyOnWriteArrayList();
234 this.connectionConsumers = new CopyOnWriteArrayList();
235 this.connectionMetaData = new ActiveMQConnectionMetaData();
236 this.closed = new SynchronizedBoolean(false);
237 this.started = new SynchronizedBoolean(false);
238 this.startTime = System.currentTimeMillis();
239 this.prefetchPolicy = new ActiveMQPrefetchPolicy();
240 this.boundedQueueManager = new MemoryBoundedQueueManager(clientID, DEFAULT_CONNECTION_MEMORY_LIMIT);
241 this.boundedQueueManager.addCapacityEventListener(this);
242 boolean transactional = this instanceof XAConnection;
243 factoryStats = factory.getFactoryStats();
244 factoryStats.addConnection(this);
245 stats = new JMSConnectionStatsImpl(sessions, transactional);
246 factory.onConnectionCreate(this);
247 }
248
249 /***
250 * @return statistics for this Connection
251 */
252 public Stats getStats() {
253 return stats;
254 }
255
256 /***
257 * @return a number unique for this connection
258 */
259 public JMSConnectionStatsImpl getConnectionStats() {
260 return stats;
261 }
262
263 /***
264 * Creates a <CODE>Session</CODE> object.
265 *
266 * @param transacted indicates whether the session is transacted
267 * @param acknowledgeMode indicates whether the consumer or the client will acknowledge any messages it receives;
268 * ignored if the session is transacted. Legal values are <code>Session.AUTO_ACKNOWLEDGE</code>,
269 * <code>Session.CLIENT_ACKNOWLEDGE</code>, and <code>Session.DUPS_OK_ACKNOWLEDGE</code>.
270 * @return a newly created session
271 * @throws JMSException if the <CODE>Connection</CODE> object fails to create a session due to some internal error
272 * or lack of support for the specific transaction and acknowledgement mode.
273 * @see Session#AUTO_ACKNOWLEDGE
274 * @see Session#CLIENT_ACKNOWLEDGE
275 * @see Session#DUPS_OK_ACKNOWLEDGE
276 * @since 1.1
277 */
278 public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException {
279 checkClosed();
280 ensureClientIDInitialised();
281 return new ActiveMQSession(this, (transacted ? Session.SESSION_TRANSACTED : acknowledgeMode));
282 }
283
284 /***
285 * Gets the client identifier for this connection.
286 * <P>
287 * This value is specific to the JMS provider. It is either preconfigured by an administrator in a <CODE>
288 * ConnectionFactory</CODE> object or assigned dynamically by the application by calling the
289 * <code>setClientID</code> method.
290 *
291 * @return the unique client identifier
292 * @throws JMSException if the JMS provider fails to return the client ID for this connection due to some internal
293 * error.
294 */
295 public String getClientID() throws JMSException {
296 checkClosed();
297 return this.clientID;
298 }
299
300 /***
301 * Sets the client identifier for this connection.
302 * <P>
303 * The preferred way to assign a JMS client's client identifier is for it to be configured in a client-specific
304 * <CODE>ConnectionFactory</CODE> object and transparently assigned to the <CODE>Connection</CODE> object it
305 * creates.
306 * <P>
307 * Alternatively, a client can set a connection's client identifier using a provider-specific value. The facility to
308 * set a connection's client identifier explicitly is not a mechanism for overriding the identifier that has been
309 * administratively configured. It is provided for the case where no administratively specified identifier exists.
310 * If one does exist, an attempt to change it by setting it must throw an <CODE>IllegalStateException</CODE>. If
311 * a client sets the client identifier explicitly, it must do so immediately after it creates the connection and
312 * before any other action on the connection is taken. After this point, setting the client identifier is a
313 * programming error that should throw an <CODE>IllegalStateException</CODE>.
314 * <P>
315 * The purpose of the client identifier is to associate a connection and its objects with a state maintained on
316 * behalf of the client by a provider. The only such state identified by the JMS API is that required to support
317 * durable subscriptions.
318 * <P>
319 * If another connection with the same <code>clientID</code> is already running when this method is called, the
320 * JMS provider should detect the duplicate ID and throw an <CODE>InvalidClientIDException</CODE>.
321 *
322 * @param newClientID the unique client identifier
323 * @throws JMSException if the JMS provider fails to set the client ID for this connection due to some internal
324 * error.
325 * @throws javax.jms.InvalidClientIDException
326 * if the JMS client specifies an invalid or duplicate client ID.
327 * @throws javax.jms.IllegalStateException
328 * if the JMS client attempts to set a connection's client ID at the wrong
329 * time or when it has been administratively configured.
330 */
331 public void setClientID(String newClientID) throws JMSException {
332 if (this.clientIDSet) {
333 throw new IllegalStateException("The clientID has already been set");
334 }
335 if (this.isConnectionInfoSentToBroker) {
336 throw new IllegalStateException("Setting clientID on a used Connection is not allowed");
337 }
338 checkClosed();
339 this.clientID = newClientID;
340 this.userSpecifiedClientID = true;
341 }
342
343 /***
344 * Gets the metadata for this connection.
345 *
346 * @return the connection metadata
347 * @throws JMSException if the JMS provider fails to get the connection metadata for this connection.
348 * @see javax.jms.ConnectionMetaData
349 */
350 public ConnectionMetaData getMetaData() throws JMSException {
351 checkClosed();
352 return this.connectionMetaData;
353 }
354
355 /***
356 * Gets the <CODE>ExceptionListener</CODE> object for this connection. Not every <CODE>Connection</CODE> has an
357 * <CODE>ExceptionListener</CODE> associated with it.
358 *
359 * @return the <CODE>ExceptionListener</CODE> for this connection, or null. if no <CODE>ExceptionListener</CODE>
360 * is associated with this connection.
361 * @throws JMSException if the JMS provider fails to get the <CODE>ExceptionListener</CODE> for this connection.
362 * @see javax.jms.Connection#setExceptionListener(ExceptionListener)
363 */
364 public ExceptionListener getExceptionListener() throws JMSException {
365 checkClosed();
366 return this.exceptionListener;
367 }
368
369 /***
370 * Sets an exception listener for this connection.
371 * <P>
372 * If a JMS provider detects a serious problem with a connection, it informs the connection's <CODE>
373 * ExceptionListener</CODE>, if one has been registered. It does this by calling the listener's <CODE>onException
374 * </CODE> method, passing it a <CODE>JMSException</CODE> object describing the problem.
375 * <P>
376 * An exception listener allows a client to be notified of a problem asynchronously. Some connections only consume
377 * messages, so they would have no other way to learn their connection has failed.
378 * <P>
379 * A connection serializes execution of its <CODE>ExceptionListener</CODE>.
380 * <P>
381 * A JMS provider should attempt to resolve connection problems itself before it notifies the client of them.
382 *
383 * @param listener the exception listener
384 * @throws JMSException if the JMS provider fails to set the exception listener for this connection.
385 */
386 public void setExceptionListener(ExceptionListener listener) throws JMSException {
387 checkClosed();
388 this.exceptionListener = listener;
389 this.transportChannel.setExceptionListener(listener);
390 }
391
392 /***
393 * Starts (or restarts) a connection's delivery of incoming messages. A call to <CODE>start</CODE> on a connection
394 * that has already been started is ignored.
395 *
396 * @throws JMSException if the JMS provider fails to start message delivery due to some internal error.
397 * @see javax.jms.Connection#stop()
398 */
399 public void start() throws JMSException {
400 checkClosed();
401 if (started.commit(false, true)) {
402 sendConnectionInfoToBroker();
403 this.transportChannel.start();
404 for (Iterator i = sessions.iterator(); i.hasNext();) {
405 ActiveMQSession s = (ActiveMQSession) i.next();
406 s.start();
407 }
408 }
409 }
410
411 /***
412 * @return true if this Connection is started
413 */
414 protected boolean isStarted() {
415 return started.get();
416 }
417
418 /***
419 * Temporarily stops a connection's delivery of incoming messages. Delivery can be restarted using the connection's
420 * <CODE>start</CODE> method. When the connection is stopped, delivery to all the connection's message consumers
421 * is inhibited: synchronous receives block, and messages are not delivered to message listeners.
422 * <P>
423 * This call blocks until receives and/or message listeners in progress have completed.
424 * <P>
425 * Stopping a connection has no effect on its ability to send messages. A call to <CODE>stop</CODE> on a
426 * connection that has already been stopped is ignored.
427 * <P>
428 * A call to <CODE>stop</CODE> must not return until delivery of messages has paused. This means that a client can
429 * rely on the fact that none of its message listeners will be called and that all threads of control waiting for
430 * <CODE>receive</CODE> calls to return will not return with a message until the connection is restarted. The
431 * receive timers for a stopped connection continue to advance, so receives may time out while the connection is
432 * stopped.
433 * <P>
434 * If message listeners are running when <CODE>stop</CODE> is invoked, the <CODE>stop</CODE> call must wait
435 * until all of them have returned before it may return. While these message listeners are completing, they must
436 * have the full services of the connection available to them.
437 *
438 * @throws JMSException if the JMS provider fails to stop message delivery due to some internal error.
439 * @see javax.jms.Connection#start()
440 */
441 public void stop() throws JMSException {
442 checkClosed();
443 if (started.commit(true, false)) {
444 for (Iterator i = sessions.iterator(); i.hasNext();) {
445 ActiveMQSession s = (ActiveMQSession) i.next();
446 s.stop();
447 }
448 sendConnectionInfoToBroker(2000, closed.get());
449 transportChannel.stop();
450 }
451 }
452
453 /***
454 * Closes the connection.
455 * <P>
456 * Since a provider typically allocates significant resources outside the JVM on behalf of a connection, clients
457 * should close these resources when they are not needed. Relying on garbage collection to eventually reclaim these
458 * resources may not be timely enough.
459 * <P>
460 * There is no need to close the sessions, producers, and consumers of a closed connection.
461 * <P>
462 * Closing a connection causes all temporary destinations to be deleted.
463 * <P>
464 * When this method is invoked, it should not return until message processing has been shut down in an orderly
465 * fashion. This means that all message listeners that may have been running have returned, and that all pending
466 * receives have returned. A close terminates all pending message receives on the connection's sessions' consumers.
467 * The receives may return with a message or with null, depending on whether there was a message available at the
468 * time of the close. If one or more of the connection's sessions' message listeners is processing a message at the
469 * time when connection <CODE>close</CODE> is invoked, all the facilities of the connection and its sessions must
470 * remain available to those listeners until they return control to the JMS provider.
471 * <P>
472 * Closing a connection causes any of its sessions' transactions in progress to be rolled back. In the case where a
473 * session's work is coordinated by an external transaction manager, a session's <CODE>commit</CODE> and <CODE>
474 * rollback</CODE> methods are not used and the result of a closed session's work is determined later by the
475 * transaction manager. Closing a connection does NOT force an acknowledgment of client-acknowledged sessions.
476 * <P>
477 * Invoking the <CODE>acknowledge</CODE> method of a received message from a closed connection's session must
478 * throw an <CODE>IllegalStateException</CODE>. Closing a closed connection must NOT throw an exception.
479 *
480 * @throws JMSException if the JMS provider fails to close the connection due to some internal error. For example, a
481 * failure to release resources or to close a socket connection can cause this exception to be thrown.
482 */
483 public synchronized void close() throws JMSException {
484 if (!closed.get()) {
485 boundedQueueManager.removeCapacityEventListener(this);
486 try {
487 for (Iterator i = this.sessions.iterator(); i.hasNext();) {
488 ActiveMQSession s = (ActiveMQSession) i.next();
489 s.close();
490 }
491 for (Iterator i = this.connectionConsumers.iterator(); i.hasNext();) {
492 ActiveMQConnectionConsumer c = (ActiveMQConnectionConsumer) i.next();
493 c.close();
494 }
495 sendConnectionInfoToBroker(2000, true);
496 this.connectionConsumers.clear();
497 this.messageDispatchers.clear();
498 this.transportChannel.stop();
499 }
500 finally {
501 this.sessions.clear();
502 started.set(false);
503 factory.onConnectionClose(this);
504 }
505 closed.set(true);
506 }
507 }
508
509 /***
510 * simply throws an exception if the Connection is already closed
511 *
512 * @throws IllegalStateException
513 */
514 protected void checkClosed() throws IllegalStateException {
515 if (this.closed.get()) {
516 throw new IllegalStateException("The Connection is closed");
517 }
518 }
519
520 /***
521 * Creates a connection consumer for this connection (optional operation). This is an expert facility not used by
522 * regular JMS clients.
523 *
524 * @param destination the destination to access
525 * @param messageSelector only messages with properties matching the message selector expression are delivered. A
526 * value of null or an empty string indicates that there is no message selector for the message consumer.
527 * @param sessionPool the server session pool to associate with this connection consumer
528 * @param maxMessages the maximum number of messages that can be assigned to a server session at one time
529 * @return the connection consumer
530 * @throws JMSException if the <CODE>Connection</CODE> object fails to create a connection consumer due to some
531 * internal error or invalid arguments for <CODE>sessionPool</CODE> and <CODE>messageSelector</CODE>.
532 * @throws javax.jms.InvalidDestinationException
533 * if an invalid destination is specified.
534 * @throws javax.jms.InvalidSelectorException
535 * if the message selector is invalid.
536 * @see javax.jms.ConnectionConsumer
537 * @since 1.1
538 */
539 public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector,
540 ServerSessionPool sessionPool, int maxMessages) throws JMSException {
541 checkClosed();
542 ConsumerInfo info = new ConsumerInfo();
543 info.setId(this.packetIdGenerator.generateId());
544 info.setConsumerId(consumerIdGenerator.generateId());
545 info.setDestination(ActiveMQMessageTransformation.transformDestination(destination));
546 info.setSelector(messageSelector);
547 return new ActiveMQConnectionConsumer(this, sessionPool, info, maxMessages);
548 }
549
550 /***
551 * Create a durable connection consumer for this connection (optional operation). This is an expert facility not
552 * used by regular JMS clients.
553 *
554 * @param topic topic to access
555 * @param subscriptionName durable subscription name
556 * @param messageSelector only messages with properties matching the message selector expression are delivered. A
557 * value of null or an empty string indicates that there is no message selector for the message consumer.
558 * @param sessionPool the server session pool to associate with this durable connection consumer
559 * @param maxMessages the maximum number of messages that can be assigned to a server session at one time
560 * @return the durable connection consumer
561 * @throws JMSException if the <CODE>Connection</CODE> object fails to create a connection consumer due to some
562 * internal error or invalid arguments for <CODE>sessionPool</CODE> and <CODE>messageSelector</CODE>.
563 * @throws javax.jms.InvalidDestinationException
564 * if an invalid destination is specified.
565 * @throws javax.jms.InvalidSelectorException
566 * if the message selector is invalid.
567 * @see javax.jms.ConnectionConsumer
568 * @since 1.1
569 */
570 public ConnectionConsumer createDurableConnectionConsumer(Topic topic, String subscriptionName,
571 String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException {
572 checkClosed();
573 ConsumerInfo info = new ConsumerInfo();
574 info.setId(this.packetIdGenerator.generateId());
575 info.setConsumerId(this.consumerIdGenerator.generateId());
576 info.setDestination(ActiveMQMessageTransformation.transformDestination(topic));
577 info.setSelector(messageSelector);
578 info.setConsumerName(subscriptionName);
579 return new ActiveMQConnectionConsumer(this, sessionPool, info, maxMessages);
580 }
581
582 /***
583 * @return Returns the prefetchPolicy.
584 */
585 public ActiveMQPrefetchPolicy getPrefetchPolicy() {
586 return prefetchPolicy;
587 }
588
589 /***
590 * @param prefetchPolicy The prefetchPolicy to set.
591 */
592 public void setPrefetchPolicy(ActiveMQPrefetchPolicy prefetchPolicy) {
593 this.prefetchPolicy = prefetchPolicy;
594 }
595
596 /***
597 * Implementation of the PacketListener interface - consume a packet
598 *
599 * @param packet - the Packet to consume
600 * @see org.codehaus.activemq.message.PacketListener#consume(org.codehaus.activemq.message.Packet)
601 */
602 public void consume(Packet packet) {
603 if (!closed.get() && packet != null) {
604 if (packet.isJMSMessage()) {
605 ActiveMQMessage message = (ActiveMQMessage) packet;
606 message.setReadOnly(true);
607 message.setProducerID(clientID);
608
609 try {
610 int count = 0;
611 for (Iterator i = this.messageDispatchers.iterator(); i.hasNext();) {
612 ActiveMQMessageDispatcher dispatcher = (ActiveMQMessageDispatcher) i.next();
613 if (dispatcher.isTarget(message)) {
614 if (count > 0) {
615
616 message = message.deepCopy();
617 }
618 dispatcher.dispatch(message);
619 count++;
620 }
621 }
622 }
623 catch (JMSException jmsEx) {
624 handleAsyncException(jmsEx);
625 }
626 }
627 else if (packet.getPacketType() == Packet.CAPACITY_INFO) {
628 CapacityInfo info = (CapacityInfo) packet;
629 flowControlSleepTime = info.getFlowControlTimeout();
630
631 }
632 }
633 }
634
635 /***
636 * @see javax.jms.ExceptionListener#onException(javax.jms.JMSException)
637 */
638 public void onException(JMSException jmsEx) {
639
640 handleAsyncException(jmsEx);
641 isTransportOK = false;
642 try {
643 close();
644 }
645 catch (JMSException ex) {
646 log.warn("Got an exception closing the connection", ex);
647 }
648 }
649
650 /***
651 * Creates a <CODE>TopicSession</CODE> object.
652 *
653 * @param transacted indicates whether the session is transacted
654 * @param acknowledgeMode indicates whether the consumer or the client will acknowledge any messages it receives;
655 * ignored if the session is transacted. Legal values are <code>Session.AUTO_ACKNOWLEDGE</code>,
656 * <code>Session.CLIENT_ACKNOWLEDGE</code>, and <code>Session.DUPS_OK_ACKNOWLEDGE</code>.
657 * @return a newly created topic session
658 * @throws JMSException if the <CODE>TopicConnection</CODE> object fails to create a session due to some internal
659 * error or lack of support for the specific transaction and acknowledgement mode.
660 * @see Session#AUTO_ACKNOWLEDGE
661 * @see Session#CLIENT_ACKNOWLEDGE
662 * @see Session#DUPS_OK_ACKNOWLEDGE
663 */
664 public TopicSession createTopicSession(boolean transacted, int acknowledgeMode) throws JMSException {
665 checkClosed();
666 return new ActiveMQSession(this, (transacted ? Session.SESSION_TRANSACTED : acknowledgeMode));
667 }
668
669 /***
670 * Creates a connection consumer for this connection (optional operation). This is an expert facility not used by
671 * regular JMS clients.
672 *
673 * @param topic the topic to access
674 * @param messageSelector only messages with properties matching the message selector expression are delivered. A
675 * value of null or an empty string indicates that there is no message selector for the message consumer.
676 * @param sessionPool the server session pool to associate with this connection consumer
677 * @param maxMessages the maximum number of messages that can be assigned to a server session at one time
678 * @return the connection consumer
679 * @throws JMSException if the <CODE>TopicConnection</CODE> object fails to create a connection consumer due to
680 * some internal error or invalid arguments for <CODE>sessionPool</CODE> and <CODE>messageSelector</CODE>.
681 * @throws InvalidDestinationException if an invalid topic is specified.
682 * @throws InvalidSelectorException if the message selector is invalid.
683 * @see javax.jms.ConnectionConsumer
684 */
685 public ConnectionConsumer createConnectionConsumer(Topic topic, String messageSelector,
686 ServerSessionPool sessionPool, int maxMessages) throws JMSException {
687 checkClosed();
688 ConsumerInfo info = new ConsumerInfo();
689 info.setId(this.packetIdGenerator.generateId());
690 info.setConsumerId(this.consumerIdGenerator.generateId());
691 info.setDestination(ActiveMQMessageTransformation.transformDestination(topic));
692 info.setSelector(messageSelector);
693 return new ActiveMQConnectionConsumer(this, sessionPool, info, maxMessages);
694 }
695
696 /***
697 * Creates a <CODE>QueueSession</CODE> object.
698 *
699 * @param transacted indicates whether the session is transacted
700 * @param acknowledgeMode indicates whether the consumer or the client will acknowledge any messages it receives;
701 * ignored if the session is transacted. Legal values are <code>Session.AUTO_ACKNOWLEDGE</code>,
702 * <code>Session.CLIENT_ACKNOWLEDGE</code>, and <code>Session.DUPS_OK_ACKNOWLEDGE</code>.
703 * @return a newly created queue session
704 * @throws JMSException if the <CODE>QueueConnection</CODE> object fails to create a session due to some internal
705 * error or lack of support for the specific transaction and acknowledgement mode.
706 * @see Session#AUTO_ACKNOWLEDGE
707 * @see Session#CLIENT_ACKNOWLEDGE
708 * @see Session#DUPS_OK_ACKNOWLEDGE
709 */
710 public QueueSession createQueueSession(boolean transacted, int acknowledgeMode) throws JMSException {
711 checkClosed();
712 return new ActiveMQSession(this, (transacted ? Session.SESSION_TRANSACTED : acknowledgeMode));
713 }
714
715 /***
716 * Creates a connection consumer for this connection (optional operation). This is an expert facility not used by
717 * regular JMS clients.
718 *
719 * @param queue the queue to access
720 * @param messageSelector only messages with properties matching the message selector expression are delivered. A
721 * value of null or an empty string indicates that there is no message selector for the message consumer.
722 * @param sessionPool the server session pool to associate with this connection consumer
723 * @param maxMessages the maximum number of messages that can be assigned to a server session at one time
724 * @return the connection consumer
725 * @throws JMSException if the <CODE>QueueConnection</CODE> object fails to create a connection consumer due to
726 * some internal error or invalid arguments for <CODE>sessionPool</CODE> and <CODE>messageSelector</CODE>.
727 * @throws InvalidDestinationException if an invalid queue is specified.
728 * @throws InvalidSelectorException if the message selector is invalid.
729 * @see javax.jms.ConnectionConsumer
730 */
731 public ConnectionConsumer createConnectionConsumer(Queue queue, String messageSelector,
732 ServerSessionPool sessionPool, int maxMessages) throws JMSException {
733 checkClosed();
734 ConsumerInfo info = new ConsumerInfo();
735 info.setId(this.packetIdGenerator.generateId());
736 info.setConsumerId(this.consumerIdGenerator.generateId());
737 info.setDestination(ActiveMQMessageTransformation.transformDestination(queue));
738 info.setSelector(messageSelector);
739 return new ActiveMQConnectionConsumer(this, sessionPool, info, maxMessages);
740 }
741
742 /***
743 * Ensures that the clientID was manually specified and not auto-generated. If the clientID was not specified this
744 * method will throw an exception. This method is used to ensure that the clientID + durableSubscriber name are used
745 * correctly.
746 */
747 public void checkClientIDWasManuallySpecified() throws JMSException {
748 if (!userSpecifiedClientID) {
749 throw new JMSException("You cannot create a durable subscriber without specifying a unique clientID on a Connection");
750 }
751 }
752
753 /***
754 * handle disconnect/reconnect events
755 *
756 * @param event
757 */
758 public void statusChanged(TransportStatusEvent event) {
759 log.info("channel status changed: " + event);
760 if (event.getChannelStatus() == TransportStatusEvent.RECONNECTED) {
761 doReconnect();
762 }
763 }
764
765
766
767 void asyncSendPacket(Packet packet) throws JMSException {
768 if (isTransportOK && !closed.get()) {
769 packet.setReceiptRequired(false);
770 if (packet.isJMSMessage() && flowControlSleepTime > 0) {
771 try {
772 Thread.sleep(flowControlSleepTime);
773 }
774 catch (InterruptedException e) {
775 }
776 }
777 this.transportChannel.asyncSend(packet);
778 }
779 }
780
781 void syncSendPacket(Packet packet) throws JMSException {
782 syncSendPacket(packet, 0);
783 }
784
785 void syncSendPacket(Packet packet, int timeout) throws JMSException {
786 if (isTransportOK && !closed.get()) {
787 Receipt receipt;
788 packet.setReceiptRequired(true);
789 receipt = this.transportChannel.send(packet, timeout);
790 if (receipt != null) {
791 if (receipt.isFailed()) {
792 Throwable e = receipt.getException();
793 if (e != null) {
794 throw (JMSException) new JMSException(e.getMessage()).initCause(e);
795 }
796 throw new JMSException("syncSendPacket failed with unknown exception");
797 }
798 }
799 }
800 else {
801 throw new JMSException("syncSendTimedOut");
802 }
803 }
804
805 Receipt syncSendRequest(Packet packet) throws JMSException {
806 if (isTransportOK && !closed.get()) {
807 Receipt receipt;
808 packet.setReceiptRequired(true);
809 receipt = this.transportChannel.send(packet);
810 if (receipt.isFailed()) {
811 Throwable e = receipt.getException();
812 if (e != null) {
813 throw (JMSException) new JMSException(e.getMessage()).initCause(e);
814 }
815 throw new JMSException("syncSendPacket failed with unknown exception");
816 }
817 return receipt;
818 }
819 else {
820 throw new JMSException("Connection closed.");
821 }
822 }
823
824 /***
825 * Used internally for adding Sessions to the Connection
826 *
827 * @param session
828 * @throws JMSException
829 */
830 protected void addSession(ActiveMQSession session) throws JMSException {
831 this.sessions.add(session);
832 addMessageDispatcher(session);
833 SessionInfo info = createSessionInfo(session);
834 info.setStarted(true);
835 asyncSendPacket(info);
836 }
837
838 /***
839 * Used interanlly for removing Sessions from a Connection
840 *
841 * @param session
842 * @throws JMSException
843 */
844 protected void removeSession(ActiveMQSession session) throws JMSException {
845 this.sessions.remove(session);
846 removeMessageDispatcher(session);
847 SessionInfo info = createSessionInfo(session);
848 info.setStarted(false);
849 asyncSendPacket(info);
850 }
851
852 private SessionInfo createSessionInfo(ActiveMQSession session) {
853 SessionInfo info = new SessionInfo();
854 info.setId(packetIdGenerator.generateId());
855 info.setClientId(clientID);
856 info.setSessionId(session.getSessionId());
857 info.setStartTime(session.getStartTime());
858 return info;
859 }
860
861 /***
862 * Add a ConnectionConsumer
863 *
864 * @param connectionConsumer
865 * @throws JMSException
866 */
867 protected void addConnectionConsumer(ActiveMQConnectionConsumer connectionConsumer) throws JMSException {
868 this.connectionConsumers.add(connectionConsumer);
869 addMessageDispatcher(connectionConsumer);
870 }
871
872 /***
873 * Remove a ConnectionConsumer
874 *
875 * @param connectionConsumer
876 */
877 protected void removeConnectionConsumer(ActiveMQConnectionConsumer connectionConsumer) {
878 this.connectionConsumers.add(connectionConsumer);
879 removeMessageDispatcher(connectionConsumer);
880 }
881
882 /***
883 * Add a Message dispatcher to receive messages from the Broker
884 *
885 * @param messageDispatch
886 * @throws JMSException if an internal error
887 */
888 protected void addMessageDispatcher(ActiveMQMessageDispatcher messageDispatch) throws JMSException {
889 this.messageDispatchers.add(messageDispatch);
890 }
891
892 /***
893 * Remove a Message dispatcher
894 *
895 * @param messageDispatcher
896 */
897 protected void removeMessageDispatcher(ActiveMQMessageDispatcher messageDispatcher) {
898 this.messageDispatchers.remove(messageDispatcher);
899 }
900
901 /***
902 * Used for handling async exceptions
903 *
904 * @param jmsEx
905 */
906 protected void handleAsyncException(JMSException jmsEx) {
907 if (this.exceptionListener != null) {
908 this.exceptionListener.onException(jmsEx);
909 }
910 else {
911 log.warn("async exception with no exception listener", jmsEx);
912 }
913 }
914
915 private void sendConnectionInfoToBroker() throws JMSException {
916 sendConnectionInfoToBroker(0, closed.get());
917 }
918
919 /***
920 * Send the ConnectionInfo to the Broker
921 *
922 * @param timeout
923 * @param isClosed
924 * @throws JMSException
925 */
926 private void sendConnectionInfoToBroker(int timeout, boolean isClosed) throws JMSException {
927 if (!isConnectionInfoSentToBroker) {
928 transportChannel.setClientID(clientID);
929 }
930 this.isConnectionInfoSentToBroker = true;
931 ensureClientIDInitialised();
932 ConnectionInfo info = new ConnectionInfo();
933 info.setClientId(this.clientID);
934 info.setHostName(IdGenerator.getHostName());
935 info.setUserName(userName);
936 info.setPassword(password);
937 info.setId(packetIdGenerator.generateId());
938 info.setStartTime(startTime);
939 info.setStarted(started.get());
940 info.setClosed(isClosed);
941 syncSendPacket(info, timeout);
942 }
943
944 /***
945 * Set the maximum amount of memory this Connection should use for buffered inbound messages
946 *
947 * @param newMemoryLimit the new memory limit in bytes
948 */
949 public void setConnectionMemoryLimit(int newMemoryLimit) {
950 boundedQueueManager.setValueLimit(newMemoryLimit);
951 }
952
953 /***
954 * Get the current value for the maximum amount of memory this Connection should use for buffered inbound messages
955 *
956 * @return the current limit in bytes
957 */
958 public int getConnectionMemoryLimit() {
959 return (int) boundedQueueManager.getValueLimit();
960 }
961
962 /***
963 * CapacityMonitorEventListener implementation called when the capacity of a CapacityService changes
964 *
965 * @param event
966 */
967 public void capacityChanged(CapacityMonitorEvent event) {
968
969 CapacityInfo info = new CapacityInfo();
970 info.setId(packetIdGenerator.generateId());
971 info.setResourceName(event.getMonitorName());
972 info.setCapacity(event.getCapacity());
973
974 try {
975 asyncSendPacket(info);
976 }
977 catch (JMSException e) {
978 JMSException jmsEx = new JMSException("failed to send change in capacity");
979 jmsEx.setLinkedException(e);
980 handleAsyncException(jmsEx);
981 }
982 }
983
984 /***
985 * @return a number unique for this connection
986 */
987 protected int getNextConsumerNumber() {
988 return this.consumerNumberGenerator.increment();
989 }
990
991 protected String generateSessionId() {
992 return this.sessionIdGenerator.generateId();
993 }
994
995 protected void ensureClientIDInitialised() {
996 if (this.clientID == null) {
997 this.clientID = this.clientIdGenerator.generateId();
998 }
999 this.clientIDSet = true;
1000 }
1001
1002 protected MemoryBoundedQueue getMemoryBoundedQueue(String name) {
1003 return boundedQueueManager.getMemoryBoundedQueue(name);
1004 }
1005
1006 protected void doReconnect() {
1007 try {
1008
1009 sendConnectionInfoToBroker();
1010 for (Iterator iter = sessions.iterator(); iter.hasNext();) {
1011 ActiveMQSession session = (ActiveMQSession) iter.next();
1012 SessionInfo sessionInfo = createSessionInfo(session);
1013 sessionInfo.setStarted(true);
1014 asyncSendPacket(sessionInfo);
1015
1016 for (Iterator consumersIterator = session.consumers.iterator(); consumersIterator.hasNext();) {
1017 ActiveMQMessageConsumer consumer = (ActiveMQMessageConsumer) consumersIterator.next();
1018 ConsumerInfo consumerInfo = session.createConsumerInfo(consumer);
1019 consumerInfo.setStarted(true);
1020 syncSendPacket(consumerInfo);
1021 }
1022
1023 for (Iterator producersIterator = session.producers.iterator(); producersIterator.hasNext();) {
1024 ActiveMQMessageProducer producer = (ActiveMQMessageProducer) producersIterator.next();
1025 ProducerInfo producerInfo = session.createProducerInfo(producer);
1026 producerInfo.setStarted(true);
1027 syncSendPacket(producerInfo);
1028 }
1029 }
1030 }
1031 catch (JMSException jmsEx) {
1032 log.error("Failed to do reconnection");
1033 handleAsyncException(jmsEx);
1034 isTransportOK = false;
1035 }
1036 }
1037 }