001/* 002 * Copyright 2002-2018 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.jdbc.datasource; 018 019import java.sql.Connection; 020import java.sql.SQLException; 021 022import org.springframework.core.NamedThreadLocal; 023import org.springframework.lang.Nullable; 024import org.springframework.util.Assert; 025import org.springframework.util.StringUtils; 026 027/** 028 * An adapter for a target JDBC {@link javax.sql.DataSource}, applying the specified 029 * user credentials to every standard {@code getConnection()} call, implicitly 030 * invoking {@code getConnection(username, password)} on the target. 031 * All other methods simply delegate to the corresponding methods of the 032 * target DataSource. 033 * 034 * <p>Can be used to proxy a target JNDI DataSource that does not have user 035 * credentials configured. Client code can work with this DataSource as usual, 036 * using the standard {@code getConnection()} call. 037 * 038 * <p>In the following example, client code can simply transparently work with 039 * the preconfigured "myDataSource", implicitly accessing "myTargetDataSource" 040 * with the specified user credentials. 041 * 042 * <pre class="code"> 043 * <bean id="myTargetDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> 044 * <property name="jndiName" value="java:comp/env/jdbc/myds"/> 045 * </bean> 046 * 047 * <bean id="myDataSource" class="org.springframework.jdbc.datasource.UserCredentialsDataSourceAdapter"> 048 * <property name="targetDataSource" ref="myTargetDataSource"/> 049 * <property name="username" value="myusername"/> 050 * <property name="password" value="mypassword"/> 051 * </bean></pre> 052 * 053 * <p>If the "username" is empty, this proxy will simply delegate to the 054 * standard {@code getConnection()} method of the target DataSource. 055 * This can be used to keep a UserCredentialsDataSourceAdapter bean definition 056 * just for the <i>option</i> of implicitly passing in user credentials if 057 * the particular target DataSource requires it. 058 * 059 * @author Juergen Hoeller 060 * @since 1.0.2 061 * @see #getConnection 062 */ 063public class UserCredentialsDataSourceAdapter extends DelegatingDataSource { 064 065 @Nullable 066 private String username; 067 068 @Nullable 069 private String password; 070 071 @Nullable 072 private String catalog; 073 074 @Nullable 075 private String schema; 076 077 private final ThreadLocal<JdbcUserCredentials> threadBoundCredentials = 078 new NamedThreadLocal<>("Current JDBC user credentials"); 079 080 081 /** 082 * Set the default username that this adapter should use for retrieving Connections. 083 * <p>Default is no specific user. Note that an explicitly specified username 084 * will always override any username/password specified at the DataSource level. 085 * @see #setPassword 086 * @see #setCredentialsForCurrentThread(String, String) 087 * @see #getConnection(String, String) 088 */ 089 public void setUsername(String username) { 090 this.username = username; 091 } 092 093 /** 094 * Set the default user's password that this adapter should use for retrieving Connections. 095 * <p>Default is no specific password. Note that an explicitly specified username 096 * will always override any username/password specified at the DataSource level. 097 * @see #setUsername 098 * @see #setCredentialsForCurrentThread(String, String) 099 * @see #getConnection(String, String) 100 */ 101 public void setPassword(String password) { 102 this.password = password; 103 } 104 105 /** 106 * Specify a database catalog to be applied to each retrieved Connection. 107 * @since 4.3.2 108 * @see Connection#setCatalog 109 */ 110 public void setCatalog(String catalog) { 111 this.catalog = catalog; 112 } 113 114 /** 115 * Specify a database schema to be applied to each retrieved Connection. 116 * @since 4.3.2 117 * @see Connection#setSchema 118 */ 119 public void setSchema(String schema) { 120 this.schema = schema; 121 } 122 123 124 /** 125 * Set user credententials for this proxy and the current thread. 126 * The given username and password will be applied to all subsequent 127 * {@code getConnection()} calls on this DataSource proxy. 128 * <p>This will override any statically specified user credentials, 129 * that is, values of the "username" and "password" bean properties. 130 * @param username the username to apply 131 * @param password the password to apply 132 * @see #removeCredentialsFromCurrentThread 133 */ 134 public void setCredentialsForCurrentThread(String username, String password) { 135 this.threadBoundCredentials.set(new JdbcUserCredentials(username, password)); 136 } 137 138 /** 139 * Remove any user credentials for this proxy from the current thread. 140 * Statically specified user credentials apply again afterwards. 141 * @see #setCredentialsForCurrentThread 142 */ 143 public void removeCredentialsFromCurrentThread() { 144 this.threadBoundCredentials.remove(); 145 } 146 147 148 /** 149 * Determine whether there are currently thread-bound credentials, 150 * using them if available, falling back to the statically specified 151 * username and password (i.e. values of the bean properties) otherwise. 152 * <p>Delegates to {@link #doGetConnection(String, String)} with the 153 * determined credentials as parameters. 154 * @see #doGetConnection 155 */ 156 @Override 157 public Connection getConnection() throws SQLException { 158 JdbcUserCredentials threadCredentials = this.threadBoundCredentials.get(); 159 Connection con = (threadCredentials != null ? 160 doGetConnection(threadCredentials.username, threadCredentials.password) : 161 doGetConnection(this.username, this.password)); 162 163 if (this.catalog != null) { 164 con.setCatalog(this.catalog); 165 } 166 if (this.schema != null) { 167 con.setSchema(this.schema); 168 } 169 return con; 170 } 171 172 /** 173 * Simply delegates to {@link #doGetConnection(String, String)}, 174 * keeping the given user credentials as-is. 175 */ 176 @Override 177 public Connection getConnection(String username, String password) throws SQLException { 178 return doGetConnection(username, password); 179 } 180 181 /** 182 * This implementation delegates to the {@code getConnection(username, password)} 183 * method of the target DataSource, passing in the specified user credentials. 184 * If the specified username is empty, it will simply delegate to the standard 185 * {@code getConnection()} method of the target DataSource. 186 * @param username the username to use 187 * @param password the password to use 188 * @return the Connection 189 * @see javax.sql.DataSource#getConnection(String, String) 190 * @see javax.sql.DataSource#getConnection() 191 */ 192 protected Connection doGetConnection(@Nullable String username, @Nullable String password) throws SQLException { 193 Assert.state(getTargetDataSource() != null, "'targetDataSource' is required"); 194 if (StringUtils.hasLength(username)) { 195 return getTargetDataSource().getConnection(username, password); 196 } 197 else { 198 return getTargetDataSource().getConnection(); 199 } 200 } 201 202 203 /** 204 * Inner class used as ThreadLocal value. 205 */ 206 private static final class JdbcUserCredentials { 207 208 public final String username; 209 210 public final String password; 211 212 public JdbcUserCredentials(String username, String password) { 213 this.username = username; 214 this.password = password; 215 } 216 217 @Override 218 public String toString() { 219 return "JdbcUserCredentials[username='" + this.username + "',password='" + this.password + "']"; 220 } 221 } 222 223}