SingleTableRootJoinNode.java
package org.codefilarete.stalactite.engine.runtime.load;
import javax.annotation.Nullable;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.codefilarete.stalactite.dsl.PolymorphismPolicy.SingleTablePolymorphism;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.stalactite.engine.runtime.load.EntityInflater.EntityMappingAdapter;
import org.codefilarete.stalactite.engine.runtime.load.EntityTreeInflater.TreeInflationContext;
import org.codefilarete.stalactite.engine.runtime.load.JoinRowConsumer.RootJoinRowConsumer;
import org.codefilarete.stalactite.mapping.EntityMapping;
import org.codefilarete.stalactite.mapping.RowTransformer;
import org.codefilarete.stalactite.query.model.Selectable;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.result.ColumnedRow;
import org.codefilarete.tool.Duo;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.collection.Arrays;
import org.codefilarete.tool.collection.Collections;
import org.codefilarete.tool.collection.Iterables;
import org.codefilarete.tool.collection.KeepOrderSet;
/**
* Particular {@link JoinRoot} made to handle single-table polymorphic case : polymorphic entity instantiation is the core focus of it.
* Identifier is given by the subclass which find its id in the row (see {@link SingleTablePolymorphicJoinRootRowConsumer#findSubInflater(ColumnedRow)}),
*
* @author Guillaume Mary
*/
public class SingleTableRootJoinNode<C, I, T extends Table<T>, DTYPE> extends JoinRoot<C, I, T> {
private final Set<? extends ConfiguredRelationalPersister<C, I>> subPersisters;
private final Set<Column<T, ?>> allColumnsInHierarchy;
private final Column<T, DTYPE> discriminatorColumn;
private final SingleTablePolymorphism<C, DTYPE> polymorphismPolicy;
public SingleTableRootJoinNode(EntityJoinTree<C, I> tree,
ConfiguredRelationalPersister<C, I> mainPersister,
Set<? extends ConfiguredRelationalPersister<C, I>> subPersisters,
Column<T, DTYPE> discriminatorColumn,
SingleTablePolymorphism<C, DTYPE> polymorphismPolicy) {
super(tree, new EntityMappingAdapter<>(mainPersister.<T>getMapping()), (T) mainPersister.getMainTable());
this.subPersisters = subPersisters;
this.discriminatorColumn = discriminatorColumn;
this.polymorphismPolicy = polymorphismPolicy;
this.allColumnsInHierarchy = Collections.cat(Arrays.asList(mainPersister), subPersisters)
.stream().flatMap(persister -> ((T) persister.getMainTable()).getColumns().stream())
.collect(Collectors.toCollection(KeepOrderSet::new));
}
@Override
public Set<Selectable<?>> getColumnsToSelect() {
return (Set) this.allColumnsInHierarchy;
}
@Override
public RootJoinRowConsumer<C> toConsumer(JoinNode<C, T> joinNode) {
// the decoder can't be a usual variable because it can't be computed now since we lack the EntityTreeInflater context
// (which is only available when the query is executed, not at build time)
Supplier<ColumnedRow> decoderProvider = () -> EntityTreeInflater.currentContext().getDecoder(joinNode);
Set<SubPersisterConsumer<C, I>> subEntityConsumer = subPersisters.stream().map(subPersister -> {
EntityMapping<C, I, T> mapping = subPersister.getMapping();
return new SubPersisterConsumer<>(
row -> mapping.getIdMapping().getIdentifierAssembler().assemble(decoderProvider.get()),
mapping.getClassToPersist(),
mapping.getRowTransformer());
}).collect(Collectors.toSet());
BiConsumer<C, ColumnedRow> rowConsumptionListener = getConsumptionListener() == null
? null
: (rootEntity, row) -> getConsumptionListener().onNodeConsumption(rootEntity, decoderProvider.get());
return new SingleTablePolymorphicJoinRootRowConsumer<>(joinNode, subEntityConsumer,
rowConsumptionListener, polymorphismPolicy, row -> decoderProvider.get().get(discriminatorColumn));
}
static class SubPersisterConsumer<C, I> {
private final Function<ColumnedRow, I> identifierAssembler;
private final Class<C> subEntityType;
private final RowTransformer<C> subEntityFactory;
private SubPersisterConsumer(Function<ColumnedRow, I> identifierAssembler,
Class<C> subEntityType,
RowTransformer<C> subEntityFactory) {
this.identifierAssembler = identifierAssembler;
this.subEntityType = subEntityType;
this.subEntityFactory = subEntityFactory;
}
}
static class SingleTablePolymorphicJoinRootRowConsumer<C, I, DTYPE> implements RootJoinRowConsumer<C> {
private final Set<SubPersisterConsumer<C, I>> subConsumers;
private final JoinNode<C, ?> joinNode;
/**
* Optional listener of ResultSet decoding
*/
@Nullable
private final BiConsumer<C, ColumnedRow> consumptionListener;
private final SingleTablePolymorphism<C, DTYPE> polymorphismPolicy;
private final Function<ColumnedRow, DTYPE> discriminatorValueReader;
private SingleTablePolymorphicJoinRootRowConsumer(JoinNode<C, ?> node,
Set<SubPersisterConsumer<C, I>> subConsumers,
@Nullable BiConsumer<C, ColumnedRow> consumptionListener,
SingleTablePolymorphism<C, DTYPE> polymorphismPolicy,
Function<ColumnedRow, DTYPE> discriminatorValueReader) {
this.subConsumers = subConsumers;
this.joinNode = node;
this.consumptionListener = consumptionListener;
this.polymorphismPolicy = polymorphismPolicy;
this.discriminatorValueReader = discriminatorValueReader;
}
@Override
public JoinNode<C, ?> getNode() {
return joinNode;
}
@Override
public C createRootInstance(ColumnedRow row, TreeInflationContext context) {
Duo<I, SubPersisterConsumer<C, I>> subInflater = findSubInflater(row);
C result;
if (subInflater == null) {
result = null;
} else {
// we don't need a ColumnedRow of sub-entity like in other polymorphic nodes because main persister properties
// were given to sub-persisters at build time (see SingleTablePolymorphismBuilder#buildSubclassPersister())
// which is logical since we don't have a join to sub-entity
result = context.giveEntityFromCache(subInflater.getRight().subEntityType, subInflater.getLeft(), () -> subInflater.getRight().subEntityFactory.transform(row));
}
if (consumptionListener != null) {
consumptionListener.accept(result, row);
}
return result;
}
@Nullable
public Duo<I, SubPersisterConsumer<C, I>> findSubInflater(ColumnedRow row) {
Class<? extends C> subEntityClass = polymorphismPolicy.getClass(discriminatorValueReader.apply(row));
Duo<SubPersisterConsumer<C, I>, Class<C>> subClassRowConsumer = Iterables.find(subConsumers, subConsumer -> subConsumer.subEntityType, subEntityClass::equals);
SubPersisterConsumer<C, I> subIdentifierConsumer = subClassRowConsumer.getLeft();
return new Duo<>(subIdentifierConsumer.identifierAssembler.apply(row), subIdentifierConsumer);
}
/**
* Implemented for debug. DO NOT RELY ON IT for anything else.
*/
@Override
public String toString() {
return Reflections.toString(this.getClass());
}
}
}