View Javadoc

1   /***
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Exoffice Technologies.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Exoffice Technologies. Exolab is a registered
23   *    trademark of Exoffice Technologies.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2000-2004 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: JmsConnection.java,v 1.26 2004/01/11 02:33:44 tanderson Exp $
44   *
45   * Date         Author  Changes
46   * 3/21/2000    jima    Created
47   */
48  package org.exolab.jms.client;
49  
50  
51  import java.util.Enumeration;
52  import java.util.Vector;
53  
54  import javax.jms.Connection;
55  import javax.jms.ConnectionMetaData;
56  import javax.jms.ExceptionListener;
57  import javax.jms.IllegalStateException;
58  import javax.jms.JMSException;
59  
60  import org.apache.commons.logging.Log;
61  import org.apache.commons.logging.LogFactory;
62  
63  import org.exolab.core.service.ServiceException;
64  import org.exolab.jms.events.BasicEventManager;
65  
66  
67  /***
68   * Client side implementation of the <code>javax.jms.Connection</code>
69   * interface.
70   *
71   * @version     $Revision: 1.26 $ $Date: 2004/01/11 02:33:44 $
72   * @author      <a href="mailto:jima@exoffice.com">Jim Alateras</a>
73   * @author      <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
74   */
75  abstract class JmsConnection implements Connection {
76  
77      /***
78       * This flag indicates whether or not the connection is closed. It
79       * is set to false on creation
80       */
81      private boolean _closed = false;
82  
83      /***
84       * This flag indicates whether the connection is in the start or
85       * stopped state
86       */
87      private boolean _stopped = true;
88  
89      /***
90       * This flag indicates whether the connection has been modified.
91       * If so, subsequent attempts to invoke {@link #setClientID} will
92       * cause an <code>IllegalStateException</code> being thrown
93       */
94      private boolean _modified = false;
95  
96      /***
97       * This is the identity associated with the client. The identity is
98       * generated and assigned by the connection factory
99       */
100     private String _clientId = null;
101 
102     /***
103      * Gates the setting of the clientId more than once
104      */
105     private boolean _clientIdSet = false;
106 
107     /***
108      * A connection can maintain one and only one exception listener, The
109      * listener will be informed of unusual and exception events
110      */
111     private ExceptionListener _exceptionListener = null;
112 
113     /***
114      * Manage the list of activated and registered sessions. Everytime a
115      * session is created through this connection it is registered.
116      */
117     private Vector _sessions = new Vector();
118 
119     /***
120      * This is a back pointer to the connection factory which created this
121      * connection. It is assigned during object construction
122      */
123     private JmsConnectionFactory _factory = null;
124 
125     /***
126      * This is a stub to a remote object on the JMSSever. This abstraction
127      * allows us to use different underlying ORBS.
128      */
129     private JmsConnectionStubIfc _stub = null;
130 
131     /***
132      * The id is generated by the server and is unique for each connection
133      * created. This id is then used to generate other ids
134      */
135     private String _connectionId = null;
136 
137     /***
138      * The number of connections created
139      */
140     private static volatile int _activeConnections = 0;
141 
142     /***
143      * The connection data is immutable at this stage. This enables us to cache
144      * a single copy in memory
145      */
146     private static final JmsConnectionMetaData _metaData =
147         new JmsConnectionMetaData();
148 
149     /***
150      * The logger
151      */
152     private static final Log _log = LogFactory.getLog(JmsConnection.class);
153 
154 
155     /***
156      * Construct a new <code>JmsConnection</code>
157      * <p>
158      * This attempts to establish a connection to the JMS server
159      *
160      * @param factory the connection factory responsible for creating this
161      * @param id the client identity
162      * @param username the client username
163      * @param password the client password
164      * @throws JMSException if a connection cannot be established
165      */
166     protected JmsConnection(JmsConnectionFactory factory, String id, 
167                             String username, String password)
168         throws JMSException {
169 
170         if (factory == null) {
171             throw new IllegalArgumentException("Argument 'factory' is null");
172         }
173         if (id == null) {
174             throw new IllegalArgumentException("Argument 'id' is null");
175         }
176         if (id.trim().length() == 0) {
177             throw new IllegalArgumentException("Argument 'id' is invalid: '"
178                                                + id + "'");
179         }
180 
181         _factory = factory;
182         _clientId = id;
183 
184         _stopped = true;
185 
186         // use the factory object to retrieve the proxy that
187         // will be used to get a JmsConnectionStubIfc instance
188         // and cache its identity locally
189         _stub = factory.getProxy().createConnection(_clientId, username,
190                                                     password);
191         _connectionId = _stub.getConnectionId();
192 
193         // start the event manager
194         startEventManager();
195     }
196 
197     // implementation of Connection.getClientID
198     public String getClientID() throws JMSException {
199         ensureOpen();
200         setModified();
201 
202         return _clientId;
203     }
204 
205 
206     // implementation of Connection.setClientID
207     public void setClientID(String id) throws JMSException {
208         ensureOpen();
209 
210         // check if the client id has already been set
211         if (_clientIdSet) {
212             throw new IllegalStateException(
213                 "The client id has already been set");
214         }
215 
216         if (_modified) {
217             throw new IllegalStateException(
218                 "The client identifier must be set before any other operation "
219                 + "is performed");
220         }
221 
222         // gate the client id from being set more than once.
223         _clientId = id;
224         _clientIdSet = true;
225     }
226 
227 
228     // implementation of Connection.getMetaData
229     public ConnectionMetaData getMetaData() throws JMSException {
230         ensureOpen();
231         setModified();
232         return _metaData;
233     }
234 
235     // implementation of Connection.getExceptionListener
236     public ExceptionListener getExceptionListener() throws JMSException {
237         ensureOpen();
238         setModified();
239         return _exceptionListener;
240     }
241 
242 
243     // implementation of Connection.setExceptionListener
244     public void setExceptionListener(ExceptionListener listener)
245         throws JMSException {
246         ensureOpen();
247         setModified();
248         _exceptionListener = listener;
249     }
250 
251     /***
252      * Notify the exception listener of a JMSException. If the exception
253      * listener is not set then ignore it
254      *
255      * @param message message to deliver
256      */
257     public void notifyExceptionListener(JMSException message) {
258         // check the error code
259         if (message.getErrorCode() != null &&
260             message.getErrorCode().equals(
261                 JmsErrorCodes.CONNECTION_TO_SERVER_DROPPED)) {
262             // the connection to the server has been dropped so we need to
263             // release all local resources.
264             try {
265                 destroy();
266             } catch (JMSException exception) {
267                 _log.error(exception.getMessage(), exception);
268             }
269         }
270 
271         // finally notify registered exception listener
272         if (_exceptionListener != null) {
273             _exceptionListener.onException(message);
274         }
275     }
276 
277     // implementation of Connection.start
278     public synchronized void start() throws JMSException {
279         ensureOpen();
280         setModified();
281 
282         try {
283             if (_stopped) {
284                 // propagate the start to all the associated sessions. When
285                 // that is complete transition the state of the connection to
286                 // RUNNING
287                 Enumeration sessions = _sessions.elements();
288                 while (sessions.hasMoreElements()) {
289                     JmsSession session = (JmsSession) sessions.nextElement();
290                     session.start();
291                 }
292                 // set the  state of the connection to start
293                 _stopped = false;
294             }
295         } catch (JMSException exception) {
296             // do we need to change _stopped to true if the any of the
297             // sessions fail to start
298             throw exception;
299         }
300     }
301 
302     // implementation of Connection.stop
303     public synchronized void stop() throws JMSException {
304         ensureOpen();
305         setModified();
306 
307         if (!_stopped) {
308             // propagate the stop to all the encapsulated sessions before
309             // changing the state of the connection. Only when all the
310             // sessions have successfully transitioned, without exception,
311             // do we change the state of the connection
312             Enumeration sessions = _sessions.elements();
313             while (sessions.hasMoreElements()) {
314                 JmsSession session = (JmsSession) sessions.nextElement();
315                 session.stop();
316             }
317 
318             // set the state of the connection to stopped before stopping
319             // all the enclosed sessions
320             _stopped = true;
321         }
322     }
323 
324     // implementation of Connection.close
325     public synchronized void close() throws JMSException {
326         if (!_closed) {
327             try {
328                 // before we close we should stop the connection and any
329                 // associated sessions
330                 stop();
331 
332                 // propagate the close to all the encapsulated sessions before
333                 // changing the state of the connection. Only when all the
334                 // sessions have successfully transitioned, without exception,
335                 // do we change the state of the connection. All the sessions
336                 // are removed from the connection.
337                 Object sessions[] = _sessions.toArray();
338                 for (int i = 0; i < sessions.length; ++i) {
339                     JmsSession session = (JmsSession) sessions[i];
340                     session.close();
341                     // the session deregisters itself with the connection via
342                     // removeSession()
343                 }
344 
345                 // send a close to the stub and then null the stub
346                 getJmsConnectionStub().close();
347                 _stub = null;
348 
349                 // remove yourself from the list of connections managed by the
350                 // connection factory and then null the factory.
351                 _factory.removeConnection(this);
352                 _factory = null;
353 
354                 // set the closed flag so calling it multiple times is
355                 // cool
356                 _closed = true;
357             } finally {
358                 stopEventManager();
359             }
360         }
361     }
362 
363     /***
364      * Release all resources used by this connection, including supporting
365      * sessions
366      *
367      * @throws JMSException - error completing this request
368      */
369     public synchronized void destroy() throws JMSException {
370         if (!_closed) {
371             try {
372                 // propagate the close to all the encapsulated sessions before
373                 // changing the state of the connection. Only when all the
374                 // sessions have successfully transitioned, without exception,
375                 // do we change the state of the connection. All the sessions
376                 // are removed from the connection.
377                 Object sessions[] = _sessions.toArray();
378                 for (int i = 0; i < sessions.length; ++i) {
379                     JmsSession session = (JmsSession) sessions[i];
380                     session.destroy();
381                 }
382 
383                 // send a close to the stub and then null the stub
384                 getJmsConnectionStub().destroy();
385                 _stub = null;
386 
387                 // remove yourself from the list of connections managed by the
388                 // connection factory and then null the factory.
389                 _factory.removeConnection(this);
390                 _factory = null;
391 
392                 // set the closed flag so calling it multiple times is
393                 // cool
394                 _closed = true;
395             } finally {
396                 stopEventManager();
397             }
398         }
399     }
400 
401     /***
402      * Return the identity of this connection. An identity is created by the
403      * server and is unique within that servers environment
404      *
405      * @return      String
406      */
407     public String getConnectionId() {
408         return _connectionId;
409     }
410 
411     /***
412      * Return an instance of the remote server connection. If one has not
413      * been assigned then throw the JMSException exception
414      *
415      * @return      JmsConnectionStub   connection to the server
416      * @throws   JMSException
417      */
418     JmsConnectionStubIfc getJmsConnectionStub() throws JMSException {
419         if (_stub == null) {
420             throw new JMSException("The connectionstub is set to null");
421         }
422 
423         return _stub;
424     }
425 
426     /***
427      * Add the specified session to the list of managed sessions
428      *
429      * @param       session         session to register
430      */
431     protected void addSession(JmsSession session) {
432         _sessions.addElement(session);
433     }
434 
435     /***
436      * Remove the specified session from the list of managed sessions.
437      * If it doesn't exist then fail silently
438      *
439      * @param       session         session to remove
440      */
441     protected void removeSession(JmsSession session) {
442         _sessions.removeElement(session);
443     }
444 
445     /***
446      * Test whether the specified session is managed by this connection
447      *
448      * @param       session         session to test against
449      * @return      boolean         true if managed
450      */
451     protected boolean isManaged(JmsSession session) {
452         return _sessions.contains(session);
453     }
454 
455     /***
456      * Returns an enumeration of all sessions managed by this connection
457      *
458      * @return an enumeration of all sessions managed by this connection
459      */
460     protected Enumeration getSessions() {
461         return _sessions.elements();
462     }
463 
464     /***
465      * Return the running state of the connection
466      *
467      * @return <code>true</code> if stopped
468      */
469     protected boolean isStopped() {
470         return _stopped;
471     }
472 
473     /***
474      * Flags this connection as being modified. Subsequent attempts
475      * to invoke {@link #setClientID} will result in an
476      * <code>IllegalStateException</code> being thrown
477      */
478     protected void setModified() {
479         _modified = true;
480     }
481 
482     /***
483      * Delete the temporary destination and all the registered sessions
484      * consumers waiting to receive messages from this destination will
485      * be stopped.
486      * <p>
487      * It will throw a JMSException if the specified destination is not
488      * temporary or if the destination is null or if the destination is
489      * not owned by this connection
490      *
491      * @param       destination         temporary destination to delete
492      * @throws   JMSException
493      */
494     synchronized void deleteTemporaryDestination(JmsDestination destination)
495         throws JMSException {
496         if ((destination != null) &&
497             (destination instanceof JmsTemporaryDestination)) {
498             JmsTemporaryDestination temp_dest =
499                 (JmsTemporaryDestination) destination;
500 
501             // check to see that this destination was actually created by
502             // this connection
503             if (temp_dest.getOwningConnection() == this) {
504                 // this is currently a no-op but we probably need a way to
505                 // clean up on the server side
506             } else {
507                 throw new JMSException("The temp destination cannot be " +
508                     "used outside the scope of the connection creating it");
509             }
510         } else {
511             throw new JMSException("The destination is not temporary");
512         }
513     }
514 
515     /***
516      * Verifies that the connection is open
517      *
518      * @throws IllegalStateException if the connection is closed
519      */
520     protected void ensureOpen() throws IllegalStateException {
521         if (_closed) {
522             throw new IllegalStateException(
523                 "Cannot perform operation - session has been closed");
524         }
525     }
526 
527     /***
528      * This method will start the event manager service on the first connection
529      * that is created
530      */
531     private static void startEventManager() {
532         try {
533             if (_activeConnections++ == 0) {
534                 try {
535                     BasicEventManager.instance().start();
536                 } catch (ServiceException exception) {
537                     // ignore
538                 }
539             }
540         } catch (Exception exception) {
541             _log.error(exception.getMessage(), exception);
542         }
543     }
544 
545     /***
546      * This method will terminate the event manager service when the last
547      * connection has been closed
548      */
549     private static void stopEventManager() {
550         try {
551             if (--_activeConnections == 0) {
552                 BasicEventManager.instance().stop();
553             }
554         } catch (Exception exception) {
555             exception.printStackTrace();
556         }
557     }
558 
559 } //-- JmsConnection
560