001/*
002 * Copyright 2002-2012 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jms.connection;
018
019import javax.jms.Connection;
020import javax.jms.ConnectionFactory;
021import javax.jms.JMSException;
022import javax.jms.QueueConnection;
023import javax.jms.QueueConnectionFactory;
024import javax.jms.TopicConnection;
025import javax.jms.TopicConnectionFactory;
026
027import org.springframework.beans.factory.InitializingBean;
028import org.springframework.core.NamedThreadLocal;
029import org.springframework.util.Assert;
030import org.springframework.util.StringUtils;
031
032/**
033 * An adapter for a target JMS {@link javax.jms.ConnectionFactory}, applying the
034 * given user credentials to every standard {@code createConnection()} call,
035 * that is, implicitly invoking {@code createConnection(username, password)}
036 * on the target. All other methods simply delegate to the corresponding methods
037 * of the target ConnectionFactory.
038 *
039 * <p>Can be used to proxy a target JNDI ConnectionFactory that does not have user
040 * credentials configured. Client code can work with the ConnectionFactory without
041 * passing in username and password on every {@code createConnection()} call.
042 *
043 * <p>In the following example, client code can simply transparently work
044 * with the preconfigured "myConnectionFactory", implicitly accessing
045 * "myTargetConnectionFactory" with the specified user credentials.
046 *
047 * <pre class="code">
048 * &lt;bean id="myTargetConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean"&gt;
049 *   &lt;property name="jndiName" value="java:comp/env/jms/mycf"/&gt;
050 * &lt;/bean&gt;
051 *
052 * &lt;bean id="myConnectionFactory" class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter"&gt;
053 *   &lt;property name="targetConnectionFactory" ref="myTargetConnectionFactory"/&gt;
054 *   &lt;property name="username" value="myusername"/&gt;
055 *   &lt;property name="password" value="mypassword"/&gt;
056 * &lt;/bean></pre>
057 *
058 * <p>If the "username" is empty, this proxy will simply delegate to the standard
059 * {@code createConnection()} method of the target ConnectionFactory.
060 * This can be used to keep a UserCredentialsConnectionFactoryAdapter bean
061 * definition just for the <i>option</i> of implicitly passing in user credentials
062 * if the particular target ConnectionFactory requires it.
063 *
064 * @author Juergen Hoeller
065 * @since 1.2
066 * @see #createConnection
067 * @see #createQueueConnection
068 * @see #createTopicConnection
069 */
070public class UserCredentialsConnectionFactoryAdapter
071                implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, InitializingBean {
072
073        private ConnectionFactory targetConnectionFactory;
074
075        private String username;
076
077        private String password;
078
079        private final ThreadLocal<JmsUserCredentials> threadBoundCredentials =
080                        new NamedThreadLocal<JmsUserCredentials>("Current JMS user credentials");
081
082
083        /**
084         * Set the target ConnectionFactory that this ConnectionFactory should delegate to.
085         */
086        public void setTargetConnectionFactory(ConnectionFactory targetConnectionFactory) {
087                Assert.notNull(targetConnectionFactory, "'targetConnectionFactory' must not be null");
088                this.targetConnectionFactory = targetConnectionFactory;
089        }
090
091        /**
092         * Set the username that this adapter should use for retrieving Connections.
093         * Default is no specific user.
094         */
095        public void setUsername(String username) {
096                this.username = username;
097        }
098
099        /**
100         * Set the password that this adapter should use for retrieving Connections.
101         * Default is no specific password.
102         */
103        public void setPassword(String password) {
104                this.password = password;
105        }
106
107        @Override
108        public void afterPropertiesSet() {
109                if (this.targetConnectionFactory == null) {
110                        throw new IllegalArgumentException("Property 'targetConnectionFactory' is required");
111                }
112        }
113
114
115        /**
116         * Set user credententials for this proxy and the current thread.
117         * The given username and password will be applied to all subsequent
118         * {@code createConnection()} calls on this ConnectionFactory proxy.
119         * <p>This will override any statically specified user credentials,
120         * that is, values of the "username" and "password" bean properties.
121         * @param username the username to apply
122         * @param password the password to apply
123         * @see #removeCredentialsFromCurrentThread
124         */
125        public void setCredentialsForCurrentThread(String username, String password) {
126                this.threadBoundCredentials.set(new JmsUserCredentials(username, password));
127        }
128
129        /**
130         * Remove any user credentials for this proxy from the current thread.
131         * Statically specified user credentials apply again afterwards.
132         * @see #setCredentialsForCurrentThread
133         */
134        public void removeCredentialsFromCurrentThread() {
135                this.threadBoundCredentials.remove();
136        }
137
138
139        /**
140         * Determine whether there are currently thread-bound credentials,
141         * using them if available, falling back to the statically specified
142         * username and password (i.e. values of the bean properties) else.
143         * @see #doCreateConnection
144         */
145        @Override
146        public final Connection createConnection() throws JMSException {
147                JmsUserCredentials threadCredentials = this.threadBoundCredentials.get();
148                if (threadCredentials != null) {
149                        return doCreateConnection(threadCredentials.username, threadCredentials.password);
150                }
151                else {
152                        return doCreateConnection(this.username, this.password);
153                }
154        }
155
156        /**
157         * Delegate the call straight to the target ConnectionFactory.
158         */
159        @Override
160        public Connection createConnection(String username, String password) throws JMSException {
161                return doCreateConnection(username, password);
162        }
163
164        /**
165         * This implementation delegates to the {@code createConnection(username, password)}
166         * method of the target ConnectionFactory, passing in the specified user credentials.
167         * If the specified username is empty, it will simply delegate to the standard
168         * {@code createConnection()} method of the target ConnectionFactory.
169         * @param username the username to use
170         * @param password the password to use
171         * @return the Connection
172         * @see javax.jms.ConnectionFactory#createConnection(String, String)
173         * @see javax.jms.ConnectionFactory#createConnection()
174         */
175        protected Connection doCreateConnection(String username, String password) throws JMSException {
176                Assert.state(this.targetConnectionFactory != null, "'targetConnectionFactory' is required");
177                if (StringUtils.hasLength(username)) {
178                        return this.targetConnectionFactory.createConnection(username, password);
179                }
180                else {
181                        return this.targetConnectionFactory.createConnection();
182                }
183        }
184
185
186        /**
187         * Determine whether there are currently thread-bound credentials,
188         * using them if available, falling back to the statically specified
189         * username and password (i.e. values of the bean properties) else.
190         * @see #doCreateQueueConnection
191         */
192        @Override
193        public final QueueConnection createQueueConnection() throws JMSException {
194                JmsUserCredentials threadCredentials = this.threadBoundCredentials.get();
195                if (threadCredentials != null) {
196                        return doCreateQueueConnection(threadCredentials.username, threadCredentials.password);
197                }
198                else {
199                        return doCreateQueueConnection(this.username, this.password);
200                }
201        }
202
203        /**
204         * Delegate the call straight to the target QueueConnectionFactory.
205         */
206        @Override
207        public QueueConnection createQueueConnection(String username, String password) throws JMSException {
208                return doCreateQueueConnection(username, password);
209        }
210
211        /**
212         * This implementation delegates to the {@code createQueueConnection(username, password)}
213         * method of the target QueueConnectionFactory, passing in the specified user credentials.
214         * If the specified username is empty, it will simply delegate to the standard
215         * {@code createQueueConnection()} method of the target ConnectionFactory.
216         * @param username the username to use
217         * @param password the password to use
218         * @return the Connection
219         * @see javax.jms.QueueConnectionFactory#createQueueConnection(String, String)
220         * @see javax.jms.QueueConnectionFactory#createQueueConnection()
221         */
222        protected QueueConnection doCreateQueueConnection(String username, String password) throws JMSException {
223                Assert.state(this.targetConnectionFactory != null, "'targetConnectionFactory' is required");
224                if (!(this.targetConnectionFactory instanceof QueueConnectionFactory)) {
225                        throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a QueueConnectionFactory");
226                }
227                QueueConnectionFactory queueFactory = (QueueConnectionFactory) this.targetConnectionFactory;
228                if (StringUtils.hasLength(username)) {
229                        return queueFactory.createQueueConnection(username, password);
230                }
231                else {
232                        return queueFactory.createQueueConnection();
233                }
234        }
235
236
237        /**
238         * Determine whether there are currently thread-bound credentials,
239         * using them if available, falling back to the statically specified
240         * username and password (i.e. values of the bean properties) else.
241         * @see #doCreateTopicConnection
242         */
243        @Override
244        public final TopicConnection createTopicConnection() throws JMSException {
245                JmsUserCredentials threadCredentials = this.threadBoundCredentials.get();
246                if (threadCredentials != null) {
247                        return doCreateTopicConnection(threadCredentials.username, threadCredentials.password);
248                }
249                else {
250                        return doCreateTopicConnection(this.username, this.password);
251                }
252        }
253
254        /**
255         * Delegate the call straight to the target TopicConnectionFactory.
256         */
257        @Override
258        public TopicConnection createTopicConnection(String username, String password) throws JMSException {
259                return doCreateTopicConnection(username, password);
260        }
261
262        /**
263         * This implementation delegates to the {@code createTopicConnection(username, password)}
264         * method of the target TopicConnectionFactory, passing in the specified user credentials.
265         * If the specified username is empty, it will simply delegate to the standard
266         * {@code createTopicConnection()} method of the target ConnectionFactory.
267         * @param username the username to use
268         * @param password the password to use
269         * @return the Connection
270         * @see javax.jms.TopicConnectionFactory#createTopicConnection(String, String)
271         * @see javax.jms.TopicConnectionFactory#createTopicConnection()
272         */
273        protected TopicConnection doCreateTopicConnection(String username, String password) throws JMSException {
274                Assert.state(this.targetConnectionFactory != null, "'targetConnectionFactory' is required");
275                if (!(this.targetConnectionFactory instanceof TopicConnectionFactory)) {
276                        throw new javax.jms.IllegalStateException("'targetConnectionFactory' is not a TopicConnectionFactory");
277                }
278                TopicConnectionFactory queueFactory = (TopicConnectionFactory) this.targetConnectionFactory;
279                if (StringUtils.hasLength(username)) {
280                        return queueFactory.createTopicConnection(username, password);
281                }
282                else {
283                        return queueFactory.createTopicConnection();
284                }
285        }
286
287
288        /**
289         * Inner class used as ThreadLocal value.
290         */
291        private static class JmsUserCredentials {
292
293                public final String username;
294
295                public final String password;
296
297                private JmsUserCredentials(String username, String password) {
298                        this.username = username;
299                        this.password = password;
300                }
301
302                @Override
303                public String toString() {
304                        return "JmsUserCredentials[username='" + this.username + "',password='" + this.password + "']";
305                }
306        }
307
308}