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 * &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        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}