WholeResultSetTransformer.java
package org.codefilarete.stalactite.sql.result;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.codefilarete.reflection.MethodReferenceCapturer;
import org.codefilarete.stalactite.sql.result.ResultSetRowTransformer.BeanFactory;
import org.codefilarete.stalactite.sql.result.ResultSetRowTransformer.IdentifierArgBeanFactory;
import org.codefilarete.stalactite.sql.result.ResultSetRowTransformer.NoIdentifierBeanFactory;
import org.codefilarete.stalactite.sql.statement.binder.ResultSetReader;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.ThreadLocals;
import org.codefilarete.tool.collection.KeepOrderSet;
import org.danekja.java.util.function.serializable.SerializableFunction;
import org.danekja.java.util.function.serializable.SerializableSupplier;
import static org.codefilarete.stalactite.sql.result.WholeResultSetTransformer.AssemblyPolicy.ON_EACH_ROW;
/**
* A class aimed at transforming a whole {@link ResultSet} into a graph of objects.
* Graph creation is declared through {@link #add(String, ResultSetReader, Class, SerializableFunction, BeanRelationFixer)}, be aware that relations will be
* unstacked/applied in order of declaration.
* Instances of this class can be reused over multiple {@link ResultSet} (supposed to have same columns) and are thread-safe for iteration.
* They can also be adapted to other {@link ResultSet}s that haven't the exact same column names by duplicating them with {@link #copyWithAliases(Function)}.
* Moreover, they can also be cloned to another type of bean which uses the same column names with {@link #copyFor(Class, SerializableFunction)}.
*
* @param <C> assembled bean type
* @param <I> bean identifier type
* @author Guillaume Mary
* @see #add(String, ResultSetReader, BiConsumer)
* @see #add(String, ResultSetReader, Class, SerializableFunction, BeanRelationFixer)
* @see #transformAll(ResultSet)
*/
public class WholeResultSetTransformer<C, I> implements ResultSetTransformer<C, I>, CopiableForAnotherQuery<C> {
/**
* Per-Thread caches for created beans during {@link ResultSet} iteration.
* Created as a {@link ThreadLocal} to share it between all {@link WholeResultSetTransformer}s that are implied in the bean graph creation :
* first approach was to use an instance variable and initialize it on all instances before {@link ResultSet} iteration and release it after,
* but this design had several drawbacks:
* - non-thread-safe usage of instances (implying synchronization during whole iteration !)
* - instances piped with {@link #add(BeanRelationFixer, ResultSetRowTransformer)} were impossible to wire to the bean cache without cloning them to a
* {@link WholeResultSetTransformer}
*/
static final ThreadLocal<SimpleBeanCache> CURRENT_BEAN_CACHE = new ThreadLocal<>();
static final ThreadLocal<Set<TreatedRelation>> CURRENT_TREATED_ASSEMBLERS = ThreadLocal.withInitial(HashSet::new);
private final RootConverter<C, I> rootConverter;
/** The list of relations that will assemble objects */
private final KeepOrderSet<Assembler<C>> assemblers = new KeepOrderSet<>();
/** Raw consumers, not linked to any beans, will be run on each row, if someone expects it to be run once per bean, then attach it to root converter */
private final Set<ColumnConsumer<C, Object>> consumers = new HashSet<>();
/**
* Constructor with root bean instantiation parameters
*
* @param rootType main bean type
* @param beanKeyColumnName the name of the column that contains bean key
* @param reader object to ease column reading, indicates column type
* @param beanFactory the bean creator, bean key will be passed as argument. Not called if bean key is null (no instantiation needed)
*/
public WholeResultSetTransformer(Class<C> rootType, String beanKeyColumnName, ResultSetReader<I> reader, SerializableFunction<I, C> beanFactory) {
this(new ResultSetRowTransformer<>(rootType, beanKeyColumnName, reader, beanFactory));
}
/**
* Constructor with root bean instantiation parameters as a default Java Bean constructor and setter for key value
*
* @param rootType main bean type
* @param beanKeyColumnName the name of the column that contains bean key
* @param reader object to ease column reading, indicates column type
* @param beanFactory the bean constructor. Not called if bean key is null (no instantiation needed)
* @param setter setter for bean key
*/
public WholeResultSetTransformer(Class<C> rootType, String beanKeyColumnName, ResultSetReader<I> reader, Supplier<C> beanFactory, BiConsumer<C, I> setter) {
this(rootType, beanKeyColumnName, reader, i -> {
C newInstance = beanFactory.get();
setter.accept(newInstance, i);
return newInstance;
});
}
/**
* Constructor with root bean instantiation parameters.
*
* With this constructor a root instance per {@link ResultSet} row will be created since there's no mean to
* distinguish a row instance from another because no key reader is given. This is expected to be used for flat data retrieval.
*
* @param rootType main bean type
* @param beanFactory the bean creator
*/
public WholeResultSetTransformer(Class<C> rootType, SerializableSupplier<C> beanFactory) {
this(new ResultSetRowTransformer<>(rootType, new NoIdentifierBeanFactory<>(beanFactory)));
}
/**
* Special constructor aimed at defining root transformer when other constructors are insufficient
*
* @param rootTransformer transformer that will create graph root-beans from {@link ResultSet}
*/
public WholeResultSetTransformer(ResultSetRowTransformer<C, I> rootTransformer) {
this.rootConverter = new CachingResultSetRowTransformer<>(rootTransformer);
}
/**
* Defines a complementary column that will be mapped on a bean property.
* Null values will be passed to the consumer, hence the property mapper must be "null-value proof".
* Those consumers will be run on each row, if this behavior is not expected then one can attach it to root converter.
*
* @param columnConsumer the object that will do the reading and mapping
*/
@Override
public <O> WholeResultSetTransformer<C, I> add(ColumnConsumer<C, O> columnConsumer) {
consumers.add((ColumnConsumer<C, Object>) columnConsumer);
return this;
}
/**
* Adds a bean relation to the main/root object.
*
* @param columnName the column name to read the bean key
* @param reader a reader for reading the bean key
* @param beanType the type of the new bean
* @param beanFactory a factory for creating the new bean, not called if bean key is null
* @param combiner a callback that will combine the newly created bean and the one of this transformer, called even with a null bean (v)
* @param <K> ResultSet column type
* @param <V> new bean type
*
* @return this
* @see #add(String, ResultSetReader, Class, BeanRelationFixer)
*/
public <K, V> WholeResultSetTransformer<C, I> add(String columnName, ResultSetReader<K> reader, Class<V> beanType,
SerializableFunction<K, V> beanFactory, BeanRelationFixer<C, V> combiner) {
ResultSetRowTransformer<V, K> relatedBeanCreator = new ResultSetRowTransformer<>(beanType, columnName, reader, beanFactory);
add(combiner, relatedBeanCreator);
return this;
}
/**
* Adds a bean relation to the main/root object.
* It is a simplified version of {@link #add(String, ResultSetReader, Class, SerializableFunction, BeanRelationFixer)} where the factory is the
* default constructor of the given type.
* Be aware that the factory doesn't take the column (bean key) value as a parameter, if no default constructor exists please prefer
* {@link #add(String, ResultSetReader, Class, SerializableFunction, BeanRelationFixer)} or {@link #add(String, ResultSetReader, BiConsumer)}
*
* @param columnName the column name to read the bean key
* @param reader a reader for reading the bean key
* @param beanType the type of the new bean
* @param combiner a callback that will combine the newly created bean and the one of this transformer, called even with a null bean (v)
* @param <K> ResultSet column type
* @param <V> new bean type
*
* @return this
*/
public <K, V> WholeResultSetTransformer<C, I> add(String columnName, ResultSetReader<K> reader, Class<V> beanType, BeanRelationFixer<C, V> combiner) {
add(columnName, reader, beanType, v -> Reflections.newInstance(beanType), combiner);
return this;
}
/**
* Combines this transformer with another one through a bean relation, creating a bean graph.
* Be aware that a copy of relatedBeanCreator is made to make it uses cache of beans during {@link ResultSet} iteration.
* Hence, modifying relatedBeanCreator after won't affect this.
*
* @param <K> the type of the other bean keys
* @param <V> the type of the other beans
* @param combiner the wire between instances of this transformer and those of the given one
* @param relatedBeanCreator the manager of the other beans
* @return this
*/
@Override
public <K, V> WholeResultSetTransformer<C, I> add(BeanRelationFixer<C, V> combiner, ResultSetRowTransformer<V, K> relatedBeanCreator) {
return add(combiner, relatedBeanCreator, ON_EACH_ROW);
}
/**
* Combines this transformer with another one through a bean relation, hence a bean graph is created.
* Be aware that a copy of relatedBeanCreator is made to make it uses cache of beans during {@link ResultSet} iteration.
* Hence, modifying relatedBeanCreator after won't affect this.
*
* @param <K> the type of the other bean keys
* @param <V> the type of the other beans
* @param combiner the wire between instances of this transformer and those of the given one
* @param relatedBeanCreator the manager of the other beans
* @return this
*/
public <K, V> WholeResultSetTransformer<C, I> add(BeanRelationFixer<C, V> combiner, ResultSetRowTransformer<V, K> relatedBeanCreator, AssemblyPolicy assemblyPolicy) {
CachingResultSetRowTransformer<V, K> relatedBeanCreatorCopy = new CachingResultSetRowTransformer<>(relatedBeanCreator);
return add(new Relation<>(combiner, relatedBeanCreatorCopy), assemblyPolicy);
}
/**
* Adds a very generic way to assemble {@link ResultSet} rows to a root bean.
* Be aware that any bean created by given assembler won't participate in current instance cache, if this is required then one should implement
* its own cache.
* Assembly will occur on each row ({@link ResultSetRowAssembler#assemble(Object, ResultSet)} will be call for each {@link ResultSet} row)
*
* @param assembler a generic combiner of a root bean and each {@link ResultSet} row
* @return this
* @see #add(ResultSetRowAssembler, AssemblyPolicy)
*/
public WholeResultSetTransformer<C, I> add(ResultSetRowAssembler<C> assembler) {
return add(assembler, ON_EACH_ROW);
}
/**
* Adds a very generic way to assemble {@link ResultSet} rows to a root bean.
* Be aware that any bean created by given assembler won't participate in current instance cache, if this is required then one should implement
* its own cache.
*
* @param assembler a generic combiner of a root bean and each {@link ResultSet} row
* @param assemblyPolicy policy to decide if given assemble shall be invoked on each row or not
* @return this
*/
public WholeResultSetTransformer<C, I> add(ResultSetRowAssembler<C> assembler, AssemblyPolicy assemblyPolicy) {
this.assemblers.add(new Assembler<>(assembler, assemblyPolicy));
return this;
}
@Override
public <T extends C> WholeResultSetTransformer<T, I> copyFor(Class<T> beanType, SerializableFunction<I, T> beanFactory) {
ResultSetRowTransformer<T, I> newRootConverter = this.rootConverter.copyFor(beanType, beanFactory);
return copy(newRootConverter);
}
@Override
public <T extends C> WholeResultSetTransformer<T, I> copyFor(Class<T> beanType, SerializableSupplier<T> beanFactory) {
ResultSetRowTransformer<T, I> newRootConverter = this.rootConverter.copyFor(beanType, beanFactory);
return copy(newRootConverter);
}
private <T extends C> WholeResultSetTransformer<T, I> copy(ResultSetRowTransformer<T, I> newRootConverter) {
// Making the copy
WholeResultSetTransformer<T, I> result = new WholeResultSetTransformer<>(newRootConverter);
// Note: combiners are shared, not copied
result.assemblers.addAll((Collection) this.assemblers);
result.consumers.addAll((Collection) this.consumers);
return result;
}
@Override
public WholeResultSetTransformer<C, I> copyWithAliases(Function<String, String> columnMapping) {
// NB: rootConverter can be cloned without a cache checking bean factory because it already has it due to previous assignments
// (follow rootConverter assignments to be sure)
ResultSetRowTransformer<C, I> rootConverterCopy = this.rootConverter.copyWithAliases(columnMapping);
WholeResultSetTransformer<C, I> result = new WholeResultSetTransformer<>(rootConverterCopy);
this.assemblers.forEach(assembler ->
result.add(assembler.getResultSetRowAssembler().copyWithAliases(columnMapping), assembler.getPolicy())
);
this.consumers.forEach(assembler ->
result.add(assembler.copyWithAliases(columnMapping))
);
return result;
}
@Override // for adhoc return type
public WholeResultSetTransformer<C, I> copyWithAliases(Map<String, String> columnMapping) {
return copyWithAliases(columnMapping::get);
}
public Set<C> transformAll(ResultSet resultSet) {
return transformAll(resultSet, Accumulators.toCollection(LinkedHashSet::new));
}
public <R, S> R transformAll(ResultSet resultSet, Accumulator<C, S, R> accumulator) {
// We convert the ResultSet with an iteration over a ResultSetIterator that uses the transform(ResultSet) method
ResultSetIterator<C> resultSetIterator = new ResultSetIterator<C>(resultSet) {
@Override
public C convert(ResultSet resultSet) throws SQLException {
return transform(resultSet);
}
};
return doWithBeanCache(() -> accumulator.collect(() -> resultSetIterator));
}
/**
* Executes given code by initializing internal caches so one can use {@link #transform(ResultSet)} without getting {@link NullPointerException}
* due to their absence. Hence, given code is expected to use {@link #transform(ResultSet)} else this method is useless.
* This method is not to be considered as a primary usage of this class since its purpose is to iterate over a whole {@link ResultSet}.
*
* @param callable any code using {@link #transform(ResultSet)}
* @param <O> type of instance returned by this method which is the one returned by given code
* @return object returned by given {@link Supplier}
*/
public <O> O doWithBeanCache(Supplier<O> callable) {
return ThreadLocals.doWithThreadLocal(CURRENT_BEAN_CACHE, SimpleBeanCache::new,
(Supplier<O>) () -> ThreadLocals.doWithThreadLocal(CURRENT_TREATED_ASSEMBLERS, HashSet::new, callable));
}
/**
* <strong>This method is not expected to be called from outside but is public to respect interface implementation</strong>
* Note that it uses an internal {@link ThreadLocal} to ensure {@link AssemblyPolicy#ONCE_PER_BEAN}
*
* @param resultSet not null
* @return current row root bean
* @throws SQLException if an error occurs while reading given {@link ResultSet}
*/
@Override
public C transform(ResultSet resultSet) throws SQLException {
// Can it be possible to have a null root bean ? if such we should add a if-null prevention. But what's the case ?
C currentRowBean = rootConverter.transform(resultSet);
// Not made with stream because it doesn't handle well checked Exception
for (Assembler<C> entry : this.assemblers) {
ResultSetRowAssembler<C> rowAssembler = entry.getResultSetRowAssembler();
switch (entry.getPolicy()) {
case ON_EACH_ROW:
rowAssembler.assemble(currentRowBean, resultSet);
break;
case ONCE_PER_BEAN:
// we check if relation has already been treated for current bean : the key to be checked is a TreatedRelation
TreatedRelation<C> treatedRelation = new TreatedRelation<>(currentRowBean, rowAssembler);
Set<TreatedRelation> treatedRelations = CURRENT_TREATED_ASSEMBLERS.get();
if (!treatedRelations.contains(treatedRelation)) {
rowAssembler.assemble(currentRowBean, resultSet);
treatedRelations.add(treatedRelation);
}
break;
}
}
this.consumers.forEach(c -> c.assemble(currentRowBean, resultSet));
return currentRowBean;
}
/**
* A relation between a property mutator (setter) and the provider of the bean to be given as the setter argument
*
* @param <K>
* @param <V>
*/
private static class Relation<K, V> implements ResultSetRowAssembler<K> {
private final BeanRelationFixer<K, V> relationFixer;
private final CachingResultSetRowTransformer<V, ?> transformer;
public Relation(BeanRelationFixer<K, V> relationFixer, CachingResultSetRowTransformer<V, ?> transformer) {
this.relationFixer = relationFixer;
this.transformer = transformer;
}
@Override
public void assemble(K bean, ResultSet resultSet) {
// getting the bean
V value = transformer.transform(resultSet);
// applying it to the setter
if (value != null) {
relationFixer.apply(bean, value);
}
}
@Override
public Relation<K, V> copyWithAliases(Function<String, String> columnMapping) {
return new Relation<>(relationFixer, new CachingResultSetRowTransformer<>(transformer.transformer.copyWithAliases(columnMapping)));
}
}
private static class Assembler<O> {
private final ResultSetRowAssembler<O> resultSetRowAssembler;
private final AssemblyPolicy policy;
private Assembler(ResultSetRowAssembler<O> resultSetRowAssembler, AssemblyPolicy policy) {
this.resultSetRowAssembler = resultSetRowAssembler;
this.policy = policy;
}
public ResultSetRowAssembler<O> getResultSetRowAssembler() {
return resultSetRowAssembler;
}
public AssemblyPolicy getPolicy() {
return policy;
}
/**
* Implementation to avoid collision in Set, based on {@link ResultSetRowAssembler} only because we don't want to assemble beans twice
* because their relation differ in {@link AssemblyPolicy}, there one can't specify twice same combiner with different policies.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Assembler assembler = (Assembler) o;
return resultSetRowAssembler.equals(assembler.resultSetRowAssembler);
}
/**
* Implementation to avoid collision in Set, based on {@link ResultSetRowAssembler} only because we don't want to assemble beans twice
* because their relation differ in {@link AssemblyPolicy}, there one can't specify twice same combiner with different policies.
*/
@Override
public int hashCode() {
return resultSetRowAssembler.hashCode();
}
}
/**
* A small class that stores a relation between a root bean ad an assembler. It acts as a key in {@link HashSet} to mark a relation as treated
* to prevent the relation to be applied multiple times whereas it shouldn't, according to the {@link AssemblyPolicy#ONCE_PER_BEAN} strategy.
*
* @param <K> root bean type
*/
private static class TreatedRelation<K> {
private final K rootBean;
private final ResultSetRowAssembler<K> assembler;
private TreatedRelation(K rootBean, ResultSetRowAssembler<K> assembler) {
this.rootBean = rootBean;
this.assembler = assembler;
}
/** Implementation to avoid collision in Set */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TreatedRelation<?> that = (TreatedRelation<?>) o;
if (!rootBean.equals(that.rootBean)) return false;
return assembler.equals(that.assembler);
}
/** Implementation to avoid collision in Set */
@Override
public int hashCode() {
int result = rootBean.hashCode();
result = 31 * result + assembler.hashCode();
return result;
}
}
/**
* Policy introduced to specify if assembly shall be done for each row of a {@link ResultSet} (case of {@link java.util.Collection}
* to be filled for instance) or only once per root bean (simple setter case)
*/
public enum AssemblyPolicy {
/** Specifies that assembly shall be done on each row of a {@link ResultSet} ({@link java.util.Collection} case) */
ON_EACH_ROW,
/** Specifies that assembly shall be done once (and only onnce) per root bean (setter case) */
ONCE_PER_BEAN
}
private interface RootConverter<C, I> {
<T extends C> ResultSetRowTransformer<T, I> copyFor(Class<T> beanType, SerializableFunction<I, T> beanFactory);
<T extends C> ResultSetRowTransformer<T, I> copyFor(Class<T> beanType, SerializableSupplier<T> beanFactory);
ResultSetRowTransformer<C, I> copyWithAliases(Function<String, String> columnMapping);
C transform(ResultSet resultSet);
}
/**
* Cache system over a {@link ResultSetRowTransformer} that gives already created instance if bean key matches.
* Due to being based on bean key, this cache doesn't make sense with beans that don't have key (no-arg constructor),
* thus, is such case, a new instance will be created each time {@link #transform(ResultSet)} is called.
*/
private static class CachingResultSetRowTransformer<C, I> implements RootConverter<C, I> {
private final ResultSetRowTransformer<C, I> transformer;
private final Function<ResultSet, C> newInstanceProvider;
private CachingResultSetRowTransformer(ResultSetRowTransformer<C, I> transformer) {
this.transformer = transformer;
// computing the instance creator according to the given bean factory
BeanFactory<C> beanFactory = transformer.getBeanFactory();
if (beanFactory instanceof ResultSetRowTransformer.NoIdentifierBeanFactory) {
newInstanceProvider = rs -> ((NoIdentifierBeanFactory<C>) beanFactory).createInstance();
} else if (beanFactory instanceof ResultSetRowTransformer.IdentifierArgBeanFactory) {
newInstanceProvider = rs -> {
I beanKey = ((IdentifierArgBeanFactory<I, C>) beanFactory).readBeanKey(rs);
try {
// we don't call the cache if the bean key is null
return beanKey == null ? null : CURRENT_BEAN_CACHE.get().computeIfAbsent(transformer.getBeanType(), beanKey, i -> transformer.transform(rs));
} catch (ClassCastException cce) {
// Trying to give a more accurate reason that the default one
// Put into places for rare cases of misusage by wrapping code that loses reader and bean factory types,
// ending with a constructor input type error
MethodReferenceCapturer methodReferenceCapturer = new MethodReferenceCapturer();
throw new ClassCastException("Can't apply " + beanKey + " on constructor "
+ Reflections.toString(methodReferenceCapturer.findExecutable(((IdentifierArgBeanFactory<I, C>) beanFactory).getFactory())) + " : " + cce.getMessage());
}
};
} else {
// should not happen
throw new IllegalArgumentException("Class " + Reflections.toString(beanFactory.getClass()) + " is not implemented");
}
}
public C transform(ResultSet resultSet) {
return newInstanceProvider.apply(resultSet);
}
@Override
public <T extends C> ResultSetRowTransformer<T, I> copyFor(Class<T> beanType, SerializableFunction<I, T> beanFactory) {
return this.transformer.copyFor(beanType, beanFactory);
}
@Override
public <T extends C> ResultSetRowTransformer<T, I> copyFor(Class<T> beanType, SerializableSupplier<T> beanFactory) {
return this.transformer.copyFor(beanType, beanFactory);
}
@Override
public ResultSetRowTransformer<C, I> copyWithAliases(Function<String, String> columnMapping) {
return this.transformer.copyWithAliases(columnMapping);
}
}
}