001/* 002 * Copyright 2002-2012 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.lookup; 018 019import java.sql.Connection; 020import java.sql.SQLException; 021import java.util.HashMap; 022import java.util.Map; 023import javax.sql.DataSource; 024 025import org.springframework.beans.factory.InitializingBean; 026import org.springframework.jdbc.datasource.AbstractDataSource; 027import org.springframework.util.Assert; 028 029/** 030 * Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()} 031 * calls to one of various target DataSources based on a lookup key. The latter is usually 032 * (but not necessarily) determined through some thread-bound transaction context. 033 * 034 * @author Juergen Hoeller 035 * @since 2.0.1 036 * @see #setTargetDataSources 037 * @see #setDefaultTargetDataSource 038 * @see #determineCurrentLookupKey() 039 */ 040public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { 041 042 private Map<Object, Object> targetDataSources; 043 044 private Object defaultTargetDataSource; 045 046 private boolean lenientFallback = true; 047 048 private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); 049 050 private Map<Object, DataSource> resolvedDataSources; 051 052 private DataSource resolvedDefaultDataSource; 053 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<Object, Object> targetDataSources) { 066 this.targetDataSources = targetDataSources; 067 } 068 069 /** 070 * Specify the default target DataSource, if any. 071 * <p>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(Object defaultTargetDataSource) { 079 this.defaultTargetDataSource = defaultTargetDataSource; 080 } 081 082 /** 083 * Specify whether to apply a lenient fallback to the default DataSource 084 * if no specific DataSource could be found for the current lookup key. 085 * <p>Default is "true", accepting lookup keys without a corresponding entry 086 * in the target DataSource map - simply falling back to the default DataSource 087 * in that case. 088 * <p>Switch this flag to "false" if you would prefer the fallback to only apply 089 * if the lookup key was {@code null}. Lookup keys without a DataSource 090 * entry will then lead to an IllegalStateException. 091 * @see #setTargetDataSources 092 * @see #setDefaultTargetDataSource 093 * @see #determineCurrentLookupKey() 094 */ 095 public void setLenientFallback(boolean lenientFallback) { 096 this.lenientFallback = lenientFallback; 097 } 098 099 /** 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}