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 * &lt;bean id="myTargetDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"&gt;
044 *   &lt;property name="jndiName" value="java:comp/env/jdbc/myds"/&gt;
045 * &lt;/bean&gt;
046 *
047 * &lt;bean id="myDataSource" class="org.springframework.jdbc.datasource.UserCredentialsDataSourceAdapter"&gt;
048 *   &lt;property name="targetDataSource" ref="myTargetDataSource"/&gt;
049 *   &lt;property name="username" value="myusername"/&gt;
050 *   &lt;property name="password" value="mypassword"/&gt;
051 * &lt;/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}