TablePerClassPolymorphicRelationJoinNode.java
package org.codefilarete.stalactite.engine.runtime.load;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Set;
import org.codefilarete.reflection.Accessor;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.JoinType;
import org.codefilarete.stalactite.engine.runtime.load.EntityTreeInflater.RelationIdentifier;
import org.codefilarete.stalactite.engine.runtime.load.EntityTreeInflater.TreeInflationContext;
import org.codefilarete.stalactite.engine.runtime.load.JoinRowConsumer.ForkJoinRowConsumer;
import org.codefilarete.stalactite.query.model.JoinLink;
import org.codefilarete.stalactite.query.model.QueryStatement;
import org.codefilarete.stalactite.query.model.QueryStatement.PseudoTable;
import org.codefilarete.stalactite.query.model.Selectable;
import org.codefilarete.stalactite.query.model.Union;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Key;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.result.BeanRelationFixer;
import org.codefilarete.stalactite.sql.result.ColumnedRow;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.collection.Iterables;
import static org.codefilarete.tool.Nullable.nullable;
/**
* Particular {@link JoinNode} made to handle relation from an entity to a collection of some another polymorphic one. Actually relation doesn't
* make the complexity of that class: polymorphic entity instantiation is the core focus of it. Here are the hot spots:
* identifier is given by the subclass which find its id in the row (see {@link TablePerClassPolymorphicRelationJoinRowConsumer#giveIdentifier}),
* and while doing it, it remembers which consumer made it. Then while instantiating entity we invoke it to get right entity type (parent mapping
* would only give parent entity, which, out of being a wrong approach, can be an abstract type). Then instance is filled up with parent properties
* by calling merging method (see line 98).
* Finally, {@link TablePerClassPolymorphicRelationJoinRowConsumer} must extend {@link ForkJoinRowConsumer} to give next branch to be consumed by
* {@link EntityTreeInflater} to avoid "dead" branch to be read : we give it according to the consumer which found the identifier.
*
* @author Guillaume Mary
*/
public class TablePerClassPolymorphicRelationJoinNode<C, T1 extends Table<T1>, JOINCOLTYPE, I> extends RelationJoinNode<C, T1, PseudoTable, JOINCOLTYPE, I> {
private static IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>> collectColumnClones(Key<?, ?> rightJoinLink, Set<? extends JoinLink<?, ?>> columnsToSelect) {
IdentityHashMap<JoinLink<?, ?>, JoinLink<?, ?>> result = new IdentityHashMap<>();
rightJoinLink.getTable().getColumns().forEach(column -> {
result.put((JoinLink<?, ?>) column, (JoinLink<?, ?>) column);
});
columnsToSelect.forEach(column -> {
result.put(column, column);
});
return result;
}
private final Set<SubPersisterAndDiscriminator<? extends C>> subPersisters = new HashSet<>();
private final PseudoTable pseudoTable;
private final Union.PseudoColumn<Integer> discriminatorColumn;
private final Key<PseudoTable, JOINCOLTYPE> pseudoRightJoinLink;
public TablePerClassPolymorphicRelationJoinNode(JoinNode<?, T1> parent,
Union subPersistersUnion,
Accessor<?, ?> propertyAccessor,
Key<T1, JOINCOLTYPE> leftJoinColumn,
Key<?, JOINCOLTYPE> rightJoinColumn,
JoinType joinType,
Set<? extends JoinLink<?, ?>> columnsToSelect,
@Nullable String tableAlias,
EntityInflater<C, I> entityInflater,
BeanRelationFixer<Object, C> beanRelationFixer,
Union.PseudoColumn<Integer> discriminatorColumn) {
super(parent, propertyAccessor, leftJoinColumn, (Key) rightJoinColumn, joinType, columnsToSelect, tableAlias, entityInflater, beanRelationFixer, null,
collectColumnClones(rightJoinColumn, columnsToSelect));
this.pseudoTable = subPersistersUnion.asPseudoTable(getTableAlias());
super.getOriginalColumnsToLocalOnes().clear();
super.getOriginalColumnsToLocalOnes().putAll(collectColumnClones(rightJoinColumn, pseudoTable.getColumns()));
this.discriminatorColumn = discriminatorColumn;
// rebuilding the right join link to make it match the union table, because the given one has columns from the abstract (parent) entity
// which doesn't have a matching / existing table
Key.KeyBuilder<PseudoTable, JOINCOLTYPE> pseudoRightJoinLinkBuilder = Key.from(pseudoTable);
rightJoinColumn.getColumns()
.forEach(column -> {
QueryStatement.PseudoColumn pseudoColumn1 = Iterables.find(pseudoTable.getColumns(), pseudoColumn -> pseudoColumn.getExpression().equals(column.getExpression()));
pseudoRightJoinLinkBuilder.addColumn(pseudoColumn1);
});
this.pseudoRightJoinLink = pseudoRightJoinLinkBuilder.build();
}
@Override
public PseudoTable getRightTable() {
return pseudoTable;
}
@Override
public Key<PseudoTable, JOINCOLTYPE> getRightJoinLink() {
return pseudoRightJoinLink;
}
/**
* Overridden, else it returns the ones given at construction time which are from Union, meaning getOwner() is not
* {@link PseudoTable} which, out of throwing a {@link ClassCastException}, avoid to give access to correct
* {@link Column}
*/
@Override
public Set<Selectable<?>> getColumnsToSelect() {
return (Set) pseudoTable.getColumns();
}
@Override
public TablePerClassPolymorphicRelationJoinRowConsumer toConsumer(JoinNode<C, PseudoTable> joinNode) {
return new TablePerClassPolymorphicRelationJoinRowConsumer(joinNode, discriminatorColumn, getConsumptionListener());
}
public <D extends C> void addSubPersisterJoin(PolymorphicMergeJoinRowConsumer<D, I> subPersisterJoin, int discriminatorValue) {
this.subPersisters.add(new SubPersisterAndDiscriminator<>(subPersisterJoin, discriminatorValue));
}
private class SubPersisterAndDiscriminator<D extends C> {
private final PolymorphicMergeJoinRowConsumer<D, I> subPersisterJoin;
private final int discriminatorValue;
public SubPersisterAndDiscriminator(PolymorphicMergeJoinRowConsumer<D, I> subPersisterJoin, int discriminatorValue) {
this.subPersisterJoin = subPersisterJoin;
this.discriminatorValue = discriminatorValue;
}
}
public class TablePerClassPolymorphicRelationJoinRowConsumer implements RelationJoinRowConsumer<C, I>, ForkJoinRowConsumer {
private final ThreadLocal<RowIdentifier<? extends C>> currentlyFoundConsumer = new ThreadLocal<>();
private final JoinNode<C, PseudoTable> joinNode;
/** Optional listener of ResultSet decoding */
@Nullable
private final EntityTreeJoinNodeConsumptionListener<C> consumptionListener;
private final Union.PseudoColumn<Integer> discriminatorColumn;
private TablePerClassPolymorphicRelationJoinRowConsumer(JoinNode<C, PseudoTable> joinNode,
Union.PseudoColumn<Integer> discriminatorColumn,
@Nullable EntityTreeJoinNodeConsumptionListener<C> consumptionListener) {
this.joinNode = joinNode;
this.consumptionListener = consumptionListener;
this.discriminatorColumn = discriminatorColumn;
}
@Override
public JoinNode<C, ?> getNode() {
return joinNode;
}
@Override
public C applyRelatedEntity(Object parentJoinEntity, ColumnedRow row, TreeInflationContext context) {
RowIdentifier<C> rowIdentifier = giveIdentifier(row);
currentlyFoundConsumer.set(rowIdentifier);
if (rowIdentifier == null) {
return null;
} else {
I rightIdentifier = rowIdentifier.entityIdentifier;
// we avoid treating twice same relation, overall to avoid adding twice same instance to a collection (one-to-many list cases)
// in case of multiple collections in ResultSet because it creates similar data (through cross join) which are treated as many as
// collections cross with each other. This also works for one-to-one relations but produces no bugs. It can also be seen as a performance
// enhancement even if it hasn't been measured.
RelationIdentifier eventuallyApplied = new RelationIdentifier(
parentJoinEntity,
getEntityType(),
rightIdentifier,
this);
// primary key null means no entity => nothing to do
if (rightIdentifier != null && context.isTreatedOrAppend(eventuallyApplied)) {
C rightEntity = context.giveEntityFromCache(getEntityType(), rightIdentifier, () -> rowIdentifier.rowConsumer.transform(row));
getBeanRelationFixer().apply(parentJoinEntity, rightEntity);
if (this.consumptionListener != null) {
this.consumptionListener.onNodeConsumption(rightEntity, row);
}
return rightEntity;
} else {
return null;
}
}
}
@Nullable
/* Optimized, from 530 000 nanos to 65 000 nanos at 1st exec, from 40 000 nanos to 12 000 nanos on usual run */
private <D extends C> RowIdentifier<D> giveIdentifier(ColumnedRow row) {
// @Optimized : use for & return instead of stream().map().filter(notNull).findFirst()
Integer discriminatorValue = row.get(discriminatorColumn);
if (discriminatorValue != null) {
SubPersisterAndDiscriminator<D> discriminatorConsumer = (SubPersisterAndDiscriminator) Iterables.find(subPersisters, o -> o.discriminatorValue == discriminatorValue);
if (discriminatorConsumer != null) {
I identifier = discriminatorConsumer.subPersisterJoin.giveIdentifier(row);
if (identifier != null) {
return new RowIdentifier<>(identifier, discriminatorConsumer.subPersisterJoin);
}
}
}
return null;
}
@Override
public JoinRowConsumer giveNextConsumer() {
return nullable(currentlyFoundConsumer.get()).map(rowIdentifier -> rowIdentifier.rowConsumer).get();
}
@Override
public void afterRowConsumption(TreeInflationContext context) {
currentlyFoundConsumer.remove();
}
private class RowIdentifier<D extends C> {
private final I entityIdentifier;
private final PolymorphicMergeJoinRowConsumer<D, I> rowConsumer;
private RowIdentifier(I entityIdentifier, PolymorphicMergeJoinRowConsumer<D, I> rowConsumer) {
this.entityIdentifier = entityIdentifier;
this.rowConsumer = rowConsumer;
}
}
/**
* Implemented for debug. DO NOT RELY ON IT for anything else.
*/
@Override
public String toString() {
return Reflections.toString(this.getClass());
}
}
}