1   /*
2    * Copyright 2002-2012 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.jdbc.datasource.lookup;
18  
19  import java.sql.Connection;
20  import java.sql.SQLException;
21  import java.util.HashMap;
22  import java.util.Map;
23  import javax.sql.DataSource;
24  
25  import org.springframework.beans.factory.InitializingBean;
26  import org.springframework.jdbc.datasource.AbstractDataSource;
27  import org.springframework.util.Assert;
28  
29  /**
30   * Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
31   * calls to one of various target DataSources based on a lookup key. The latter is usually
32   * (but not necessarily) determined through some thread-bound transaction context.
33   *
34   * @author Juergen Hoeller
35   * @since 2.0.1
36   * @see #setTargetDataSources
37   * @see #setDefaultTargetDataSource
38   * @see #determineCurrentLookupKey()
39   */
40  public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
41  
42  	private Map<Object, Object> targetDataSources;
43  
44  	private Object defaultTargetDataSource;
45  
46  	private boolean lenientFallback = true;
47  
48  	private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
49  
50  	private Map<Object, DataSource> resolvedDataSources;
51  
52  	private DataSource resolvedDefaultDataSource;
53  
54  
55  	/**
56  	 * Specify the map of target DataSources, with the lookup key as key.
57  	 * The mapped value can either be a corresponding {@link javax.sql.DataSource}
58  	 * instance or a data source name String (to be resolved via a
59  	 * {@link #setDataSourceLookup DataSourceLookup}).
60  	 * <p>The key can be of arbitrary type; this class implements the
61  	 * generic lookup process only. The concrete key representation will
62  	 * be handled by {@link #resolveSpecifiedLookupKey(Object)} and
63  	 * {@link #determineCurrentLookupKey()}.
64  	 */
65  	public void setTargetDataSources(Map<Object, Object> targetDataSources) {
66  		this.targetDataSources = targetDataSources;
67  	}
68  
69  	/**
70  	 * Specify the default target DataSource, if any.
71  	 * <p>The mapped value can either be a corresponding {@link javax.sql.DataSource}
72  	 * instance or a data source name String (to be resolved via a
73  	 * {@link #setDataSourceLookup DataSourceLookup}).
74  	 * <p>This DataSource will be used as target if none of the keyed
75  	 * {@link #setTargetDataSources targetDataSources} match the
76  	 * {@link #determineCurrentLookupKey()} current lookup key.
77  	 */
78  	public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
79  		this.defaultTargetDataSource = defaultTargetDataSource;
80  	}
81  
82  	/**
83  	 * Specify whether to apply a lenient fallback to the default DataSource
84  	 * if no specific DataSource could be found for the current lookup key.
85  	 * <p>Default is "true", accepting lookup keys without a corresponding entry
86  	 * in the target DataSource map - simply falling back to the default DataSource
87  	 * in that case.
88  	 * <p>Switch this flag to "false" if you would prefer the fallback to only apply
89  	 * if the lookup key was {@code null}. Lookup keys without a DataSource
90  	 * entry will then lead to an IllegalStateException.
91  	 * @see #setTargetDataSources
92  	 * @see #setDefaultTargetDataSource
93  	 * @see #determineCurrentLookupKey()
94  	 */
95  	public void setLenientFallback(boolean lenientFallback) {
96  		this.lenientFallback = lenientFallback;
97  	}
98  
99  	/**
100 	 * Set the DataSourceLookup implementation to use for resolving data source
101 	 * name Strings in the {@link #setTargetDataSources targetDataSources} map.
102 	 * <p>Default is a {@link JndiDataSourceLookup}, allowing the JNDI names
103 	 * of application server DataSources to be specified directly.
104 	 */
105 	public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
106 		this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
107 	}
108 
109 
110 	@Override
111 	public void afterPropertiesSet() {
112 		if (this.targetDataSources == null) {
113 			throw new IllegalArgumentException("Property 'targetDataSources' is required");
114 		}
115 		this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
116 		for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
117 			Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
118 			DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
119 			this.resolvedDataSources.put(lookupKey, dataSource);
120 		}
121 		if (this.defaultTargetDataSource != null) {
122 			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
123 		}
124 	}
125 
126 	/**
127 	 * Resolve the given lookup key object, as specified in the
128 	 * {@link #setTargetDataSources targetDataSources} map, into
129 	 * the actual lookup key to be used for matching with the
130 	 * {@link #determineCurrentLookupKey() current lookup key}.
131 	 * <p>The default implementation simply returns the given key as-is.
132 	 * @param lookupKey the lookup key object as specified by the user
133 	 * @return the lookup key as needed for matching
134 	 */
135 	protected Object resolveSpecifiedLookupKey(Object lookupKey) {
136 		return lookupKey;
137 	}
138 
139 	/**
140 	 * Resolve the specified data source object into a DataSource instance.
141 	 * <p>The default implementation handles DataSource instances and data source
142 	 * names (to be resolved via a {@link #setDataSourceLookup DataSourceLookup}).
143 	 * @param dataSource the data source value object as specified in the
144 	 * {@link #setTargetDataSources targetDataSources} map
145 	 * @return the resolved DataSource (never {@code null})
146 	 * @throws IllegalArgumentException in case of an unsupported value type
147 	 */
148 	protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
149 		if (dataSource instanceof DataSource) {
150 			return (DataSource) dataSource;
151 		}
152 		else if (dataSource instanceof String) {
153 			return this.dataSourceLookup.getDataSource((String) dataSource);
154 		}
155 		else {
156 			throw new IllegalArgumentException(
157 					"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
158 		}
159 	}
160 
161 
162 	@Override
163 	public Connection getConnection() throws SQLException {
164 		return determineTargetDataSource().getConnection();
165 	}
166 
167 	@Override
168 	public Connection getConnection(String username, String password) throws SQLException {
169 		return determineTargetDataSource().getConnection(username, password);
170 	}
171 
172 	@Override
173 	@SuppressWarnings("unchecked")
174 	public <T> T unwrap(Class<T> iface) throws SQLException {
175 		if (iface.isInstance(this)) {
176 			return (T) this;
177 		}
178 		return determineTargetDataSource().unwrap(iface);
179 	}
180 
181 	@Override
182 	public boolean isWrapperFor(Class<?> iface) throws SQLException {
183 		return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
184 	}
185 
186 	/**
187 	 * Retrieve the current target DataSource. Determines the
188 	 * {@link #determineCurrentLookupKey() current lookup key}, performs
189 	 * a lookup in the {@link #setTargetDataSources targetDataSources} map,
190 	 * falls back to the specified
191 	 * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
192 	 * @see #determineCurrentLookupKey()
193 	 */
194 	protected DataSource determineTargetDataSource() {
195 		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
196 		Object lookupKey = determineCurrentLookupKey();
197 		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
198 		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
199 			dataSource = this.resolvedDefaultDataSource;
200 		}
201 		if (dataSource == null) {
202 			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
203 		}
204 		return dataSource;
205 	}
206 
207 	/**
208 	 * Determine the current lookup key. This will typically be
209 	 * implemented to check a thread-bound transaction context.
210 	 * <p>Allows for arbitrary keys. The returned key needs
211 	 * to match the stored lookup key type, as resolved by the
212 	 * {@link #resolveSpecifiedLookupKey} method.
213 	 */
214 	protected abstract Object determineCurrentLookupKey();
215 
216 }