ElementCollectionResolver.java
package org.codefilarete.stalactite.engine.configurer.resolver.elementcollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import org.codefilarete.reflection.Accessor;
import org.codefilarete.reflection.Mutator;
import org.codefilarete.stalactite.engine.EntityPersister;
import org.codefilarete.stalactite.engine.cascade.AfterInsertCollectionCascader;
import org.codefilarete.stalactite.engine.configurer.elementcollection.ElementRecord;
import org.codefilarete.stalactite.engine.configurer.elementcollection.ElementRecordMapping;
import org.codefilarete.stalactite.engine.configurer.elementcollection.IndexedElementRecord;
import org.codefilarete.stalactite.engine.configurer.elementcollection.IndexedElementRecordMapping;
import org.codefilarete.stalactite.engine.configurer.model.ResolvedElementCollectionRelation;
import org.codefilarete.stalactite.engine.runtime.CollectionUpdater;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.stalactite.engine.runtime.SimpleRelationalEntityPersister;
import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithMappedAssociationEngine.AfterUpdateTrigger;
import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithMappedAssociationEngine.DeleteTargetEntitiesBeforeDeleteCascader;
import org.codefilarete.stalactite.mapping.DefaultEntityMapping;
import org.codefilarete.stalactite.mapping.EmbeddedClassMapping;
import org.codefilarete.stalactite.mapping.IdAccessor;
import org.codefilarete.stalactite.sql.ConnectionConfiguration;
import org.codefilarete.stalactite.sql.Dialect;
import org.codefilarete.stalactite.sql.ddl.structure.KeyMapping;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.tool.Duo;
import org.codefilarete.tool.Nullable;
import org.codefilarete.tool.collection.Iterables;
import static org.codefilarete.tool.Nullable.nullable;
public class ElementCollectionResolver {
private final Dialect dialect;
private final ConnectionConfiguration connectionConfiguration;
public ElementCollectionResolver(Dialect dialect, ConnectionConfiguration connectionConfiguration) {
this.dialect = dialect;
this.connectionConfiguration = connectionConfiguration;
}
public <SRC, SRCID, TRGT, TRGTID, S extends Collection<TRGT>, LEFTTABLE extends Table<LEFTTABLE>, COLLECTIONTABLE extends Table<COLLECTIONTABLE>,
ER extends ElementRecord<TRGT, SRCID>>
ElementRecordPersister<TRGT, SRCID, COLLECTIONTABLE, ER> resolve(ResolvedElementCollectionRelation<SRC, TRGT, S, SRCID, LEFTTABLE, COLLECTIONTABLE, ER> resolvedRelation,
ConfiguredRelationalPersister<SRC, SRCID> sourcePersister) {
ElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ER> elementCollectionMapping = (ElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ER>) buildCollectionMapping(resolvedRelation, sourcePersister);
// Note that table will be added to schema thanks to select cascade because join is added to source persister
ElementRecordPersister<TRGT, SRCID, COLLECTIONTABLE, ER> collectionPersister =
new ElementRecordPersister<>(elementCollectionMapping.elementRecordMapping, dialect, connectionConfiguration);
Accessor<SRC, Collection<ER>> collectionProviderForInsert = elementCollectionMapping.collectionProvider(resolvedRelation.getAccessor(), sourcePersister.getMapping(), false);
sourcePersister.addInsertListener(new TargetInstancesInsertCascader<>(collectionPersister, collectionProviderForInsert));
Mutator<Duo<SRC, SRC>, Boolean> collectionUpdater = new CollectionUpdater<SRC, ER, Collection<ER>>(
elementCollectionMapping.collectionProvider(resolvedRelation.getAccessor(), sourcePersister.getMapping(), true),
collectionPersister,
(o, i) -> { /* no reverse setter because we store only raw values */ },
true,
// we base our id policy on a particular identifier because Id is all the same for ElementCollection (it is source bean id)
ElementRecord::footprint) {
/**
* Overridden to force insertion of added entities because as a difference with default behavior (parent class), collection elements are
* not entities, so they can't be moved from a collection to another, hence they don't need to be updated, therefore there's no need to
* use {@code getElementPersister().persist(..)} mechanism. Even more : it is counterproductive (meaning false) because
* {@code persist(..)} uses {@code update(..)} when entities are considered already persisted (not {@code isNew()}), which is always the
* case for new {@link ElementRecord}
*/
@Override
protected void insertTargets(UpdateContext updateContext) {
collectionPersister.insert(updateContext.getAddedElements());
}
};
sourcePersister.addUpdateListener(new AfterUpdateTrigger<>(collectionUpdater));
// delete management (we provide persisted instances so they are perceived as deletable)
sourcePersister.addDeleteListener(new DeleteTargetEntitiesBeforeDeleteCascader<>(collectionPersister, elementCollectionMapping.collectionProvider(resolvedRelation.getAccessor(), sourcePersister.getMapping(), true)));
return collectionPersister;
}
private <SRC, SRCID, TRGT, S extends Collection<TRGT>, LEFTTABLE extends Table<LEFTTABLE>, COLLECTIONTABLE extends Table<COLLECTIONTABLE>, ER extends ElementRecord<TRGT, SRCID>>
ElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ElementRecord<TRGT, SRCID>> buildCollectionMapping(ResolvedElementCollectionRelation<SRC, TRGT, S, SRCID, LEFTTABLE, COLLECTIONTABLE, ER> resolvedRelation, ConfiguredRelationalPersister<SRC, SRCID> sourcePersister) {
ElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ElementRecord<TRGT, SRCID>> elementCollectionMapping;
if (resolvedRelation.isOrdered()) {
elementCollectionMapping = buildIndexedCollectionMapping((ResolvedElementCollectionRelation<SRC, TRGT, S, SRCID, LEFTTABLE, COLLECTIONTABLE, IndexedElementRecord<TRGT, SRCID>>) resolvedRelation, sourcePersister);
} else {
elementCollectionMapping = buildNonIndexedCollectionMapping(resolvedRelation, sourcePersister);
}
return elementCollectionMapping;
}
private <SRC, SRCID, TRGT, S extends Collection<TRGT>, LEFTTABLE extends Table<LEFTTABLE>, COLLECTIONTABLE extends Table<COLLECTIONTABLE>, ER extends ElementRecord<TRGT, SRCID>>
ElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ElementRecord<TRGT, SRCID>> buildNonIndexedCollectionMapping(ResolvedElementCollectionRelation<SRC, TRGT, S, SRCID, LEFTTABLE, COLLECTIONTABLE, ER> resolvedRelation, ConfiguredRelationalPersister<SRC, SRCID> sourcePersister) {
EmbeddedClassMapping<ER, COLLECTIONTABLE> elementRecordMappingStrategy = new EmbeddedClassMapping<ER, COLLECTIONTABLE>((Class) ElementRecord.class,
resolvedRelation.getJoin().getRightKey().getTable(),
resolvedRelation.getColumnMapping());
ElementRecordMapping<TRGT, SRCID, COLLECTIONTABLE, ER> elementRecordMapping = new ElementRecordMapping<>(
resolvedRelation.getJoin().getRightKey().getTable(),
elementRecordMappingStrategy,
sourcePersister.getMapping().getIdMapping().getIdentifierAssembler(),
resolvedRelation.getPrimaryKeyForeignKeyColumnMapping());
ElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ER> elementCollectionMapping = new ElementCollectionMapping<>(resolvedRelation.getJoin().getKeyMapping(), elementRecordMapping);
return (ElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ElementRecord<TRGT, SRCID>>) elementCollectionMapping;
}
private <SRC, SRCID, TRGT, S extends Collection<TRGT>, LEFTTABLE extends Table<LEFTTABLE>, COLLECTIONTABLE extends Table<COLLECTIONTABLE>, ER extends IndexedElementRecord<TRGT, SRCID>>
ElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ElementRecord<TRGT, SRCID>> buildIndexedCollectionMapping(ResolvedElementCollectionRelation<SRC, TRGT, S, SRCID, LEFTTABLE, COLLECTIONTABLE, ER> resolvedRelation, ConfiguredRelationalPersister<SRC, SRCID> sourcePersister) {
EmbeddedClassMapping<ER, COLLECTIONTABLE> elementRecordMappingStrategy = new EmbeddedClassMapping<ER, COLLECTIONTABLE>((Class) IndexedElementRecord.class,
resolvedRelation.getJoin().getRightKey().getTable(),
resolvedRelation.getColumnMapping());
IndexedElementRecordMapping<TRGT, SRCID, COLLECTIONTABLE, ER> elementRecordMapping = new IndexedElementRecordMapping<>(
resolvedRelation.getJoin().getRightKey().getTable(),
elementRecordMappingStrategy,
resolvedRelation.getIndexColumn(),
sourcePersister.getMapping().getIdMapping().getIdentifierAssembler(),
resolvedRelation.getPrimaryKeyForeignKeyColumnMapping());
IndexedElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ? extends ElementRecord<TRGT, SRCID>> indexedElementCollectionMapping = new IndexedElementCollectionMapping<>(
resolvedRelation.getJoin().getKeyMapping(),
elementRecordMapping);
return (ElementCollectionMapping<SRC, SRCID, TRGT, S, LEFTTABLE, COLLECTIONTABLE, ElementRecord<TRGT, SRCID>>) indexedElementCollectionMapping;
}
private static class ElementCollectionMapping<SRC, SRCID, TRGT, S extends Collection<TRGT>, SRCTABLE extends Table<SRCTABLE>, COLLECTIONTABLE extends Table<COLLECTIONTABLE>, ER extends ElementRecord<TRGT, SRCID>> {
public final KeyMapping<SRCTABLE, COLLECTIONTABLE, SRCID> joinMapping;
public final DefaultEntityMapping<ER, ER, COLLECTIONTABLE> elementRecordMapping;
public ElementCollectionMapping(KeyMapping<SRCTABLE, COLLECTIONTABLE, SRCID> joinMapping, DefaultEntityMapping<ER, ER, COLLECTIONTABLE> elementRecordMapping) {
this.joinMapping = joinMapping;
this.elementRecordMapping = elementRecordMapping;
}
protected Accessor<SRC, Collection<ER>> collectionProvider(Accessor<SRC, S> collectionAccessor,
IdAccessor<SRC, SRCID> idAccessor,
boolean markAsPersisted) {
return src -> Iterables.collect(Nullable.nullable(collectionAccessor.get(src)).getOr(() -> (S) new ArrayList<>()),
trgt -> (ER) new ElementRecord<>(idAccessor.getId(src), trgt).setPersisted(markAsPersisted),
HashSet::new);
}
}
private static class IndexedElementCollectionMapping<SRC, SRCID, TRGT, S extends Collection<TRGT>, SRCTABLE extends Table<SRCTABLE>, COLLECTIONTABLE extends Table<COLLECTIONTABLE>, ER extends IndexedElementRecord<TRGT, SRCID>>
extends ElementCollectionMapping<SRC, SRCID, TRGT, S, SRCTABLE, COLLECTIONTABLE, ER> {
public IndexedElementCollectionMapping(KeyMapping<SRCTABLE, COLLECTIONTABLE, SRCID> joinMapping, DefaultEntityMapping<ER, ER, COLLECTIONTABLE> elementRecordMapping) {
super(joinMapping, elementRecordMapping);
}
@Override
protected Accessor<SRC, Collection<ER>> collectionProvider(Accessor<SRC, S> collectionAccessor,
IdAccessor<SRC, SRCID> idAccessor,
boolean markAsPersisted) {
return src -> {
S collection = nullable(collectionAccessor.get(src)).getOr(() -> (S) new ArrayList<>());
return Iterables.collect(collection,
trgt -> (ER) new IndexedElementRecord<>(idAccessor.getId(src), trgt, Iterables.indexOf(collection, trgt)).setPersisted(markAsPersisted),
HashSet::new);
};
}
}
private static class TargetInstancesInsertCascader<SRC, TRGT, SRCID, ER extends ElementRecord<TRGT, SRCID>> extends AfterInsertCollectionCascader<SRC, ER> {
private final Accessor<SRC, ? extends Collection<ER>> collectionGetter;
public TargetInstancesInsertCascader(EntityPersister<ER, ER> targetPersister, Accessor<SRC, ? extends Collection<ER>> collectionGetter) {
super(targetPersister);
this.collectionGetter = collectionGetter;
}
@Override
protected void postTargetInsert(Iterable<? extends ER> entities) {
// Nothing to do. Identified#isPersisted flag should be fixed by target persister
}
@Override
protected Collection<ER> getTargets(SRC source) {
return collectionGetter.get(source);
}
}
public static class ElementRecordPersister<TRGT, SRCID, T extends Table<T>, ER extends ElementRecord<TRGT, SRCID>> extends SimpleRelationalEntityPersister<ER, ER, T> {
public ElementRecordPersister(DefaultEntityMapping<ER, ER, T> elementRecordMapping, Dialect dialect, ConnectionConfiguration connectionConfiguration) {
super(elementRecordMapping, dialect, connectionConfiguration);
}
}
}