SkeletonAggregateResolver.java
package org.codefilarete.stalactite.engine.configurer.resolver;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.codefilarete.reflection.PropertyMutator;
import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
import org.codefilarete.stalactite.engine.PersistenceContext;
import org.codefilarete.stalactite.engine.PersisterRegistry;
import org.codefilarete.stalactite.engine.configurer.DefaultComposedIdentifierAssembler;
import org.codefilarete.stalactite.engine.configurer.builder.embeddable.EmbeddableMapping;
import org.codefilarete.stalactite.engine.configurer.dslresolver.AssignedByAnotherIdentifierMapping;
import org.codefilarete.stalactite.engine.configurer.dslresolver.CompositeIdentifierMapping;
import org.codefilarete.stalactite.engine.configurer.dslresolver.SingleIdentifierMapping;
import org.codefilarete.stalactite.engine.configurer.model.AncestorJoin;
import org.codefilarete.stalactite.engine.configurer.model.DirectRelationJoin;
import org.codefilarete.stalactite.engine.configurer.model.Entity;
import org.codefilarete.stalactite.engine.configurer.model.Entity.Versioning;
import org.codefilarete.stalactite.engine.configurer.model.ExtraTableJoin;
import org.codefilarete.stalactite.engine.configurer.model.IdentifierMapping;
import org.codefilarete.stalactite.engine.listener.DeleteListener;
import org.codefilarete.stalactite.engine.listener.InsertListener;
import org.codefilarete.stalactite.engine.listener.UpdateListener;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.stalactite.engine.runtime.RelationalEntityPersister;
import org.codefilarete.stalactite.engine.runtime.SimpleRelationalEntityPersister;
import org.codefilarete.stalactite.engine.runtime.WriteExecutor.JDBCBatchingIterator;
import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree;
import org.codefilarete.stalactite.engine.runtime.load.EntityMerger.EntityMergerAdapter;
import org.codefilarete.stalactite.mapping.ComposedIdMapping;
import org.codefilarete.stalactite.mapping.DefaultEntityMapping;
import org.codefilarete.stalactite.mapping.EntityMapping;
import org.codefilarete.stalactite.mapping.IdMapping;
import org.codefilarete.stalactite.mapping.SimpleIdMapping;
import org.codefilarete.stalactite.mapping.id.assembly.SingleIdentifierAssembler;
import org.codefilarete.stalactite.mapping.id.manager.AlreadyAssignedIdentifierManager;
import org.codefilarete.stalactite.mapping.id.manager.CompositeKeyAlreadyAssignedIdentifierInsertionManager;
import org.codefilarete.stalactite.mapping.id.manager.IdentifierInsertionManager;
import org.codefilarete.stalactite.sql.ddl.structure.Column;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.statement.WriteOperation;
import org.codefilarete.tool.Duo;
import org.codefilarete.tool.collection.Iterables;
import static org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.JoinType.INNER;
/**
* Class aims at solving the structural "bones" of an {@link Entity}, which means the mapping of:
* - identifier
* - direct properties, including embedded ones
* - inheritance (ancestors, not polymorphism)
*
* This logic was extended to properties on extra tables.
*/
public class SkeletonAggregateResolver {
private final PersistenceContext persistenceContext;
private final PersisterRegistry persisterRegistry;
public SkeletonAggregateResolver(PersistenceContext persistenceContext) {
this(persistenceContext, persistenceContext.getPersisterRegistry());
}
SkeletonAggregateResolver(PersistenceContext persistenceContext, PersisterRegistry persisterRegistry) {
this.persistenceContext = persistenceContext;
this.persisterRegistry = persisterRegistry;
}
public <B, C extends B, I, T extends Table<T>>
ConfiguredRelationalPersister<C, I> buildPersister(Entity<C, I, T> rootEntity) {
// TODO: check for ealready existing persister in the persistence context
// TODO: wrap result in an OptimizedUpdatePersister
// TODO: be inspired from DefaultPersisterBuilder.build()
IdMapping<C, I> idMapping = createIdMapping(rootEntity);
Versioning<C, ?, T> versioning = rootEntity.getVersioning();
DefaultEntityMapping<C, I, T> entityMapping = new DefaultEntityMapping<>(
rootEntity.getEntityType(), rootEntity.getTable(),
rootEntity.getPropertyMappingHolder().getWritablePropertiesPerAccessor(), rootEntity.getPropertyMappingHolder().getReadonlyPropertiesPerAccessor(),
versioning == null ? null : new Duo<>(versioning.getVersioningAccessor(), versioning.getVersioningColumn()),
idMapping,
null,
false
);
SimpleRelationalEntityPersister<C, I, T> rootPersister = new SimpleRelationalEntityPersister<>(
entityMapping,
persistenceContext.getDialect(),
persistenceContext.getConnectionConfiguration()
);
// when identifier policy is already-assigned one, we must ensure that entity is marked as persisted when it comes back from database
// because user may forget to / can't mark it as such
IdentifierInsertionManager<C, I> identifierInsertionManager = rootPersister.getMapping().getIdMapping().getIdentifierInsertionManager();
if (identifierInsertionManager instanceof AlreadyAssignedIdentifierManager) {
// Transferring identifier manager InsertListener to here
rootPersister.addInsertListener(((AlreadyAssignedIdentifierManager<C, I>) identifierInsertionManager).getInsertListener());
rootPersister.addSelectListener(((AlreadyAssignedIdentifierManager<C, I>) identifierInsertionManager).getSelectListener());
}
appendInheritance(rootEntity, rootPersister);
return rootPersister;
}
private <B, C extends B, I, T extends Table<T>, LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>>
void appendInheritance(Entity<C, I, T> entity, SimpleRelationalEntityPersister<C, I, T> result) {
// looking for extra tables: they are stored as ExtraTableJoin in the entity relations
entity.getRelations().stream()
.filter(ExtraTableJoin.class::isInstance).map(ExtraTableJoin.class::cast).forEach(extraTableJoin -> {
sewExtraTables(extraTableJoin, result, result, entity, EntityJoinTree.ROOT_JOIN_NAME);
});
AncestorJoin<B, LEFTTABLE, RIGHTTABLE, I> parent = (AncestorJoin<B, LEFTTABLE, RIGHTTABLE, I>) entity.getParent();
while (parent != null) {
SimpleRelationalEntityPersister<B, I, RIGHTTABLE> parentPersister = sewParentEntity(parent, result);
DirectRelationJoin<LEFTTABLE, RIGHTTABLE, I> join = parent.getJoin();
EntityMapping<B, I, RIGHTTABLE> mapping = parentPersister.getMapping();
String joinName = result.getEntityJoinTree().addMergeJoin(
EntityJoinTree.ROOT_JOIN_NAME,
new EntityMergerAdapter<>(mapping),
join.getLeftKey(),
join.getRightKey(),
INNER);
Entity<B, I, RIGHTTABLE> ancestorEntity = parent.getAncestor();
ancestorEntity.getRelations().stream()
.filter(ExtraTableJoin.class::isInstance).map(ExtraTableJoin.class::cast).forEach(extraTableJoin -> {
// we join the extra table persister to the result persister, not to the ancestor one, to create
// a single complete aggregate query instead of multiple partial ones that would need to be
// reconciled later
sewExtraTables(extraTableJoin, result, parentPersister, ancestorEntity, joinName);
});
// preparing next iteration
parent = (AncestorJoin<B, LEFTTABLE, RIGHTTABLE, I>) parent.getAncestor().getParent();
}
}
@Nullable
private <B, C extends B, I, T extends Table<T>> IdMapping<C, I> createIdMapping(Entity<C, I, T> entity) {
IdMapping<C, I> idMapping = null;
IdentifierMapping<C, I> identifierMapping = entity.getIdentifierMapping();
if (identifierMapping instanceof SingleIdentifierMapping<?, ?>) {
Column<T, I> column = (Column<T, I>) Iterables.first(entity.getTable().getPrimaryKey().getColumns());
idMapping = new SimpleIdMapping<>(
entity.getIdAccessor(),
identifierMapping.getIdentifierInsertionManager(),
new SingleIdentifierAssembler<>(column));
} else if (identifierMapping instanceof CompositeIdentifierMapping) {
CompositeIdentifierMapping<C, I, T> compositeIdentifierMapping = (CompositeIdentifierMapping<C, I, T>) identifierMapping;
EmbeddableMapping<I, T> build = compositeIdentifierMapping.getCompositeKeyMapping();
Map<ReadWritePropertyAccessPoint<I, Object>, Column<T, Object>> compositeKeyMapping = build.getMapping();
CompositeKeyAlreadyAssignedIdentifierInsertionManager<C, I> identifierInsertionManager = compositeIdentifierMapping.getIdentifierInsertionManager();
idMapping = new ComposedIdMapping<>(
entity.getIdAccessor(),
identifierInsertionManager,
new DefaultComposedIdentifierAssembler<>(entity.getTable(), identifierInsertionManager.getIdentifierType(), compositeKeyMapping));
} else if (identifierMapping instanceof AssignedByAnotherIdentifierMapping) {
idMapping = mimicIdMapping(identifierMapping, entity.getTable());
}
return idMapping;
}
private <B, C extends B, I, T extends Table<T>> IdMapping<C, I> mimicIdMapping(IdentifierMapping<C, I> identifierMapping, T targetTable) {
IdMapping<C, I> result;
if (identifierMapping instanceof AssignedByAnotherIdentifierMapping) {
result = mimicIdMapping(((AssignedByAnotherIdentifierMapping<C, I>) identifierMapping).getSource(), targetTable);
} else if (identifierMapping instanceof SingleIdentifierMapping) {
Column<T, I> column = (Column<T, I>) Iterables.first(targetTable.getPrimaryKey().getColumns());
SingleIdentifierAssembler<I, T> identifierAssembler = new SingleIdentifierAssembler<>(column);
result = new SimpleIdMapping<>(
identifierMapping.getIdAccessor(),
new BasicIdentifierInsertionManager<>(identifierMapping.getIdentifierInsertionManager().getIdentifierType()),
identifierAssembler);
} else {
// propagating the composite identifier to the actual entity and table
CompositeKeyAlreadyAssignedIdentifierInsertionManager<C, I> identifierInsertionManager = (CompositeKeyAlreadyAssignedIdentifierInsertionManager<C, I>) identifierMapping.getIdentifierInsertionManager();
EmbeddableMapping<I, ?> build = ((CompositeIdentifierMapping<C, I, ?>) identifierMapping).getCompositeKeyMapping();
Map<ReadWritePropertyAccessPoint<I, Object>, Column<T, Object>> entityTableKeyMapping = new HashMap<>();
build.getMapping().forEach((accessor, column) -> {
Column<T, Object> entityColumn = targetTable.getColumn(column.getName());
entityTableKeyMapping.put(accessor, entityColumn);
});
entityTableKeyMapping.values().forEach(Column::primaryKey);
result = new ComposedIdMapping<>(
identifierMapping.getIdAccessor(),
identifierInsertionManager,
new DefaultComposedIdentifierAssembler<>(
targetTable,
identifierInsertionManager.getIdentifierType(),
entityTableKeyMapping));
}
return result;
}
private <C, I, LEFTTABLE extends Table<LEFTTABLE>, EXTRATABLE extends Table<EXTRATABLE>>
SimpleRelationalEntityPersister<C, I, EXTRATABLE> buildExtraTablePersister(ExtraTableJoin<C, LEFTTABLE, EXTRATABLE, I> extraTableJoin,
SimpleRelationalEntityPersister<C, I, LEFTTABLE> owningPersister,
Entity<C, I, LEFTTABLE> identifierDefiner) {
EXTRATABLE extratable = extraTableJoin.getJoin().getRightKey().getTable();
IdMapping<C, I> idMapping = mimicIdMapping(identifierDefiner.getIdentifierMapping(), extratable);
DefaultEntityMapping<C, I, EXTRATABLE> entityMapping = new DefaultEntityMapping<>(
identifierDefiner.getEntityType(), extratable,
extraTableJoin.getPropertyMappingHolder().getWritablePropertiesPerAccessor(), extraTableJoin.getPropertyMappingHolder().getReadonlyPropertiesPerAccessor(),
// no versioning for extra table persister (make no sense because it's managed by the "trunk" persister)
null,
idMapping,
// entity factory makes no sense for extra table properties because the values will be merged through
// a merge join, thus there won't be any call to the row transformer to create an instance
null,
false
);
SimpleRelationalEntityPersister<C, I, EXTRATABLE> extraTablePersister = new SimpleRelationalEntityPersister<>(
entityMapping,
persistenceContext.getDialect(),
persistenceContext.getConnectionConfiguration()
);
owningPersister.addInsertListener(new InsertListener<C>() {
@Override
public void afterInsert(Iterable<? extends C> entities) {
extraTablePersister.insert(entities);
}
});
owningPersister.addUpdateListener(new UpdateListener<C>() {
@Override
public void afterUpdate(Iterable<? extends Duo<C, C>> entities, boolean allColumnsStatement) {
extraTablePersister.update(entities, allColumnsStatement);
}
});
owningPersister.addDeleteListener(new DeleteListener<C>() {
@Override
public void beforeDelete(Iterable<? extends C> entities) {
extraTablePersister.delete(entities);
}
});
return extraTablePersister;
}
private <B, C extends B, I, LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>, EXTRATABLE extends Table<EXTRATABLE>>
void sewExtraTables(ExtraTableJoin<B, RIGHTTABLE, EXTRATABLE, I> extraTableJoin,
SimpleRelationalEntityPersister<C, I, LEFTTABLE> owningPersister,
SimpleRelationalEntityPersister<B, I, RIGHTTABLE> cascader,
Entity<B, I, RIGHTTABLE> entity,
String owningJoinName) {
SimpleRelationalEntityPersister<B, I, EXTRATABLE> extraTablePersister = buildExtraTablePersister(extraTableJoin, cascader, entity);
owningPersister.getEntityJoinTree().addMergeJoin(
owningJoinName,
new EntityMergerAdapter<>(extraTablePersister.getMapping()),
extraTableJoin.getJoin().getLeftKey(),
extraTableJoin.getJoin().getRightKey(),
INNER);
}
private <B, C extends B, I, LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>>
SimpleRelationalEntityPersister<B, I, RIGHTTABLE> sewParentEntity(AncestorJoin<B, LEFTTABLE, RIGHTTABLE, I> parent, RelationalEntityPersister<C, I> child) {
Map<ReadWritePropertyAccessPoint<B, ?>, Column<RIGHTTABLE, ?>> writablePropertyToColumn = new HashMap<>();
parent.getAncestor().getPropertyMappingHolder().getWritablePropertyToColumn().forEach(propertyMapping -> {
writablePropertyToColumn.put(propertyMapping.getAccessPoint(), propertyMapping.getColumn());
});
Map<PropertyMutator<B, ?>, Column<RIGHTTABLE, ?>> readonlyPropertyToColumn = new HashMap<>();
parent.getAncestor().getPropertyMappingHolder().getReadonlyPropertyToColumn().forEach(propertyMapping -> {
readonlyPropertyToColumn.put(propertyMapping.getAccessPoint(), propertyMapping.getColumn());
});
IdMapping<B, I> idMapping = createIdMapping(parent.getAncestor());
Versioning<B, ?, RIGHTTABLE> versioning = parent.getAncestor().getVersioning();
DefaultEntityMapping<B, I, RIGHTTABLE> entityMapping = new DefaultEntityMapping<>(
parent.getAncestor().getEntityType(), parent.getAncestor().getTable(),
writablePropertyToColumn, readonlyPropertyToColumn,
versioning == null ? null : new Duo<>(versioning.getVersioningAccessor(), versioning.getVersioningColumn()),
idMapping,
null,
false
);
SimpleRelationalEntityPersister<B, I, RIGHTTABLE> ancestorPersister = new SimpleRelationalEntityPersister<>(
entityMapping,
persistenceContext.getDialect(),
persistenceContext.getConnectionConfiguration()
);
persisterRegistry.addPersister(ancestorPersister);
child.addInsertListener(new InsertListener<C>() {
@Override
public void beforeInsert(Iterable<? extends C> entities) {
ancestorPersister.insert(entities);
}
});
child.addUpdateListener(new UpdateListener<C>() {
@Override
public void afterUpdate(Iterable<? extends Duo<C, C>> entities, boolean allColumnsStatement) {
ancestorPersister.update((Iterable) entities, allColumnsStatement);
}
});
child.addDeleteListener(new DeleteListener<C>() {
@Override
public void afterDelete(Iterable<? extends C> entities) {
ancestorPersister.delete(entities);
}
});
return ancestorPersister;
}
/**
* Basic {@link IdentifierInsertionManager} that provides a simple JDBC batching iterator to write entities
* to the table.
*
* @param <C> the entity type to write
* @param <I> the entity identifier type
* @see JDBCBatchingIterator
*/
private static class BasicIdentifierInsertionManager<C, I> implements IdentifierInsertionManager<C, I> {
private final Class<I> memberType;
public BasicIdentifierInsertionManager(Class<I> memberType) {
this.memberType = memberType;
}
@Override
public Class<I> getIdentifierType() {
return memberType;
}
@Override
public JDBCBatchingIterator<C> buildJDBCBatchingIterator(Iterable<? extends C> entities, WriteOperation<? extends Column<? extends Table, ?>> writeOperation, int batchSize) {
return new JDBCBatchingIterator<>(entities, writeOperation, batchSize);
}
}
}