JoinTablePolymorphismEntityFinder.java
package org.codefilarete.stalactite.engine.runtime.jointable;
import java.sql.ResultSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.codefilarete.stalactite.engine.configurer.builder.PersisterBuilderContext;
import org.codefilarete.stalactite.engine.configurer.builder.BuildLifeCycleListener;
import org.codefilarete.stalactite.engine.runtime.AbstractPolymorphicEntityFinder;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.JoinType;
import org.codefilarete.stalactite.engine.runtime.load.EntityMerger.EntityMergerAdapter;
import org.codefilarete.stalactite.engine.runtime.load.EntityTreeQueryBuilder;
import org.codefilarete.stalactite.engine.runtime.load.EntityTreeQueryBuilder.EntityTreeQuery;
import org.codefilarete.stalactite.engine.runtime.load.JoinTableRootJoinNode;
import org.codefilarete.stalactite.engine.runtime.load.MergeJoinNode;
import org.codefilarete.stalactite.engine.runtime.load.MergeJoinNode.MergeJoinRowConsumer;
import org.codefilarete.stalactite.engine.runtime.query.EntityCriteriaSupport;
import org.codefilarete.stalactite.engine.runtime.query.EntityQueryCriteriaSupport;
import org.codefilarete.stalactite.query.ConfiguredEntityCriteria;
import org.codefilarete.stalactite.query.builder.QuerySQLBuilderFactory.QuerySQLBuilder;
import org.codefilarete.stalactite.query.model.GroupBy;
import org.codefilarete.stalactite.query.model.Having;
import org.codefilarete.stalactite.query.model.Limit;
import org.codefilarete.stalactite.query.model.OrderBy;
import org.codefilarete.stalactite.query.model.Query;
import org.codefilarete.stalactite.query.model.Selectable;
import org.codefilarete.stalactite.query.model.Where;
import org.codefilarete.stalactite.sql.ConnectionProvider;
import org.codefilarete.stalactite.sql.Dialect;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.result.ColumnedRowIterator;
import org.codefilarete.stalactite.sql.statement.PreparedSQL;
import org.codefilarete.stalactite.sql.statement.ReadOperation;
import org.codefilarete.stalactite.sql.statement.SQLExecutionException;
import org.codefilarete.stalactite.sql.statement.binder.ResultSetReader;
import org.codefilarete.tool.Duo;
import org.codefilarete.tool.collection.KeepOrderMap;
import static org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.ROOT_JOIN_NAME;
/**
* @author Guillaume Mary
*/
public class JoinTablePolymorphismEntityFinder<C, I, T extends Table<T>> extends AbstractPolymorphicEntityFinder<C, I, T> {
private final SingleLoadEntityJoinTree<C, I> singleLoadEntityJoinTree;
private final EntityCriteriaSupport<C> criteriaSupport;
private Query query;
private EntityTreeQuery<C> entityTreeQuery;
public JoinTablePolymorphismEntityFinder(
ConfiguredRelationalPersister<C, I> mainPersister,
Map<? extends Class<C>, ? extends ConfiguredRelationalPersister<C, I>> persisterPerSubclass,
ConnectionProvider connectionProvider,
Dialect dialect) {
super(mainPersister, persisterPerSubclass, connectionProvider, dialect);
this.singleLoadEntityJoinTree = buildSingleLoadEntityJoinTree(mainPersister);
this.criteriaSupport = new EntityCriteriaSupport<>(singleLoadEntityJoinTree);
// made for optimization (to avoid creating multiple times the query) but also to avoid adding several times the polymorphic JoinNode consumers
// to itself by calling several time the "toConsumer(..)" method that usually calls some "add" method, which declares duplicates of consumers,
// which, at the very end, implies an exception during the aggregate inflation phase : "Can't find consumer node"
PersisterBuilderContext.CURRENT.get().addBuildLifeCycleListener(new BuildLifeCycleListener() {
@Override
public void afterBuild() {
}
@Override
public void afterAllBuild() {
buildQuery();
}
});
}
private void buildQuery() {
entityTreeQuery = new EntityTreeQueryBuilder<>(singleLoadEntityJoinTree, dialect.getColumnBinderRegistry()).buildSelectQuery();
query = entityTreeQuery.getQuery();
}
@Override
protected EntityTreeQuery<C> getAggregateQueryTemplate() {
return entityTreeQuery;
}
@Override
public EntityJoinTree<C, I> getEntityJoinTree() {
return singleLoadEntityJoinTree;
}
@Override
public EntityQueryCriteriaSupport<C, I> newCriteriaSupport() {
return new EntityQueryCriteriaSupport<>(this, criteriaSupport.copy());
}
private SingleLoadEntityJoinTree<C, I> buildSingleLoadEntityJoinTree(ConfiguredRelationalPersister<C, I> mainPersister) {
SingleLoadEntityJoinTree<C, I> result = new SingleLoadEntityJoinTree<>(
mainPersister,
new HashSet<>(persisterPerSubclass.values())
);
// sub entities persisters will be used to create entities
persisterPerSubclass.forEach((type, persister) -> {
String mergeJoin = result.addMergeJoin(
ROOT_JOIN_NAME,
new EntityMergerAdapter<>(persister.<T>getMapping()),
mainPersister.getMainTable().getPrimaryKey(),
persister.getMainTable().getPrimaryKey(),
JoinType.OUTER,
joinNode -> {
// implemented to add newly created sub consumer to root one, therefore it will be able to create the right sub-instance
MergeJoinRowConsumer<C> subEntityConsumer = new MergeJoinRowConsumer<>((MergeJoinNode<C, ?, ?, ?>) joinNode, persister.getMapping().getRowTransformer());
result.getRoot().addSubPersister(persister, subEntityConsumer);
return subEntityConsumer;
}
);
// we add all relations of sub-persister to the join tree to make the data available in the final query
// because JoinTableRootJoinNode of SingleLoadEntityJoinTree need them
persister.getEntityJoinTree().projectTo(result, mergeJoin);
});
// we project main persister tree to keep its relations
mainEntityJoinTree.projectTo(result, ROOT_JOIN_NAME);
return result;
}
@Override
public Set<C> selectWithSingleQuery(ConfiguredEntityCriteria where, Map<String, Object> values, OrderBy orderBy, Limit limit) {
LOGGER.debug("Finding entities in a single query with criteria {}", where);
if (hasSubPolymorphicPersister) {
LOGGER.debug("Single query was asked but due to sub-polymorphism the query is made in 2 phases");
return selectIn2Phases(where, values, orderBy, limit);
} else {
Query queryClone = new Query(query.getSelectDelegate(), query.getFromDelegate(), new Where<>().add(where.getCriteria()), new GroupBy(), new Having(), orderBy, limit);
return super.selectWithSingleQuery(queryClone, values, entityTreeQuery, dialect, connectionProvider);
}
}
@Override
public Set<C> selectIn2Phases(ConfiguredEntityCriteria where, Map<String, Object> values, OrderBy orderBy, Limit limit) {
LOGGER.debug("Finding entities in 2-phases query with criteria {}", where);
// we clone the query to avoid polluting the instance one, else, from select(..) to select(..), we append the criteria at the end of it,
// which makes the query usually returning no data (because of the condition mix)
Query queryClone = new Query(query.getSelectDelegate(), query.getFromDelegate(), new Where<>(where.getCriteria()), new GroupBy(), new Having(), orderBy, limit);
QuerySQLBuilder sqlQueryBuilder = dialect.getQuerySQLBuilderFactory().queryBuilder(queryClone);
// selecting ids and their entity type
Map<Selectable<?>, ResultSetReader<?>> columnReaders = new HashMap<>();
queryClone.getColumns().forEach((selectable) -> columnReaders.put(selectable, dialect.getColumnBinderRegistry().getBinder((Column) selectable)));
Map<Class, Set<I>> idsPerSubtype = readIds(sqlQueryBuilder.toPreparableSQL().toPreparedSQL(values), columnReaders, queryClone.getAliases());
Set<I> ids = idsPerSubtype.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
if (hasSubPolymorphicPersister) {
LOGGER.debug("Asking sub-polymorphic persisters to load the entities");
Set<C> result = new HashSet<>();
idsPerSubtype.forEach((k, v) -> result.addAll(persisterPerSubclass.get(k).select(v)));
return result;
} else {
return selectWithSingleQueryWhereIdIn(ids);
}
}
private Map<Class, Set<I>> readIds(PreparedSQL preparedSQL, Map<Selectable<?>, ResultSetReader<?>> columnReaders, Map<Selectable<?>, String> aliases) {
// Below we keep the order of given entities mainly to get steady unit tests. Meanwhile, this may have performance
// impacts but it's very difficult to measure
Map<Class, Set<I>> result = new KeepOrderMap<>();
try (ReadOperation<Integer> readOperation = dialect.getReadOperationFactory().createInstance(preparedSQL, connectionProvider)) {
ResultSet resultSet = readOperation.execute();
ColumnedRowIterator resultSetIterator = new ColumnedRowIterator(resultSet, columnReaders, aliases);
resultSetIterator.forEachRemaining(row -> {
// looking for entity type on row : we read each subclass PK and check for nullity. The non-null one is the
// right one
Set<Entry<Class<C>, ConfiguredRelationalPersister<C, I>>> entries = persisterPerSubclass.entrySet();
Duo<Class, I> duo = null;
I identifier;
for (Entry<Class<C>, ConfiguredRelationalPersister<C, I>> entry : entries) {
identifier = entry.getValue().getMapping().getIdMapping().getIdentifierAssembler().assemble(row);
if (identifier != null) {
duo = new Duo<>(entry.getKey(), identifier);
break;
}
}
result.computeIfAbsent(duo.getLeft(), k -> new HashSet<>()).add(duo.getRight());
});
return result;
} catch (RuntimeException e) {
throw new SQLExecutionException(preparedSQL.getSQL(), e);
}
}
/**
* Appropriate {@link EntityJoinTree} to instantiate {@link JoinTableRootJoinNode} as root in order to handle join-node polymorphism of root entity.
* @param <C>
* @param <I>
* @author Guillaume Mary
*/
private static class SingleLoadEntityJoinTree<C, I> extends EntityJoinTree<C, I> {
public <T extends Table<T>> SingleLoadEntityJoinTree(ConfiguredRelationalPersister<C, I> mainPersister,
Set<? extends ConfiguredRelationalPersister<C, I>> subPersisters) {
super(tree -> new JoinTableRootJoinNode<>(
tree,
mainPersister,
subPersisters,
mainPersister.<T>getMapping().getSelectableColumns(),
(T) mainPersister.getMainTable())
);
}
@Override
public JoinTableRootJoinNode<C, I, ?> getRoot() {
return (JoinTableRootJoinNode<C, I, ?>) super.getRoot();
}
}
}