001/* 002 * Copyright 2002-2016 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.UsesJava7; 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 private String username; 066 067 private String password; 068 069 private String catalog; 070 071 private String schema; 072 073 private final ThreadLocal<JdbcUserCredentials> threadBoundCredentials = 074 new NamedThreadLocal<JdbcUserCredentials>("Current JDBC user credentials"); 075 076 077 /** 078 * Set the default username that this adapter should use for retrieving Connections. 079 * <p>Default is no specific user. Note that an explicitly specified username 080 * will always override any username/password specified at the DataSource level. 081 * @see #setPassword 082 * @see #setCredentialsForCurrentThread(String, String) 083 * @see #getConnection(String, String) 084 */ 085 public void setUsername(String username) { 086 this.username = username; 087 } 088 089 /** 090 * Set the default user's password that this adapter should use for retrieving Connections. 091 * <p>Default is no specific password. Note that an explicitly specified username 092 * will always override any username/password specified at the DataSource level. 093 * @see #setUsername 094 * @see #setCredentialsForCurrentThread(String, String) 095 * @see #getConnection(String, String) 096 */ 097 public void setPassword(String password) { 098 this.password = password; 099 } 100 101 /** 102 * Specify a database catalog to be applied to each retrieved Connection. 103 * @since 4.3.2 104 * @see Connection#setCatalog 105 */ 106 public void setCatalog(String catalog) { 107 this.catalog = catalog; 108 } 109 110 /** 111 * Specify a database schema to be applied to each retrieved Connection. 112 * @since 4.3.2 113 * @see Connection#setSchema 114 */ 115 public void setSchema(String schema) { 116 this.schema = schema; 117 } 118 119 120 /** 121 * Set user credententials for this proxy and the current thread. 122 * The given username and password will be applied to all subsequent 123 * {@code getConnection()} calls on this DataSource proxy. 124 * <p>This will override any statically specified user credentials, 125 * that is, values of the "username" and "password" bean properties. 126 * @param username the username to apply 127 * @param password the password to apply 128 * @see #removeCredentialsFromCurrentThread 129 */ 130 public void setCredentialsForCurrentThread(String username, String password) { 131 this.threadBoundCredentials.set(new JdbcUserCredentials(username, password)); 132 } 133 134 /** 135 * Remove any user credentials for this proxy from the current thread. 136 * Statically specified user credentials apply again afterwards. 137 * @see #setCredentialsForCurrentThread 138 */ 139 public void removeCredentialsFromCurrentThread() { 140 this.threadBoundCredentials.remove(); 141 } 142 143 144 /** 145 * Determine whether there are currently thread-bound credentials, 146 * using them if available, falling back to the statically specified 147 * username and password (i.e. values of the bean properties) else. 148 * <p>Delegates to {@link #doGetConnection(String, String)} with the 149 * determined credentials as parameters. 150 */ 151 @Override 152 @UsesJava7 153 public Connection getConnection() throws SQLException { 154 JdbcUserCredentials threadCredentials = this.threadBoundCredentials.get(); 155 Connection con = (threadCredentials != null ? 156 doGetConnection(threadCredentials.username, threadCredentials.password) : 157 doGetConnection(this.username, this.password)); 158 159 if (this.catalog != null) { 160 con.setCatalog(this.catalog); 161 } 162 if (this.schema != null) { 163 con.setSchema(this.schema); 164 } 165 return con; 166 } 167 168 /** 169 * Simply delegates to {@link #doGetConnection(String, String)}, 170 * keeping the given user credentials as-is. 171 */ 172 @Override 173 public Connection getConnection(String username, String password) throws SQLException { 174 return doGetConnection(username, password); 175 } 176 177 /** 178 * This implementation delegates to the {@code getConnection(username, password)} 179 * method of the target DataSource, passing in the specified user credentials. 180 * If the specified username is empty, it will simply delegate to the standard 181 * {@code getConnection()} method of the target DataSource. 182 * @param username the username to use 183 * @param password the password to use 184 * @return the Connection 185 * @see javax.sql.DataSource#getConnection(String, String) 186 * @see javax.sql.DataSource#getConnection() 187 */ 188 protected Connection doGetConnection(String username, String password) throws SQLException { 189 Assert.state(getTargetDataSource() != null, "'targetDataSource' is required"); 190 if (StringUtils.hasLength(username)) { 191 return getTargetDataSource().getConnection(username, password); 192 } 193 else { 194 return getTargetDataSource().getConnection(); 195 } 196 } 197 198 199 /** 200 * Inner class used as ThreadLocal value. 201 */ 202 private static class JdbcUserCredentials { 203 204 public final String username; 205 206 public final String password; 207 208 private JdbcUserCredentials(String username, String password) { 209 this.username = username; 210 this.password = password; 211 } 212 213 @Override 214 public String toString() { 215 return "JdbcUserCredentials[username='" + this.username + "',password='" + this.password + "']"; 216 } 217 } 218 219}