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 * <bean id="myTargetConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean"> 049 * <property name="jndiName" value="java:comp/env/jms/mycf"/> 050 * </bean> 051 * 052 * <bean id="myConnectionFactory" class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter"> 053 * <property name="targetConnectionFactory" ref="myTargetConnectionFactory"/> 054 * <property name="username" value="myusername"/> 055 * <property name="password" value="mypassword"/> 056 * </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}