/* * Hibernate OGM, Domain model persistence for NoSQL datastores * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.ogm.options.navigation.impl; import static org.hibernate.ogm.util.impl.CollectionHelper.newConcurrentHashMap; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentMap; import org.hibernate.ogm.options.container.impl.OptionsContainer; import org.hibernate.ogm.options.navigation.source.impl.OptionValueSource; import org.hibernate.ogm.options.spi.Option; import org.hibernate.ogm.options.spi.OptionsContext; import org.hibernate.ogm.options.spi.UniqueOption; /** * Provides access to the options effectively applying for a given entity or property. * * @author Gunnar Morling */ public class OptionsContextImpl implements OptionsContext { private final List<OptionValueSource> sources; private final Class<?> entityType; private final String propertyName; private final List<Class<?>> hierarchy; /** * Caches the container contributing the value per option type. */ private final ConcurrentMap<Class<? extends Option<?, ?>>, OptionsContainer> optionCache; private OptionsContextImpl(List<OptionValueSource> sources, Class<?> entityType, String propertyName) { this.sources = sources; this.entityType = entityType; this.propertyName = propertyName; this.hierarchy = getClassHierarchy( entityType ); this.optionCache = newConcurrentHashMap(); } public static OptionsContext forGlobal(List<OptionValueSource> sources) { return new OptionsContextImpl( sources, null, null ); } public static OptionsContext forProperty(List<OptionValueSource> sources, Class<?> entityType, String propertyName) { return new OptionsContextImpl( sources, entityType, propertyName ); } public static OptionsContext forEntity(List<OptionValueSource> sources, Class<?> entityType) { return new OptionsContextImpl( sources, entityType, null ); } @Override public <I, V, O extends Option<I, V>> V get(Class<O> optionType, I identifier) { OptionsContainer optionsContainer = optionCache.get( optionType ); if ( optionsContainer == null ) { optionsContainer = getAndCacheOptionsContainer( optionType ); } return optionsContainer.get( optionType, identifier ); } @Override public <V, O extends UniqueOption<V>> V getUnique(Class<O> optionType) { OptionsContainer optionsContainer = optionCache.get( optionType ); if ( optionsContainer == null ) { optionsContainer = getAndCacheOptionsContainer( optionType ); } return optionsContainer.getUnique( optionType ); } @Override public <I, V, O extends Option<I, V>> Map<I, V> getAll(Class<O> optionType) { OptionsContainer optionsContainer = optionCache.get( optionType ); if ( optionsContainer == null ) { optionsContainer = getAndCacheOptionsContainer( optionType ); } return optionsContainer.getAll( optionType ); } private <I, V, O extends Option<I, V>> OptionsContainer getAndCacheOptionsContainer(Class<O> optionType) { OptionsContainer container = getMostSpecificContainer( optionType ); OptionsContainer cachedContainer = optionCache.putIfAbsent( optionType, container ); if ( cachedContainer != null ) { container = cachedContainer; } return container; } /** * Returns that container which effectively contributes the given option's value as per the algorithm outlined in * the documentation of {@link OptionsContext}. * * @param optionType the option type of interest * @return the container which effectively contributes the given option's value; May be an empty container in case * no value at all is configured for the given option, but never {@code null} */ private <I, V, O extends Option<I, V>> OptionsContainer getMostSpecificContainer(Class<O> optionType) { OptionsContainer container; if ( propertyName != null ) { for ( Class<?> clazz : hierarchy ) { for ( OptionValueSource source : sources ) { container = source.getPropertyOptions( clazz, propertyName ); if ( !container.getAll( optionType ).isEmpty() ) { return container; } } } } if ( entityType != null ) { for ( Class<?> clazz : hierarchy ) { for ( OptionValueSource source : sources ) { container = source.getEntityOptions( clazz ); if ( !container.getAll( optionType ).isEmpty() ) { return container; } } } } for ( OptionValueSource source : sources ) { container = source.getGlobalOptions(); if ( !container.getAll( optionType ).isEmpty() ) { return container; } } return OptionsContainer.EMPTY; } /** * Returns the class hierarchy of the given type, from bottom to top, starting with the given class itself. * Interfaces are not included. * * @param clazz the class of interest * @return the class hierarchy of the given class */ private static List<Class<?>> getClassHierarchy(Class<?> clazz) { List<Class<?>> hierarchy = new ArrayList<Class<?>>( 4 ); for ( Class<?> current = clazz; current != null; current = current.getSuperclass() ) { hierarchy.add( current ); } return hierarchy; } @Override public String toString() { return "OptionsContextImpl [entityType=" + entityType + ", propertyName=" + propertyName + "]"; } }