KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > jms > connection > SingleConnectionFactory


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.jms.connection;
18
19 import java.lang.reflect.InvocationHandler JavaDoc;
20 import java.lang.reflect.InvocationTargetException JavaDoc;
21 import java.lang.reflect.Method JavaDoc;
22 import java.lang.reflect.Proxy JavaDoc;
23 import java.util.ArrayList JavaDoc;
24 import java.util.List JavaDoc;
25
26 import javax.jms.Connection JavaDoc;
27 import javax.jms.ConnectionFactory JavaDoc;
28 import javax.jms.ExceptionListener JavaDoc;
29 import javax.jms.JMSException JavaDoc;
30 import javax.jms.QueueConnection JavaDoc;
31 import javax.jms.QueueConnectionFactory JavaDoc;
32 import javax.jms.TopicConnection JavaDoc;
33 import javax.jms.TopicConnectionFactory JavaDoc;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37
38 import org.springframework.beans.factory.DisposableBean;
39 import org.springframework.beans.factory.InitializingBean;
40 import org.springframework.util.Assert;
41
42 /**
43  * A JMS ConnectionFactory adapter that returns the same Connection on all
44  * <code>createConnection</code> calls, and ignores calls to
45  * <code>Connection.close()</code>. According to the JMS Connection model,
46  * this is even thread-safe.
47  *
48  * <p>Useful for testing and standalone environments, to keep using the same
49  * Connection for multiple JmsTemplate calls, without having a pooling
50  * ConnectionFactory, also spanning any number of transactions.
51  *
52  * <p>You can either pass in a JMS Connection directly, or let this
53  * factory lazily create a Connection via a given target ConnectionFactory.
54  * In the latter case, this factory just works with JMS 1.1; use
55  * {@link SingleConnectionFactory102} for JMS 1.0.2.
56  *
57  * @author Mark Pollack
58  * @author Juergen Hoeller
59  * @since 1.1
60  * @see #createConnection()
61  * @see javax.jms.Connection#close()
62  * @see SingleConnectionFactory102
63  * @see org.springframework.jms.core.JmsTemplate
64  */

