# OneToManyWithMappedAssociationEngine.java

    package org.codefilarete.stalactite.engine.runtime.onetomany;

    import java.util.Collection;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.function.BiConsumer;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;

    import org.codefilarete.reflection.AccessorDefinition;
    import org.codefilarete.reflection.ReversibleAccessor;
    import org.codefilarete.stalactite.dsl.idpolicy.GeneratedKeysPolicy;
    import org.codefilarete.stalactite.engine.EntityPersister;
    import org.codefilarete.stalactite.engine.cascade.AfterInsertCollectionCascader;
    import org.codefilarete.stalactite.engine.cascade.BeforeDeleteByIdCollectionCascader;
    import org.codefilarete.stalactite.engine.cascade.BeforeDeleteCollectionCascader;
    import org.codefilarete.stalactite.engine.configurer.onetomany.FirstPhaseCycleLoadListener;
    import org.codefilarete.stalactite.engine.listener.DeleteByIdListener;
    import org.codefilarete.stalactite.engine.listener.DeleteListener;
    import org.codefilarete.stalactite.engine.listener.SelectListener;
    import org.codefilarete.stalactite.engine.listener.UpdateListener;
    import org.codefilarete.stalactite.engine.runtime.CollectionUpdater;
    import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
    import org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.JoinType;
    import org.codefilarete.stalactite.mapping.Mapping.ShadowColumnValueProvider;
    import org.codefilarete.stalactite.mapping.id.assembly.IdentifierAssembler;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Key;
    import org.codefilarete.stalactite.sql.ddl.structure.Key.KeyBuilder;
    import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.tool.Duo;
    import org.codefilarete.tool.Nullable;
    import org.codefilarete.tool.collection.IdentityMap;
    import org.codefilarete.tool.collection.Iterables;

    import static org.codefilarete.stalactite.engine.runtime.load.EntityJoinTree.ROOT_JOIN_NAME;
    import static org.codefilarete.tool.Nullable.nullable;
    import static org.codefilarete.tool.collection.Iterables.stream;

    /**
     * @author Guillaume Mary
     */
    public class OneToManyWithMappedAssociationEngine<SRC, TRGT, SRCID, TRGTID, C extends Collection<TRGT>, RIGHTTABLE extends Table<RIGHTTABLE>> {

    	protected final ConfiguredRelationalPersister<SRC, SRCID> sourcePersister;

    	protected final ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister;

    	protected final MappedManyRelationDescriptor<SRC, TRGT, C, SRCID> manyRelationDescriptor;

    	/**
    	 * Foreign key column value store, for insert, update and delete cases : stores parent entity value per child entity,
    	 * (parent can be a null if child was removed from relation).
    	 * Implemented as a ThreadLocal because we can hardly cross layers and methods to pass such a value.
    	 * Cleaned after update and delete.
    	 */
    	protected final ThreadLocal<TargetToSourceRelationStorage> currentTargetToSourceRelationStorage = new ThreadLocal<>();

    	/**
    	 * Storage of relation between TRGT and SRC entities to avoid to depend on "mapped by" properties which is optional.
    	 * Foreign key maintenance code will refer to it.
    	 * @author Guillaume Mary
    	 */
    	protected class TargetToSourceRelationStorage {

    		private final IdentityMap<TRGT, SRC> store = new IdentityMap<>();

    		TargetToSourceRelationStorage() {
    		}

    		private void add(TRGT target, SRC source) {
    			store.put(target, source);
    		}

    		protected SRC get(TRGT target) {
    			return store.get(target);
    		}

    		private SRCID giveSourceId(TRGT trgt) {
    			return nullable(get(trgt)).map(sourcePersister.getMapping()::getId).get();
    		}
    	}

    	protected final ShadowColumnValueProvider<TRGT, RIGHTTABLE> foreignKeyValueProvider;

    	public OneToManyWithMappedAssociationEngine(ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister,
    												MappedManyRelationDescriptor<SRC, TRGT, C, SRCID> manyRelationDescriptor,
    												ConfiguredRelationalPersister<SRC, SRCID> sourcePersister,
    												Set<Column<RIGHTTABLE, ?>> mappedReverseColumns,
    												Function<SRCID, Map<Column<RIGHTTABLE, ?>, ?>> reverseColumnsValueProvider) {
    		this.targetPersister = targetPersister;
    		this.manyRelationDescriptor = manyRelationDescriptor;
    		this.sourcePersister = sourcePersister;
    		this.foreignKeyValueProvider = new ShadowColumnValueProvider<TRGT, RIGHTTABLE>() {
    			@Override
    			public Set<Column<RIGHTTABLE, ?>> getColumns() {
    				return mappedReverseColumns;
    			}

    			@Override
    			public Map<Column<RIGHTTABLE, ?>, ?> giveValue(TRGT trgt) {
    				Map<Column<RIGHTTABLE, ?>, Object> result;
    				if (giveRelationStorageContext() == null) {
    					// case of TRGT is also root (SRC) in a cycling parent -> parent relation : when some root entities are
    					// inserted/updated the insert listener that initializes currentForeignKeyValueProvider on databaseAutoIncrement is not yet called
    					result = new HashMap<>();
    					getColumns().forEach(col -> result.put(col, null));
    				} else {
    					SRCID srcid = giveRelationStorageContext().giveSourceId(trgt);
    					result = (Map<Column<RIGHTTABLE, ?>, Object>) reverseColumnsValueProvider.apply(srcid);
    				}
    				return result;
    			}
    		};

    		addForeignKeyManager();
    	}

    	protected TargetToSourceRelationStorage giveRelationStorageContext() {
    		return currentTargetToSourceRelationStorage.get();
    	}

    	protected void clearRelationStorageContext() {
    		currentTargetToSourceRelationStorage.remove();
    	}

    	protected void addForeignKeyManager() {
    		targetPersister.<RIGHTTABLE>getMapping().addShadowColumnInsert(foreignKeyValueProvider);
    		targetPersister.<RIGHTTABLE>getMapping().addShadowColumnUpdate(foreignKeyValueProvider);
    	}

    	public MappedManyRelationDescriptor<SRC, TRGT, C, SRCID> getManyRelationDescriptor() {
    		return manyRelationDescriptor;
    	}

    	public <T1 extends Table<T1>, T2 extends Table<T2>> String addSelectCascade(Key<T1, SRCID> sourcePrimaryKey,
    																				boolean loadSeparately) {
    		// we add target subgraph joins to main persister
    		String relationJoinNodeName = targetPersister.joinAsMany(ROOT_JOIN_NAME, sourcePersister, manyRelationDescriptor.getCollectionProvider(), sourcePrimaryKey, (Key<T2, SRCID>) manyRelationDescriptor.getReverseColumn(),
    				manyRelationDescriptor.getRelationFixer(), null, true, loadSeparately);

    		// we must trigger subgraph event on loading of our own graph, this is mainly for event that initializes things because given ids
    		// are not those of their entity
    		SelectListener<TRGT, TRGTID> targetSelectListener = targetPersister.getPersisterListener().getSelectListener();
    		sourcePersister.addSelectListener(new SelectListener<SRC, SRCID>() {
    			@Override
    			public void beforeSelect(Iterable<SRCID> ids) {
    				// since ids are not those of its entities, we should not pass them as argument, this will only initialize things if needed
    				targetSelectListener.beforeSelect(Collections.emptyList());
    			}

    			@Override
    			public void afterSelect(Set<? extends SRC> result) {
    				Set<TRGT> collect = Iterables.stream(result).flatMap(src -> Nullable.nullable(manyRelationDescriptor.getCollectionGetter().apply(src))
    						.map(Collection::stream)
    						.getOr(Stream.empty()))
    						.collect(Collectors.toSet());
    				targetSelectListener.afterSelect(collect);
    			}

    			@Override
    			public void onSelectError(Iterable<SRCID> ids, RuntimeException exception) {
    				// since ids are not those of its entities, we should not pass them as argument
    				targetSelectListener.onSelectError(Collections.emptyList(), exception);
    			}
    		});
    		return relationJoinNodeName;
    	}

    	public void addInsertCascade(ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
    		sourcePersister.addInsertListener(
    				new TargetInstancesInsertCascader(targetPersister, manyRelationDescriptor.getCollectionGetter()));
    	}

    	public void addUpdateCascade(boolean shouldDeleteRemoved, ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
    		sourcePersister.addUpdateListener(new UpdateListener<SRC>() {

    			/**
    			 * Implemented to store target-to-source relation, made to help relation maintenance (because foreign key
    			 * maintainer will refer to it) and avoid to depend on "mapped by" properties which is optional
    			 * Made AFTER insert to benefit from id when set by database with IdentifierPolicy is {@link GeneratedKeysPolicy}
    			 */
    			@Override
    			public void afterUpdate(Iterable<? extends Duo<SRC, SRC>> entities, boolean allColumnsStatement) {
    				storeTargetToSourceRelation(Iterables.mappingIterator(entities, Duo::getLeft), false);
    			}

    			/**
    			 * Overridden to help debug
    			 * @return targetToSourceRelationStorer
    			 */
    			@Override
    			public String toString() {
    				return "targetToSourceRelationStorer";
    			}
    		});
    		addTargetInstancesUpdateCascader(shouldDeleteRemoved);
    		sourcePersister.addUpdateListener(new UpdateListener<SRC>() {
    			@Override
    			public void afterUpdate(Iterable<? extends Duo<SRC, SRC>> entities, boolean allColumnsStatement) {
    				clearRelationStorageContext();
    			}

    			/**
    			 * Overridden to help debug
    			 * @return targetToSourceRelationStorageCleaner
    			 */
    			@Override
    			public String toString() {
    				return "targetToSourceRelationStorageCleaner";
    			}
    		});
    	}

    	protected void addTargetInstancesUpdateCascader(boolean shouldDeleteRemoved) {
    		BiConsumer<Duo<SRC, SRC>, Boolean> collectionUpdater = new CollectionUpdater<>(
    				manyRelationDescriptor.getCollectionGetter(),
    				targetPersister,
    				manyRelationDescriptor.getReverseSetter(),
    				shouldDeleteRemoved);
    		sourcePersister.addUpdateListener(
    				new AfterUpdateTrigger<>(collectionUpdater));
    	}

    	public void addDeleteCascade(boolean shouldDeleteRemoved, ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
    		sourcePersister.addDeleteListener(new DeleteListener<SRC>() {

    			@Override
    			public void beforeDelete(Iterable<? extends SRC> entities) {
    				storeTargetToSourceRelation(entities, true);
    			}
    		});
    		sourcePersister.addDeleteByIdListener(new DeleteByIdListener<SRC>() {
    			@Override
    			public void beforeDeleteById(Iterable<? extends SRC> entities) {
    				storeTargetToSourceRelation(entities, true);
    			}
    		});

    		if (shouldDeleteRemoved) {
    			// adding deletion of many-side entities
    			sourcePersister.addDeleteListener(
    					new DeleteTargetEntitiesBeforeDeleteCascader<>(targetPersister, manyRelationDescriptor.getCollectionGetter()));
    			// we add the deleteById event since we suppose that if delete is required then there's no reason that rough delete is not
    			sourcePersister.addDeleteByIdListener(
    					new DeleteByIdTargetEntitiesBeforeDeleteByIdCascader<>(targetPersister, manyRelationDescriptor.getCollectionGetter()));
    		} else // entity shouldn't be deleted, so we may have to update it
    			if (manyRelationDescriptor.getReverseSetter() != null) {
    				// we cut the link between target and source
    				// NB : we don't take versioning into account overall because we can't : how to do it since we miss the unmodified version ?
    				sourcePersister.addDeleteListener(new BeforeDeleteCollectionCascader<SRC, TRGT>(targetPersister) {

    					@Override
    					public void beforeDelete(Iterable<? extends SRC> entities) {
    						List<TRGT> targets = stream(entities).flatMap(c -> getTargets(c).stream()).collect(Collectors.toList());
    						targets.forEach(e -> manyRelationDescriptor.getReverseSetter().accept(e, null));
    						targetPersister.updateById(targets);
    					}

    					@Override
    					protected Collection<TRGT> getTargets(SRC src) {
    						return nullable(manyRelationDescriptor.getCollectionGetter().apply(src)).getOr(manyRelationDescriptor.getCollectionFactory());
    					}
    				});
    			}
    		sourcePersister.addDeleteListener(new DeleteListener<SRC>() {
    			@Override
    			public void afterDelete(Iterable<? extends SRC> entities) {
    				clearRelationStorageContext();
    			}
    		});
    		sourcePersister.addDeleteByIdListener(new DeleteByIdListener<SRC>() {
    			@Override
    			public void afterDeleteById(Iterable<? extends SRC> entities) {
    				clearRelationStorageContext();
    			}
    		});
    	}

    	/**
    	 * Method to be invoked in case of entity cycle detected in its persistence configuration.
    	 * We add a second phase load because cycle can hardly be supported by simply joining things together, in particular due to that
    	 * Query and SQL generation don't support several instances of table and columns in them (aliases generation must be enhanced), and
    	 * overall column reading will be messed up because of that (to avoid all of this we should have mapping strategy clones)
    	 *
    	 * @param sourcePrimaryKey left table primary key
    	 * @param relationOwner right table primary key
    	 * @param collectionGetter relation provider
    	 * @param firstPhaseCycleLoadListener code to be invoked when reading rows
    	 */
    	public <T extends Table<T>> void addSelectIn2Phases(PrimaryKey<T, SRCID> sourcePrimaryKey,
    														Key<T, SRCID> relationOwner,
    														ReversibleAccessor<SRC, C> collectionGetter,
    														FirstPhaseCycleLoadListener<SRC, TRGTID> firstPhaseCycleLoadListener) {
    		// Join is declared on non-added tables : Person (alias = null) / Person (alias = null)
    		T relationOwnerTable = sourcePrimaryKey.getTable();
    		Table relationOwnerTableClone = new Table(relationOwnerTable.getName());
    		KeyBuilder<?, SRCID> relationOwnerPrimaryKeyBuilder = Key.from(relationOwnerTableClone);
    		relationOwnerTable.getPrimaryKey().getColumns().forEach(column ->
    				relationOwnerPrimaryKeyBuilder.addColumn(relationOwnerTableClone.addColumn(column.getName(), column.getJavaType()))
    		);
    		KeyBuilder<Table, SRCID> relationOwnerClone = Key.from(relationOwnerTableClone);
    		relationOwner.getColumns().forEach(column ->
    				relationOwnerClone.addColumn(relationOwnerTableClone.addColumn(column.getExpression(), column.getJavaType()))
    		);

    		IdentifierAssembler<TRGTID, T> targetIdentifierAssembler = targetPersister.getMapping().getIdMapping().getIdentifierAssembler();
    		Key<Table, SRCID> rightKey = relationOwnerClone.build();
    		Key<?, SRCID> rightPrimaryKey = relationOwnerPrimaryKeyBuilder.build();
    		sourcePersister.getEntityJoinTree().addPassiveJoin(
    				ROOT_JOIN_NAME,
    				sourcePrimaryKey,
    				rightKey,
    				relationOwnerTableClone.getName() + "_" + AccessorDefinition.giveDefinition(collectionGetter).getName(),
    				JoinType.OUTER,
    				rightPrimaryKey.getColumns(),
    				(src, columnValueProvider) -> firstPhaseCycleLoadListener.onFirstPhaseRowRead(src, targetIdentifierAssembler.assemble(columnValueProvider)),
    				true);
    	}

    	public class TargetInstancesInsertCascader extends AfterInsertCollectionCascader<SRC, TRGT> {

    		private final Function<SRC, ? extends Collection<TRGT>> collectionGetter;

    		public TargetInstancesInsertCascader(EntityPersister<TRGT, TRGTID> targetPersister, Function<SRC, ? extends Collection<TRGT>> collectionGetter) {
    			super(targetPersister);
    			this.collectionGetter = collectionGetter;
    		}

    		@Override
    		public void afterInsert(Iterable<? extends SRC> entities) {
    			storeTargetToSourceRelation(entities, false);
    			super.afterInsert(entities);
    			clearRelationStorageContext();
    		}

    		@Override
    		protected void postTargetInsert(Iterable<? extends TRGT> entities) {
    			// Nothing to do. Identified#isPersisted flag should be fixed by target persister
    		}

    		@Override
    		protected Collection<TRGT> getTargets(SRC source) {
    			return collectionGetter.apply(source);
    		}
    	}

    	/**
    	 * Triggers given consumer after update
    	 *
    	 * @param <I>
    	 */
    	public static class AfterUpdateTrigger<I> implements UpdateListener<I> {

    		private final BiConsumer<Duo<? extends I, ? extends I>, Boolean> afterUpdateListener;

    		public AfterUpdateTrigger(BiConsumer<? extends Duo<? extends I, ? extends I>, Boolean> afterUpdateListener) {
    			this.afterUpdateListener = (BiConsumer<Duo<? extends I, ? extends I>, Boolean>) afterUpdateListener;
    		}

    		@Override
    		public void afterUpdate(Iterable<? extends Duo<I, I>> entities, boolean allColumnsStatement) {
    			entities.forEach(entry -> afterUpdateListener.accept(entry, allColumnsStatement));
    		}
    	}

    	public static class DeleteTargetEntitiesBeforeDeleteCascader<I, O> extends BeforeDeleteCollectionCascader<I, O> {

    		private final Function<I, ? extends Collection<O>> collectionGetter;

    		public DeleteTargetEntitiesBeforeDeleteCascader(EntityPersister<O, ?> targetPersister, Function<I, ? extends Collection<O>> collectionGetter) {
    			super(targetPersister);
    			this.collectionGetter = collectionGetter;
    		}

    		@Override
    		protected Collection<O> getTargets(I i) {
    			return collectionGetter.apply(i);
    		}
    	}

    	public static class DeleteByIdTargetEntitiesBeforeDeleteByIdCascader<I, O> extends BeforeDeleteByIdCollectionCascader<I, O> {

    		private final Function<I, ? extends Collection<O>> collectionGetter;

    		public DeleteByIdTargetEntitiesBeforeDeleteByIdCascader(EntityPersister<O, ?> targetPersister,
    																Function<I, ? extends Collection<O>> collectionGetter) {
    			super(targetPersister);
    			this.collectionGetter = collectionGetter;
    		}

    		@Override
    		protected void postTargetDelete(Iterable<O> entities) {
    			// no post treatment to do
    		}

    		@Override
    		protected Collection<O> getTargets(I i) {
    			return collectionGetter.apply(i);
    		}
    	}

    	/**
    	 * Store target-to-source relation in current thread storage
    	 */
    	private void storeTargetToSourceRelation(Iterable<? extends SRC> sourceEntities, boolean relationIsNullified) {
    		if (giveRelationStorageContext() == null) {
    			currentTargetToSourceRelationStorage.set(new TargetToSourceRelationStorage());
    		}
    		for (SRC sourceEntity : sourceEntities) {
    			C collection = manyRelationDescriptor.getCollectionGetter().apply(sourceEntity);
    			nullable(collection).getOr(manyRelationDescriptor.getCollectionFactory()).forEach(trgt -> {
    				giveRelationStorageContext().add(trgt, relationIsNullified ? null : sourceEntity);
    			});
    		}
    	}
    }