/* * 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 java.lang.annotation.ElementType; import java.lang.reflect.Method; import javassist.util.proxy.MethodFilter; import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyFactory; import org.hibernate.ogm.options.navigation.EntityContext; import org.hibernate.ogm.options.navigation.GlobalContext; import org.hibernate.ogm.options.navigation.PropertyContext; import org.hibernate.ogm.options.navigation.spi.BaseContext; import org.hibernate.ogm.options.navigation.spi.ConfigurationContext; import org.hibernate.ogm.options.spi.Option; import org.hibernate.ogm.util.impl.Log; import org.hibernate.ogm.util.impl.LoggerFactory; import org.hibernate.ogm.util.impl.ReflectionHelper; /** * Keeps track of the entities and properties configured using the fluent configuration API. There is one instance of * this context per invocation of this API (beginning with the creation of a {@link GlobalContext}). This instance is * passed between the individual context types created in the course of using the fluent API. The book-keeping of * configured options is delegated to {@link AppendableConfigurationContext}. * * @author Davide D'Alto <davide@hibernate.org> * @author Gunnar Morling */ public class ConfigurationContextImpl implements ConfigurationContext { private static final Log log = LoggerFactory.make(); /** * Contains all options configured via this and other configuration contexts. */ private final AppendableConfigurationContext allOptions; private Class<?> currentEntityType; private String currentPropertyName; public ConfigurationContextImpl(AppendableConfigurationContext appendableContext) { this.allOptions = appendableContext; } @Override public <V> void addGlobalOption(Option<?, V> option, V value) { allOptions.addGlobalOption( option, value ); } @Override public <V> void addEntityOption(Option<?, V> option, V value) { allOptions.addEntityOption( currentEntityType, option, value ); } @Override public <V> void addPropertyOption(Option<?, V> option, V value) { allOptions.addPropertyOption( currentEntityType, currentPropertyName, option, value ); } public void configureEntity(Class<?> entityType) { this.currentEntityType = entityType; } public void configureProperty(String propertyName, ElementType elementType) { if ( elementType != ElementType.FIELD && elementType != ElementType.METHOD ) { throw log.getUnsupportedElementTypeException( elementType ); } if ( !ReflectionHelper.propertyExists( currentEntityType, propertyName, elementType ) ) { throw log.getPropertyDoesNotExistException( currentEntityType.getName(), propertyName, elementType ); } this.currentPropertyName = propertyName; } /** * Creates a new {@link GlobalContext} object based on the given context implementation types. All implementation * types must declare a public or protected constructor with a single parameter, accepting {@link ConfigurationContext}. * <p> * Each context implementation type must provide an implementation of the method(s) declared on the particular * provider-specific context interface. All methods declared on context super interfaces - {@code entity()} and * {@code property()} - are implemented following the dynamic proxy pattern, the implementation types therefore can * be declared abstract, avoiding the need to implement these methods themselves. * <p> * By convention, the implementation types should directly or indirectly extend {@link BaseContext}. * * @param globalContextImplType the provider-specific global context implementation type * @param entityContextImplType the provider-specific entity context implementation type * @param propertyContextImplType the provider-specific property context implementation type * @return a new {@link GlobalContext} object based on the given context implementation types */ @Override @SuppressWarnings("unchecked") public <G extends GlobalContext<?, ?>> G createGlobalContext(Class<? extends G> globalContextImplType, final Class<? extends EntityContext<?, ?>> entityContextImplType, Class<? extends PropertyContext<?, ?>> propertyContextImplType) { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setSuperclass( globalContextImplType ); proxyFactory.setFilter( new EntityMethodFilter() ); try { return (G) proxyFactory.create( new Class<?>[] { ConfigurationContext.class }, new Object[] { this }, new EntityOrPropertyMethodHandler( entityContextImplType, propertyContextImplType ) ); } catch (Exception e) { throw log.cannotCreateGlobalContextProxy( globalContextImplType, e ); } } @SuppressWarnings("unchecked") private <E extends EntityContext<?, ?>> E createEntityMappingContext(Class<? extends E> entityContextImplType, Class<? extends PropertyContext<?, ?>> propertyContextImplType) { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setSuperclass( entityContextImplType ); proxyFactory.setFilter( new EntityOrPropertyMethodFilter() ); try { return (E) proxyFactory.create( new Class<?>[] { ConfigurationContext.class }, new Object[] { this }, new EntityOrPropertyMethodHandler( entityContextImplType, propertyContextImplType ) ); } catch (Exception e) { throw log.cannotCreateEntityContextProxy( entityContextImplType, e ); } } @SuppressWarnings("unchecked") private <P extends PropertyContext<?, ?>> P createPropertyMappingContext(Class<? extends EntityContext<?, ?>> entityContextImplType, Class<? extends P> propertyContextImplType) { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setSuperclass( propertyContextImplType ); proxyFactory.setFilter( new EntityOrPropertyMethodFilter() ); try { return (P) proxyFactory.create( new Class<?>[] { ConfigurationContext.class }, new Object[] { this }, new EntityOrPropertyMethodHandler( entityContextImplType, propertyContextImplType ) ); } catch (Exception e) { throw log.cannotCreateEntityContextProxy( propertyContextImplType, e ); } } private final class EntityOrPropertyMethodHandler implements MethodHandler { private final Class<? extends EntityContext<?, ?>> entityContextImplType; private final Class<? extends PropertyContext<?, ?>> propertyContextImplType; private EntityOrPropertyMethodHandler(Class<? extends EntityContext<?, ?>> entityContextImplType, Class<? extends PropertyContext<?, ?>> propertyContextImplType) { this.entityContextImplType = entityContextImplType; this.propertyContextImplType = propertyContextImplType; } @Override public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { if ( thisMethod.getName().equals( "entity" ) ) { configureEntity( (Class<?>) args[0] ); return createEntityMappingContext( entityContextImplType, propertyContextImplType ); } else { configureProperty( (String) args[0], (ElementType) args[1] ); return createPropertyMappingContext( entityContextImplType, propertyContextImplType ); } } } private final class EntityMethodFilter implements MethodFilter { @Override public boolean isHandled(Method m) { return m.getName().equals( "entity" ) && m.getParameterTypes().length == 1 && m.getParameterTypes()[0] == Class.class; } } private final class EntityOrPropertyMethodFilter implements MethodFilter { @Override public boolean isHandled(Method m) { return ( m.getName().equals( "entity" ) && m.getParameterTypes().length == 1 && m.getParameterTypes()[0] == Class.class ) || ( m.getName().equals( "property" ) && m.getParameterTypes().length == 2 && m.getParameterTypes()[0] == String.class && m .getParameterTypes()[1] == ElementType.class ); } } }