65 public class SingleConnectionFactory
66         implements ConnectionFactory JavaDoc, QueueConnectionFactory JavaDoc, TopicConnectionFactory JavaDoc, ExceptionListener JavaDoc,
67         InitializingBean, DisposableBean {
68
69     protected final Log logger = LogFactory.getLog(getClass());
70
71     private ConnectionFactory JavaDoc targetConnectionFactory;
72
73     private String JavaDoc clientId;
74
75     private ExceptionListener JavaDoc exceptionListener;
76
77     private boolean reconnectOnException = false;
78
79     /** Wrapped Connection */
80     private Connection JavaDoc target;
81
82     /** Proxy Connection */
83     private Connection JavaDoc connection;
84
85     /** Synchronization monitor for the shared Connection */
86     private final Object JavaDoc connectionMonitor = new Object JavaDoc();
87
88
89     /**
90      * Create a new SingleConnectionFactory for bean-style usage.
91      * @see #setTargetConnectionFactory
92      */

93     public SingleConnectionFactory() {
94     }
95
96     /**
97      * Create a new SingleConnectionFactory that always returns the
98      * given Connection. Works with both JMS 1.1 and 1.0.2.
99      * @param target the single Connection
100      */

101     public SingleConnectionFactory(Connection JavaDoc target) {
102         Assert.notNull(target, "Target Connection must not be null");
103         this.target = target;
104         this.connection = getSharedConnectionProxy(target);
105     }
106
107     /**
108      * Create a new SingleConnectionFactory that always returns a single
109      * Connection that it will lazily create via the given target
110      * ConnectionFactory.
111      * @param targetConnectionFactory the target ConnectionFactory
112      */

113     public SingleConnectionFactory(ConnectionFactory JavaDoc targetConnectionFactory) {
114         Assert.notNull(targetConnectionFactory, "Target ConnectionFactory must not be null");
115         this.targetConnectionFactory = targetConnectionFactory;
116     }
117
118
119     /**
120      * Set the target ConnectionFactory which will be used to lazily
121      * create a single Connection.
122      */

123     public void setTargetConnectionFactory(ConnectionFactory JavaDoc targetConnectionFactory) {
124         this.targetConnectionFactory = targetConnectionFactory;
125     }
126
127     /**
128      * Return the target ConnectionFactory which will be used to lazily
129      * create a single Connection, if any.
130      */

131     public ConnectionFactory JavaDoc getTargetConnectionFactory() {
132         return this.targetConnectionFactory;
133     }
134
135     /**
136      * Specify a JMS client ID for the single Connection created and exposed
137      * by this ConnectionFactory.
138      * <p>Note that client IDs need to be unique among all active Connections
139      * of the underlying JMS provider. Furthermore, a client ID can only be
140      * assigned if the original ConnectionFactory hasn't already assigned one.
141      * @see javax.jms.Connection#setClientID
142      * @see #setTargetConnectionFactory
143      */

144     public void setClientId(String JavaDoc clientId) {
145         this.clientId = clientId;
146     }
147
148     /**
149      * Return a JMS client ID for the single Connection created and exposed
150      * by this ConnectionFactory, if any.
151      */

152     protected String JavaDoc getClientId() {
153         return this.clientId;
154     }
155
156     /**
157      * Specify an JMS ExceptionListener implementation that should be
158      * registered with with the single Connection created by this factory.
159      * @see #setReconnectOnException
160      */

161     public void setExceptionListener(ExceptionListener JavaDoc exceptionListener) {
162         this.exceptionListener = exceptionListener;
163     }
164
165     /**
166      * Return the JMS ExceptionListener implementation that should be registered
167      * with with the single Connection created by this factory, if any.
168      */

169     protected ExceptionListener JavaDoc getExceptionListener() {
170         return this.exceptionListener;
171     }
172
173     /**
174      * Specify whether the single Connection should be reset (to be subsequently renewed)
175      * when a JMSException is reported by the underlying Connection.
176      * <p>Default is "false". Switch this to "true" to automatically trigger
177      * recovery based on your JMS provider's exception notifications.
178      * <p>Internally, this will lead to a special JMS ExceptionListener
179      * (this SingleConnectionFactory itself) being registered with the
180      * underlying Connection. This can also be combined with a
181      * user-specified ExceptionListener, if desired.
182      * @see #setExceptionListener
183      */

184     public void setReconnectOnException(boolean reconnectOnException) {
185         this.reconnectOnException = reconnectOnException;
186     }
187
188     /**
189      * Return whether the single Connection should be renewed when
190      * a JMSException is reported by the underlying Connection.
191      */

192     protected boolean isReconnectOnException() {
193         return this.reconnectOnException;
194     }
195
196     /**
197      * Make sure a Connection or ConnectionFactory has been set.
198      */

199     public void afterPropertiesSet() {
200         if (this.connection == null && getTargetConnectionFactory() == null) {
201             throw new IllegalArgumentException JavaDoc("Connection or 'targetConnectionFactory' is required");
202         }
203     }
204
205
206     public Connection JavaDoc createConnection() throws JMSException JavaDoc {
207         synchronized (this.connectionMonitor) {
208             if (this.connection == null) {
209                 initConnection();
210             }
211             return this.connection;
212         }
213     }
214
215     public Connection JavaDoc createConnection(String JavaDoc username, String JavaDoc password) throws JMSException JavaDoc {
216         throw new javax.jms.IllegalStateException JavaDoc(
217                 "SingleConnectionFactory does not support custom username and password");
218     }
219
220     public QueueConnection JavaDoc createQueueConnection() throws JMSException JavaDoc {
221         Connection JavaDoc con = createConnection();
222         if (!(con instanceof QueueConnection JavaDoc)) {
223             throw new javax.jms.IllegalStateException JavaDoc(
224                     "This SingleConnectionFactory does not hold a QueueConnection but rather: " + con);
225         }
226         return ((QueueConnection JavaDoc) con);
227     }
228
229     public QueueConnection JavaDoc createQueueConnection(String JavaDoc username, String JavaDoc password) throws JMSException JavaDoc {
230         throw new javax.jms.IllegalStateException JavaDoc(
231                 "SingleConnectionFactory does not support custom username and password");
232     }
233
234     public TopicConnection JavaDoc createTopicConnection() throws JMSException JavaDoc {
235         Connection JavaDoc con = createConnection();
236         if (!(con instanceof TopicConnection JavaDoc)) {
237             throw new javax.jms.IllegalStateException JavaDoc(
238                     "This SingleConnectionFactory does not hold a TopicConnection but rather: " + con);
239         }
240         return ((TopicConnection JavaDoc) con);
241     }
242
243     public TopicConnection JavaDoc createTopicConnection(String JavaDoc username, String JavaDoc password) throws JMSException JavaDoc {
244         throw new javax.jms.IllegalStateException JavaDoc(
245                 "SingleConnectionFactory does not support custom username and password");
246     }
247
248     /**
249      * Exception listener callback that renews the underlying single Connection.
250      */

251     public void onException(JMSException JavaDoc ex) {
252         resetConnection();
253     }
254
255     /**
256      * Close the underlying shared connection.
257      * The provider of this ConnectionFactory needs to care for proper shutdown.
258      * <p>As this bean implements DisposableBean, a bean factory will
259      * automatically invoke this on destruction of its cached singletons.
260      */

261     public void destroy() {
262         resetConnection();
263     }
264
265
266     /**
267      * Initialize the underlying shared Connection.
268      * <p>Closes and reinitializes the Connection if an underlying
269      * Connection is present already.
270      * @throws javax.jms.JMSException if thrown by JMS API methods
271      */

272     public void initConnection() throws JMSException JavaDoc {
273         if (getTargetConnectionFactory() == null) {
274             throw new IllegalStateException JavaDoc(
275                     "'targetConnectionFactory' is required for lazily initializing a Connection");
276         }
277         synchronized (this.connectionMonitor) {
278             if (this.target != null) {
279                 closeConnection(this.target);
280             }
281             this.target = doCreateConnection();
282             prepareConnection(this.target);
283             if (logger.isInfoEnabled()) {
284                 logger.info("Established shared JMS Connection: " + this.target);
285             }
286             this.connection = getSharedConnectionProxy(this.target);
287         }
288     }
289
290     /**
291      * Reset the underlying shared Connection, to be reinitialized on next access.
292      */

293     public void resetConnection() {
294         synchronized (this.connectionMonitor) {
295             if (this.target != null) {
296                 closeConnection(this.target);
297             }
298             this.target = null;
299             this.connection = null;
300         }
301     }
302
303     /**
304      * Create a JMS Connection via this template's ConnectionFactory.
305      * <p>This implementation uses JMS 1.1 API.
306      * @return the new JMS Connection
307      * @throws javax.jms.JMSException if thrown by JMS API methods
308      */

309     protected Connection JavaDoc doCreateConnection() throws JMSException JavaDoc {
310         return getTargetConnectionFactory().createConnection();
311     }
312
313     /**
314      * Prepare the given Connection before it is exposed.
315      * <p>The default implementation applies ExceptionListener and client id.
316      * Can be overridden in subclasses.
317      * @param con the Connection to prepare
318      * @throws JMSException if thrown by JMS API methods
319      * @see #setExceptionListener
320      * @see #setReconnectOnException
321      */

322     protected void prepareConnection(Connection JavaDoc con) throws JMSException JavaDoc {
323         if (getExceptionListener() != null || isReconnectOnException()) {
324             ExceptionListener JavaDoc listenerToUse = getExceptionListener();
325             if (isReconnectOnException()) {
326                 listenerToUse = new InternalChainedExceptionListener(this, listenerToUse);
327             }
328             con.setExceptionListener(listenerToUse);
329         }
330         if (getClientId() != null) {
331             con.setClientID(getClientId());
332         }
333     }
334
335     /**
336      * Close the given Connection.
337      * @param con the Connection to close
338      */

339     protected void closeConnection(Connection JavaDoc con) {
340         try {
341             try {
342                 con.stop();
343             }
344             finally {
345                 con.close();
346             }
347         }
348         catch (Throwable JavaDoc ex) {
349             logger.warn("Could not close shared JMS Connection", ex);
350         }
351     }
352
353     /**
354      * Wrap the given Connection with a proxy that delegates every method call to it
355      * but suppresses close calls. This is useful for allowing application code to
356      * handle a special framework Connection just like an ordinary Connection from a
357      * JMS ConnectionFactory.
358      * @param target the original Connection to wrap
359      * @return the wrapped Connection
360      */

361     protected Connection JavaDoc getSharedConnectionProxy(Connection JavaDoc target) {
362         List JavaDoc classes = new ArrayList JavaDoc(3);
363         classes.add(Connection JavaDoc.class);
364         if (target instanceof QueueConnection JavaDoc) {
365             classes.add(QueueConnection JavaDoc.class);
366         }
367         if (target instanceof TopicConnection JavaDoc) {
368             classes.add(TopicConnection JavaDoc.class);
369         }
370         return (Connection JavaDoc) Proxy.newProxyInstance(
371                 getClass().getClassLoader(),
372                 (Class JavaDoc[]) classes.toArray(new Class JavaDoc[classes.size()]),
373                 new SharedConnectionInvocationHandler(target));
374     }
375
376
377     /**
378      * Invocation handler that suppresses close calls on JMS Connections.
379      */

380     private static class SharedConnectionInvocationHandler implements InvocationHandler JavaDoc {
381
382         private final Connection JavaDoc target;
383
384         private SharedConnectionInvocationHandler(Connection JavaDoc target) {
385             this.target = target;
386         }
387
388         public Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args) throws Throwable JavaDoc {
389             if (method.getName().equals("equals")) {
390                 // Only consider equal when proxies are identical.
391
return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
392             }
393             else if (method.getName().equals("hashCode")) {
394                 // Use hashCode of Connection proxy.
395
return new Integer JavaDoc(hashCode());
396             }
397             else if (method.getName().equals("setExceptionListener")) {
398                 // Handle setExceptionListener method: throw exception.
399
throw new javax.jms.IllegalStateException JavaDoc(
400                         "setExceptionListener call not supported on proxy for shared Connection. " +
401                         "Set the 'exceptionListener' property on the SingleConnectionFactory instead.");
402             }
403             else if (method.getName().equals("setClientID")) {
404                 // Handle setExceptionListener method: throw exception.
405
throw new javax.jms.IllegalStateException JavaDoc(
406                         "setClientID call not supported on proxy for shared Connection. " +
407                         "Set the 'clientId' property on the SingleConnectionFactory instead.");
408             }
409             else if (method.getName().equals("stop")) {
410                 // Handle stop method: don't pass the call on.
411
return null;
412             }
413             else if (method.getName().equals("close")) {
414                 // Handle close method: don't pass the call on.
415
return null;
416             }
417             try {
418                 Object JavaDoc retVal = method.invoke(this.target, args);
419                 if (method.getName().equals("getExceptionListener") && retVal instanceof InternalChainedExceptionListener) {
420                     // Handle getExceptionListener method: hide internal chain.
421
InternalChainedExceptionListener listener = (InternalChainedExceptionListener) retVal;
422                     return listener.getUserListener();
423                 }
424                 else {
425                     return retVal;
426                 }
427             }
428             catch (InvocationTargetException JavaDoc ex) {
429                 throw ex.getTargetException();
430             }
431         }
432     }
433
434
435     /**
436      * Internal chained ExceptionListener for handling the internal recovery listener
437      * in combination with a user-specified listener.
438      */

439     private static class InternalChainedExceptionListener extends ChainedExceptionListener {
440
441         public InternalChainedExceptionListener(ExceptionListener JavaDoc internalListener, ExceptionListener JavaDoc userListener) {
442             addDelegate(internalListener);
443             if (userListener != null) {
444                 addDelegate(userListener);
445             }
446         }
447
448         public ExceptionListener JavaDoc getUserListener() {
449             ExceptionListener JavaDoc[] delegates = getDelegates();
450             return (delegates.length > 1 ? delegates[1] : null);
451         }
452     }
453
454 }
455
Popular Tags