/* * Copyright 2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cassandra.core.session.lookup; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.InitializingBean; import org.springframework.cassandra.core.session.SessionFactory; import org.springframework.util.Assert; import com.datastax.driver.core.Session; /** * Abstract {@link org.springframework.cassandra.core.session.SessionFactory} implementation that routes * {@link #getSession()} calls to one of various target {@link SessionFactory factories} based on a lookup key. The * latter is usually (but not necessarily) determined through some thread-bound transaction context. * * @author Mark Paluch * @since 2.0 * @see #setTargetSessionFactories(Map) * @see #setDefaultTargetSessionFactory(Object) * @see #determineCurrentLookupKey() * @see SessionFactoryLookup */ public abstract class AbstractRoutingSessionFactory implements SessionFactory, InitializingBean { private Map<Object, Object> targetSessionFactories; private Object defaultTargetSessionFactory; private boolean lenientFallback = true; private SessionFactoryLookup sessionFactoryLookup = new MapSessionFactoryLookup(); private Map<Object, SessionFactory> resolvedSessionFactories; private SessionFactory resolvedDefaultSessionFactory; /** * Specify the map of target session factories, with the lookup key as key. * <p> * The mapped value can either be a corresponding {@link SessionFactory} instance or a data source name String (to be * resolved via a {@link #setSessionFactoryLookup(SessionFactoryLookup)}). * <p> * The key can be of arbitrary type; this class implements the generic lookup process only. The concrete key * representation will be handled by {@link #resolveSpecifiedLookupKey(Object)} and * {@link #determineCurrentLookupKey()}. */ public void setTargetSessionFactories(Map<Object, Object> targetSessionFactories) { this.targetSessionFactories = targetSessionFactories; } /** * Specify the default target {@link SessionFactory}, if any. * <p> * The mapped value can either be a corresponding {@link SessionFactory} instance or a data source name String (to be * resolved via a {@link #setSessionFactoryLookup(SessionFactoryLookup)}). * <p> * This {@link SessionFactory} will be used as target if none of the keyed {@link #setTargetSessionFactories(Map)} * match the {@link #determineCurrentLookupKey()} current lookup key. */ public void setDefaultTargetSessionFactory(Object defaultTargetSessionFactory) { this.defaultTargetSessionFactory = defaultTargetSessionFactory; } /** * Specify whether to apply a lenient fallback to the default {@link SessionFactory} if no specific * {@link SessionFactory} could be found for the current lookup key. * <p> * Default is {@literal true}, accepting lookup keys without a corresponding entry in the target * {@link SessionFactory} map - simply falling back to the default {@link SessionFactory} in that case. * <p> * Switch this flag to {@literal false} if you would prefer the fallback to only apply if the lookup key was * {@code null}. Lookup keys without a {@link SessionFactory} entry will then lead to an * {@link IllegalStateException}. * * @param lenientFallback {@literal true} to accepting lookup keys without a corresponding entry in the target. * @see #setTargetSessionFactories * @see #setDefaultTargetSessionFactory * @see #determineCurrentLookupKey() */ public void setLenientFallback(boolean lenientFallback) { this.lenientFallback = lenientFallback; } /** * Set the {@link SessionFactoryLookup} implementation to use for resolving session factory name Strings in the * {@link #setTargetSessionFactories(Map)} map. * <p> * Default is a {@link MapSessionFactoryLookup}, allowing a string keyed map of {@link SessionFactory session * factories}. * * @param sessionFactoryLookup the {@link SessionFactoryLookup}. Defaults to {@link MapSessionFactoryLookup} if * {@literal null}. */ public void setSessionFactoryLookup(SessionFactoryLookup sessionFactoryLookup) { this.sessionFactoryLookup = (sessionFactoryLookup != null ? sessionFactoryLookup : new MapSessionFactoryLookup()); } /* (non-Javadoc) * @see org.springframework.cassandra.core.session.SessionFactory#getSession() */ @Override public Session getSession() { return determineTargetSessionFactory().getSession(); } // ------------------------------------------------------------------------- // Implementation hooks and helper methods // ------------------------------------------------------------------------- @Override public void afterPropertiesSet() { Assert.notNull(this.targetSessionFactories, "Property targetSessionFactories is required"); this.resolvedSessionFactories = new HashMap<>(this.targetSessionFactories.size()); for (Map.Entry<Object, Object> entry : this.targetSessionFactories.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); SessionFactory sessionFactory = resolveSpecifiedSessionFactory(entry.getValue()); this.resolvedSessionFactories.put(lookupKey, sessionFactory); } if (this.defaultTargetSessionFactory != null) { this.resolvedDefaultSessionFactory = resolveSpecifiedSessionFactory(this.defaultTargetSessionFactory); } } /** * Resolve the given lookup key object, as specified in the {@link #setTargetSessionFactories(Map)} map, into the * actual lookup key to be used for matching with the {@link #determineCurrentLookupKey() current lookup key}. * <p> * The default implementation simply returns the given key as-is. * * @param lookupKey the lookup key object as specified by the user * @return the lookup key as needed for matching */ protected Object resolveSpecifiedLookupKey(Object lookupKey) { return lookupKey; } /** * Resolve the specified {@code sessionFactory} object into a {@link SessionFactory} instance. * <p> * The default implementation handles {@link SessionFactory} instances and session factory names (to be resolved via a * {@link #setSessionFactoryLookup(SessionFactoryLookup)}). * * @param sessionFactory the session factory value object as specified in the {@link #setTargetSessionFactories(Map)} * map * @return the resolved {@link SessionFactory} * @throws IllegalArgumentException in case of an unsupported value type. * @throws SessionFactoryLookupFailureException if the lookup failed. */ protected SessionFactory resolveSpecifiedSessionFactory(Object sessionFactory) throws IllegalArgumentException { if (sessionFactory instanceof SessionFactory) { return (SessionFactory) sessionFactory; } else if (sessionFactory instanceof String) { return this.sessionFactoryLookup.getSessionFactory((String) sessionFactory); } else { throw new IllegalArgumentException(String .format("Illegal session factory value. Only [org.springframework.cassandra.core.session.SessionFactory]" + " and String supported: %s", sessionFactory)); } } /** * Retrieve the current target {@link SessionFactory}. Determines the {@link #determineCurrentLookupKey() current * lookup key}, performs a lookup in the {@link #setTargetSessionFactories(Map)} map, falls back to the specified * {@link #setDefaultTargetSessionFactory default target SessionFactory} if necessary. * * @see #determineCurrentLookupKey() */ protected SessionFactory determineTargetSessionFactory() { Assert.notNull(this.resolvedSessionFactories, "SessionFactory router not initialized"); Object lookupKey = determineCurrentLookupKey(); SessionFactory sessionFactory = this.resolvedSessionFactories.get(lookupKey); if (sessionFactory == null && (this.lenientFallback || lookupKey == null)) { sessionFactory = this.resolvedDefaultSessionFactory; } if (sessionFactory == null) { throw new IllegalStateException( String.format("Cannot determine target SessionFactory for lookup key [%s]", lookupKey)); } return sessionFactory; } /** * Determine the current lookup key. This will typically be implemented to check a thread-bound context. * <p> * Allows for arbitrary keys. * * @return the current lookup key. The returned key needs to match the stored lookup key type. */ protected abstract Object determineCurrentLookupKey(); }