001: /*
002: * Copyright 2002-2006 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: * http://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:
017: package org.springframework.jdbc.datasource.lookup;
018:
019: import java.sql.Connection;
020: import java.sql.SQLException;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.Map;
024:
025: import javax.sql.DataSource;
026:
027: import org.springframework.beans.factory.InitializingBean;
028: import org.springframework.jdbc.datasource.AbstractDataSource;
029: import org.springframework.util.Assert;
030:
031: /**
032: * Abstract DataSource implementation that routes {@link #getConnection()} calls
033: * to one of various target DataSources based on a lookup key. The latter is usually
034: * (but not necessarily) determined through some thread-bound transaction context.
035: *
036: * @author Juergen Hoeller
037: * @since 2.0.1
038: * @see #setTargetDataSources
039: * @see #setDefaultTargetDataSource
040: * @see #determineCurrentLookupKey()
041: */
042: public abstract class AbstractRoutingDataSource extends
043: AbstractDataSource implements InitializingBean {
044:
045: private Map targetDataSources;
046:
047: private Object defaultTargetDataSource;
048:
049: private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
050:
051: private Map resolvedDataSources;
052:
053: private DataSource resolvedDefaultDataSource;
054:
055: /**
056: * Specify the map of target DataSources, with the lookup key as key.
057: * The mapped value can either be a corresponding {@link javax.sql.DataSource}
058: * instance or a data source name String (to be resolved via a
059: * {@link #setDataSourceLookup DataSourceLookup}.
060: * <p>The key can be of arbitrary type; this class implements the
061: * generic lookup process only. The concrete key representation will
062: * be handled by {@link #resolveSpecifiedLookupKey(Object)} and
063: * {@link #determineCurrentLookupKey()}.
064: */
065: public void setTargetDataSources(Map targetDataSources) {
066: this .targetDataSources = targetDataSources;
067: }
068:
069: /**
070: * Specify the default target DataSource, if any.
071: * The mapped value can either be a corresponding {@link javax.sql.DataSource}
072: * instance or a data source name String (to be resolved via a
073: * {@link #setDataSourceLookup DataSourceLookup}.
074: * <p>This DataSource will be used as target if none of the keyed
075: * {@link #setTargetDataSources targetDataSources} match the
076: * {@link #determineCurrentLookupKey()} current lookup key}.
077: */
078: public void setDefaultTargetDataSource(
079: Object defaultTargetDataSource) {
080: this .defaultTargetDataSource = defaultTargetDataSource;
081: }
082:
083: /**
084: * Set the DataSourceLookup implementation to use for resolving data source
085: * name Strings in the {@link #setTargetDataSources targetDataSources} map.
086: * <p>Default is a {@link JndiDataSourceLookup}, allowing the JNDI names
087: * of application server DataSources to be specified directly.
088: */
089: public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
090: this .dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup
091: : new JndiDataSourceLookup());
092: }
093:
094: public void afterPropertiesSet() {
095: if (this .targetDataSources == null) {
096: throw new IllegalArgumentException(
097: "targetDataSources is required");
098: }
099: this .resolvedDataSources = new HashMap(this .targetDataSources
100: .size());
101: for (Iterator it = this .targetDataSources.entrySet().iterator(); it
102: .hasNext();) {
103: Map.Entry entry = (Map.Entry) it.next();
104: Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
105: DataSource dataSource = resolveSpecifiedDataSource(entry
106: .getValue());
107: this .resolvedDataSources.put(lookupKey, dataSource);
108: }
109: if (this .defaultTargetDataSource != null) {
110: this .resolvedDefaultDataSource = resolveSpecifiedDataSource(this .defaultTargetDataSource);
111: }
112: }
113:
114: /**
115: * Resolve the specified data source object into a DataSource instance.
116: * <p>The default implementation handles DataSource instances and data source
117: * names (to be resolved via a {@link #setDataSourceLookup DataSourceLookup}).
118: * @param dataSource the data source value object as specified in the
119: * {@link #setTargetDataSources targetDataSources} map
120: * @return the resolved DataSource (never <code>null</code>)
121: * @throws IllegalArgumentException in case of an unsupported value type
122: */
123: protected DataSource resolveSpecifiedDataSource(Object dataSource)
124: throws IllegalArgumentException {
125: if (dataSource instanceof DataSource) {
126: return (DataSource) dataSource;
127: } else if (dataSource instanceof String) {
128: return this .dataSourceLookup
129: .getDataSource((String) dataSource);
130: } else {
131: throw new IllegalArgumentException(
132: "Illegal data source value - only [javax.sql.DataSource] and String supported: "
133: + dataSource);
134: }
135: }
136:
137: public Connection getConnection() throws SQLException {
138: return determineTargetDataSource().getConnection();
139: }
140:
141: public Connection getConnection(String username, String password)
142: throws SQLException {
143: return determineTargetDataSource().getConnection(username,
144: password);
145: }
146:
147: /**
148: * Retrieve the current target DataSource. Determines the
149: * {@link #determineCurrentLookupKey() current lookup key}, performs
150: * a lookup in the {@link #setTargetDataSources targetDataSources} map,
151: * falls back to the specified
152: * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
153: * @see #determineCurrentLookupKey()
154: */
155: protected DataSource determineTargetDataSource() {
156: Assert.notNull(this .resolvedDataSources,
157: "DataSource router not initialized");
158: Object lookupKey = determineCurrentLookupKey();
159: DataSource dataSource = (DataSource) this .resolvedDataSources
160: .get(lookupKey);
161: if (dataSource == null) {
162: dataSource = this .resolvedDefaultDataSource;
163: }
164: if (dataSource == null) {
165: throw new IllegalStateException(
166: "Cannot determine target DataSource for lookup key ["
167: + lookupKey + "]");
168: }
169: return dataSource;
170: }
171:
172: /**
173: * Resolve the given lookup key object, as specified in the
174: * {@link #setTargetDataSources targetDataSources} map, into
175: * the actual lookup key to be used for matching with the
176: * {@link #determineCurrentLookupKey() current lookup key}.
177: * <p>The default implementation simply returns the given key as-is.
178: * @param lookupKey the lookup key object as specified by the user
179: * @return the lookup key as needed for matching
180: */
181: protected Object resolveSpecifiedLookupKey(Object lookupKey) {
182: return lookupKey;
183: }
184:
185: /**
186: * Determine the current lookup key. This will typically be
187: * implemented to check a thread-bound transaction context.
188: * <p>Allows for arbitrary keys. The returned key needs
189: * to match the stored lookup key type, as resolved by the
190: * {@link #resolveSpecifiedLookupKey} method.
191: */
192: protected abstract Object determineCurrentLookupKey();
193:
194: }
|