/*
* 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.util.configurationreader.impl;
import java.util.List;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.ogm.util.configurationreader.spi.ClassPropertyReaderContext;
import org.hibernate.ogm.util.configurationreader.spi.PropertyReaderContext;
import org.hibernate.ogm.util.configurationreader.spi.PropertyValidator;
import org.hibernate.ogm.util.configurationreader.spi.ShortNameResolver;
import org.hibernate.ogm.util.impl.Log;
import org.hibernate.ogm.util.impl.LoggerFactory;
/**
* A {@link PropertyReaderContext} which allows to retrieve properties by instantiating a given implementation type,
* e.g. specified as fully-qualified class name or class object.
*
* @param <T> the type of the value to instantiate
*
* @author Gunnar Morling
*/
public class DefaultClassPropertyReaderContext<T> extends PropertyReaderContextBase<T> implements ClassPropertyReaderContext<T> {
private static final Log log = LoggerFactory.make();
private final ClassLoaderService classLoaderService;
private Class<? extends T> defaultImplementation;
private String defaultImplementationName;
private Instantiator<T> instantiator;
private ShortNameResolver shortNameResolver;
DefaultClassPropertyReaderContext(ClassLoaderService classLoaderService, Object value, String propertyName, Class<T> clazz, T defaultValue, boolean isRequired, List<PropertyValidator<T>> validators) {
super( classLoaderService, value, propertyName, clazz, defaultValue, isRequired, validators );
this.classLoaderService = classLoaderService;
}
@Override
public DefaultClassPropertyReaderContext<T> withDefaultImplementation(Class<? extends T> defaultImplementation) {
this.defaultImplementation = defaultImplementation;
this.defaultImplementationName = null;
return this;
}
@Override
public DefaultClassPropertyReaderContext<T> withDefaultImplementation(String defaultImplementationName) {
this.defaultImplementationName = defaultImplementationName;
this.defaultImplementation = null;
return this;
}
/**
* Sets an instantiator to be used to create an instance of the property. Currently not exposed on the SPI as it is
* only needed within this module. May be promoted to an SPI later on, if required.
*
* @param instantiator the {@link Instantiator} to use to create the instance of the property
* @return an instance of the property
*/
public DefaultClassPropertyReaderContext<T> withInstantiator(Instantiator<T> instantiator) {
this.instantiator = instantiator;
return this;
}
@Override
public DefaultClassPropertyReaderContext<T> withShortNameResolver(ShortNameResolver shortNameResolver) {
this.shortNameResolver = shortNameResolver;
return this;
}
@Override
public T getTypedValue() {
ShortNameResolver resolver = shortNameResolver != null ? shortNameResolver : NoOpNameResolver.INSTANCE;
Instantiator<T> instantiator = this.instantiator != null ? this.instantiator : DefaultInstantiator.<T>getInstance();
Object configuredValue = getConfiguredValue();
Class<T> targetType = getTargetType();
T typedValue = null;
if ( configuredValue == null ) {
typedValue = getDefaultValue( resolver, instantiator );
}
else if ( targetType.isAssignableFrom( configuredValue.getClass() ) ) {
@SuppressWarnings("unchecked")
T v = (T) configuredValue;
typedValue = v;
}
else if ( configuredValue instanceof Class ) {
Class<? extends T> configuredClazz = narrowDownClass( (Class<?>) configuredValue, targetType );
typedValue = instantiator.newInstance( configuredClazz );
}
else if ( configuredValue instanceof String ) {
Class<? extends T> configuredClazz = getClassFromString( (String) configuredValue, targetType, resolver );
typedValue = instantiator.newInstance( configuredClazz );
}
else {
throw log.unexpectedInstanceType( getPropertyName(), configuredValue.toString(), configuredValue.getClass(), targetType );
}
return typedValue;
}
private T getDefaultValue(ShortNameResolver resolver, Instantiator<T> instantiator) {
if ( getDefaultValue() != null ) {
return getDefaultValue();
}
else if ( defaultImplementationName != null ) {
defaultImplementation = getClassFromString( defaultImplementationName, getTargetType(), resolver );
}
return defaultImplementation != null ? instantiator.newInstance( defaultImplementation ) : null;
}
private Class<? extends T> getClassFromString(String className, Class<T> targetType, ShortNameResolver shortNameResolver) {
if ( shortNameResolver.isShortName( className ) ) {
className = shortNameResolver.resolve( className );
}
Class<?> clazz = null;
try {
clazz = classLoaderService.classForName( className );
}
catch (ClassLoadingException e) {
throw log.unableToLoadClass( getPropertyName(), className, e );
}
return narrowDownClass( clazz, targetType );
}
private Class<? extends T> narrowDownClass(Class<?> clazz, Class<T> targetType) {
if ( !targetType.isAssignableFrom( clazz ) ) {
throw log.unexpectedClassType( getPropertyName(), clazz, targetType );
}
@SuppressWarnings("unchecked")
Class<T> typed = (Class<T>) clazz;
return typed;
}
private static class NoOpNameResolver implements ShortNameResolver {
static final NoOpNameResolver INSTANCE = new NoOpNameResolver();
@Override
public boolean isShortName(String name) {
return false;
}
@Override
public String resolve(String shortName) {
throw new UnsupportedOperationException();
}
}
private static class DefaultInstantiator<T> implements Instantiator<T> {
@SuppressWarnings("rawtypes")
private static final DefaultInstantiator<?> INSTANCE = new DefaultInstantiator();
@SuppressWarnings("unchecked")
public static <T> DefaultInstantiator<T> getInstance() {
return (DefaultInstantiator<T>) INSTANCE;
}
@Override
public T newInstance(Class<? extends T> clazz) {
try {
return clazz.newInstance();
}
catch (Exception e) {
throw log.unableToInstantiateType( clazz, e );
}
}
}
}