# OneToOneOwnedByTargetEngine.java

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

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.Set;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Predicate;
    import java.util.stream.Collector;
    import java.util.stream.Collectors;

    import org.codefilarete.reflection.Accessor;
    import org.codefilarete.stalactite.dsl.idpolicy.GeneratedKeysPolicy;
    import org.codefilarete.stalactite.engine.cascade.BeforeDeleteByIdSupport;
    import org.codefilarete.stalactite.engine.cascade.BeforeDeleteSupport;
    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.ConfiguredPersister;
    import org.codefilarete.stalactite.mapping.Mapping.ShadowColumnValueProvider;
    import org.codefilarete.stalactite.mapping.Mapping.UpwhereColumn;
    import org.codefilarete.stalactite.mapping.id.assembly.IdentifierAssembler;
    import org.codefilarete.stalactite.sql.ConnectionConfiguration;
    import org.codefilarete.stalactite.sql.Dialect;
    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.statement.PreparedUpdate;
    import org.codefilarete.stalactite.sql.statement.WriteOperation;
    import org.codefilarete.tool.Duo;
    import org.codefilarete.tool.collection.IdentityMap;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.Maps;

    import static org.codefilarete.tool.Nullable.nullable;
    import static org.codefilarete.tool.collection.Iterables.stream;

    public class OneToOneOwnedByTargetEngine<SRC, TRGT, SRCID, TRGTID, LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>>
    		extends AbstractOneToOneEngine<SRC, TRGT, SRCID, TRGTID, LEFTTABLE, RIGHTTABLE> {

    	/**
    	 * 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.
    	 */
    	private final ThreadLocal<TargetToSourceRelationStorage> currentTargetToSourceRelationStorage = new ThreadLocal<>();

    	private class TargetToSourceRelationStorage {

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

    		TargetToSourceRelationStorage() {
    		}

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

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

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

    	public OneToOneOwnedByTargetEngine(ConfiguredPersister<SRC, SRCID> sourcePersister,
    									   ConfiguredPersister<TRGT, TRGTID> targetPersister,
    									   Accessor<SRC, TRGT> targetAccessor,
    									   Map<Column<LEFTTABLE, ?>, Column<RIGHTTABLE, ?>> keyColumnsMapping) {
    		super(sourcePersister, targetPersister, targetAccessor, keyColumnsMapping);
    	}

    	protected void ensureRelationStorageContext() {
    		if (giveRelationStorageContext() == null) {
    			currentTargetToSourceRelationStorage.set(new TargetToSourceRelationStorage());
    		}
    	}

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

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

    	@Override
    	public void addInsertCascade() {
    		// adding cascade treatment: after source insert, target is persisted
    		// Please note that we collect entities in a Set to avoid persisting duplicates twice which may produce constraint exception if some source
    		// entities points to same target entity. In details the Set is an identity Set to avoid basing our comparison on implemented
    		// equals/hashCode although this could be sufficient, identity seems safer and match our logic.
    		Collector<TRGT, ?, Set<TRGT>> identitySetProvider = Collectors.toCollection(org.codefilarete.tool.collection.Collections::newIdentitySet);
    		Consumer<Iterable<? extends SRC>> persistTargetCascader = entities -> {
    			targetPersister.persist(stream(entities).map(targetAccessor::get).filter(Objects::nonNull).collect(identitySetProvider));
    		};
    		// Please note that 1st implementation was to simply add persistTargetCascader, but it invokes persist() which may invoke update()
    		// and because we are in the relation-owned-by-target case targetPersister.update() needs foreign key value provider to be
    		// fulfilled (see addUpdateCascade for addShadowColumnUpdate), so we wrap persistTargetCascader with a foreign key value provider.
    		// This focuses particular use case when a target is modified and newly assigned to the source
    		sourcePersister.addInsertListener(new InsertListener<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 afterInsert(Iterable<? extends SRC> entities) {
    				ensureRelationStorageContext();
    				for (SRC sourceEntity : entities) {
    					giveRelationStorageContext().add(targetAccessor.get(sourceEntity), sourceEntity);
    				}
    				persistTargetCascader.accept(entities);
    				clearRelationStorageContext();
    			}

    			@Override
    			public void onInsertError(Iterable<? extends SRC> entities, RuntimeException runtimeException) {
    				clearRelationStorageContext();
    			}
    		});

    		targetPersister.<RIGHTTABLE>getMapping().addShadowColumnInsert(new ShadowColumnValueProvider<TRGT, RIGHTTABLE>() {
    			@Override
    			public Set<Column<RIGHTTABLE, ?>> getColumns() {
    				return new HashSet<>(keyColumnsMapping.values());
    			}

    			@Override
    			public Map<Column<RIGHTTABLE, ?>, ?> giveValue(TRGT bean) {
    				// in many cases currentTargetToSourceRelationStorage is already present through source persister listener (insert or update)
    				// but in the corner case of source and target persist same type (in a parent -> child case) then at very first insert of root
    				// instance, currentTargetToSourceRelationStorage is not present, so we prevent this by initializing it
    				ensureRelationStorageContext();
    				SRCID srcid = giveRelationStorageContext().giveSourceId(bean);
    				Map<Column<LEFTTABLE, ?>, ?> columnValues = sourcePersister.getMapping().getIdMapping().<LEFTTABLE>getIdentifierAssembler().getColumnValues(srcid);
    				return Maps.innerJoin(keyColumnsMapping, columnValues);
    			}
    		});
    	}

    	@Override
    	public void addUpdateCascade(boolean orphanRemoval) {
    		super.addUpdateCascade(orphanRemoval);
    		// adding cascade treatment, please note that this will also be used by insert cascade if target is already persisted
    		targetPersister.<RIGHTTABLE>getMapping().addShadowColumnUpdate(new ShadowColumnValueProvider<TRGT, RIGHTTABLE>() {
    			@Override
    			public Set<Column<RIGHTTABLE, ?>> getColumns() {
    				return new HashSet<>(keyColumnsMapping.values());
    			}

    			@Override
    			public Map<Column<RIGHTTABLE, ?>, ?> giveValue(TRGT bean) {
    				// in many cases currentTargetToSourceRelationStorage is already present through source persister listener (insert or update)
    				// but in the corner case of source and target persist same type (in a parent -> child case) then at very first insert of root
    				// instance, currentTargetToSourceRelationStorage is not present, so we prevent this by initializing it
    				ensureRelationStorageContext();
    				SRCID srcid = giveRelationStorageContext().giveSourceId(bean);
    				Map<Column<LEFTTABLE, ?>, ?> columnValues = sourcePersister.getMapping().getIdMapping().<LEFTTABLE>getIdentifierAssembler().getColumnValues(srcid);
    				return Maps.innerJoin(keyColumnsMapping, columnValues);
    			}
    		});

    		// - after source update, target is updated too
    		sourcePersister.addUpdateListener(new UpdateListener<SRC>() {

    			@Override
    			public void afterUpdate(Iterable<? extends Duo<SRC, SRC>> payloads, boolean allColumnsStatement) {
    				ensureRelationStorageContext();
    				List<TRGT> newObjects = new ArrayList<>();
    				List<Duo<TRGT, TRGT>> existingEntities = new ArrayList<>();

    				// very small class to ease registering entities to be persisted
    				class PersisterHelper {

    					void markToPersist(TRGT targetOfModified, SRC modifiedSource) {
    						if (targetPersister.getMapping().isNew(targetOfModified)) {
    							newObjects.add(targetOfModified);
    						} else {
    							existingEntities.add(new Duo<>(targetOfModified, null));
    						}
    						giveRelationStorageContext().add(targetOfModified, modifiedSource);
    					}
    				}

    				List<TRGT> nullifiedRelations = new ArrayList<>();
    				PersisterHelper persisterHelper = new PersisterHelper();
    				for (Duo<? extends SRC, ? extends SRC> payload : payloads) {

    					TRGT targetOfModified = getTarget(payload.getLeft());
    					TRGT targetOfUnmodified = getTarget(payload.getRight());
    					if (targetOfModified == null && targetOfUnmodified != null) {
    						// "REMOVED"
    						// relation is nullified : relation column should be nullified too
    						nullifiedRelations.add(targetOfUnmodified);
    					} else if (targetOfModified != null && targetOfUnmodified == null) {
    						// "ADDED"
    						// newly set relation, entities will fully inserted / updated some lines above, nothing to do
    						// relation is set, we fully update modified bean, then its properties will be updated too
    						persisterHelper.markToPersist(targetOfModified, payload.getLeft());
    					} else if (targetOfModified != null) {
    						// "HELD"
    						persisterHelper.markToPersist(targetOfModified, payload.getLeft());
    						// Was target entity reassigned to another one ? Relation changed to another entity : we must nullify reverse column of detached target
    						if (!targetPersister.getMapping().getId(targetOfUnmodified).equals(targetPersister.getMapping().getId(targetOfModified))) {
    							nullifiedRelations.add(targetOfUnmodified);
    						}
    					} // else both sides are null => nothing to do
    				}

    				// we look for entities to persist: left elements are the modified ones
    				Set<TRGT> toPersist = Iterables.stream(payloads)
    						.map(duo -> {
    							if (duo.getLeft() != null) {
    								// modified or newly-set entity in the relation, we add it for persistence
    								return getTarget(duo.getLeft());
    							} else {
    								return (TRGT) null;
    							}
    						}).filter(Objects::nonNull)
    						.collect(Collectors.toSet());
    				targetPersister.persist(toPersist);

    				if (!orphanRemoval) {
    					targetPersister.updateById(nullifiedRelations);
    				}
    				// else : no need to nullify relation since entities are being deleted
    				// (overall it fails since entities are already deleted through before delete listener)

    				clearRelationStorageContext();
    			}

    			private TRGT getTarget(SRC src) {
    				return src == null ? null : targetAccessor.get(src);
    			}

    			@Override
    			public void onUpdateError(Iterable<? extends SRC> entities, RuntimeException runtimeException) {
    				clearRelationStorageContext();
    			}
    		});
    	}

    	@Override
    	public void addDeleteCascade(boolean orphanRemoval) {
    		if (orphanRemoval) {
    			// adding cascade treatment: target is deleted before source deletion (because of foreign key constraint)
    			sourcePersister.addDeleteListener(new BeforeDeleteSupport<>(targetPersister::delete, targetAccessor::get, Objects::nonNull));
    			// 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 BeforeDeleteByIdSupport<>(targetPersister::delete, targetAccessor::get, Objects::nonNull));
    		} else {
    			// no target entities deletion asked (no delete orphan) : we only need to nullify the relation
    			sourcePersister.addDeleteListener(new NullifyRelationColumnBeforeDelete(targetAccessor, targetPersister));
    		}
    	}

    	/**
    	 * Made to maintain reverse column when write is not authorized
    	 * Will create an SQL update order dedicated to it
    	 */
    	public void addForeignKeyMaintainer(Dialect dialect, ConnectionConfiguration connectionConfiguration, Key<RIGHTTABLE, SRCID> rightKey) {
    		ForeignKeyUpdateOrderProvider foreignKeyUpdateOrderProvider = new ForeignKeyUpdateOrderProvider(dialect, connectionConfiguration, rightKey);
    		sourcePersister.getPersisterListener().addInsertListener(new InsertListener<SRC>() {

    			/**
    			 * Implemented to update target owning column after insert. Made AFTER insert to benefit from id when set by database with
    			 * IdentifierPolicy is {@link GeneratedKeysPolicy}
    			 */
    			@Override
    			public void afterInsert(Iterable<? extends SRC> entities) {
    				WriteOperation<UpwhereColumn<RIGHTTABLE>> upwhereColumnWriteOperation = foreignKeyUpdateOrderProvider.getOperation();
    				foreignKeyUpdateOrderProvider.<SRC>addValuesToUpdateBatch(
    						entities,
    						sourcePersister::getId,
    						sourcePersister.<LEFTTABLE>getMapping().getIdMapping().getIdentifierAssembler(),
    						Function.identity(),
    						upwhereColumnWriteOperation);
    				upwhereColumnWriteOperation.executeBatch();
    			}
    		});

    		sourcePersister.getPersisterListener().addUpdateListener(new UpdateListener<SRC>() {

    			@Override
    			public void afterUpdate(Iterable<? extends Duo<SRC, SRC>> entities, boolean allColumnsStatement) {
    				WriteOperation<UpwhereColumn<RIGHTTABLE>> upwhereColumnWriteOperation = foreignKeyUpdateOrderProvider.getOperation();
    				foreignKeyUpdateOrderProvider.<Duo<SRC, SRC>>addValuesToUpdateBatch((Iterable<? extends Duo<SRC, SRC>>) entities,
    						duo -> sourcePersister.getId(duo.getLeft()),
    						sourcePersister.<RIGHTTABLE>getMapping().getIdMapping().getIdentifierAssembler(),
    						Duo::getLeft,
    						upwhereColumnWriteOperation);
    				foreignKeyUpdateOrderProvider.<Duo<SRC, SRC>>addValuesToUpdateBatch((Iterable<? extends Duo<SRC, SRC>>) entities,
    						duo -> null,
    						sourcePersister.<RIGHTTABLE>getMapping().getIdMapping().getIdentifierAssembler(),
    						Duo::getRight,
    						upwhereColumnWriteOperation);
    				upwhereColumnWriteOperation.executeBatch();
    			}
    		});

    		sourcePersister.getPersisterListener().addDeleteListener(new DeleteListener<SRC>() {

    			/**
    			 * Implemented to nullify target owning column before insert.
    			 */
    			@Override
    			public void beforeDelete(Iterable<? extends SRC> entities) {
    				WriteOperation<UpwhereColumn<RIGHTTABLE>> upwhereColumnWriteOperation = foreignKeyUpdateOrderProvider.getOperation();
    				foreignKeyUpdateOrderProvider.<SRC>addValuesToUpdateBatch(
    						entities,
    						duo -> null,	// nullifies the relation
    						sourcePersister.<RIGHTTABLE>getMapping().getIdMapping().getIdentifierAssembler(),
    						Function.identity(),
    						upwhereColumnWriteOperation);
    				upwhereColumnWriteOperation.executeBatch();
    			}
    		});
    	}

    	private class NullifyRelationColumnBeforeDelete implements DeleteListener<SRC> {

    		private final ConfiguredPersister<TRGT, TRGTID> targetPersister;
    		private final Accessor<SRC, TRGT> targetEntityProvider;

    		private NullifyRelationColumnBeforeDelete(Accessor<SRC, TRGT> targetEntityProvider, ConfiguredPersister<TRGT, TRGTID> targetPersister) {
    			this.targetPersister = targetPersister;
    			this.targetEntityProvider = targetEntityProvider;
    		}

    		@Override
    		public void beforeDelete(Iterable<? extends SRC> entities) {
    			ensureRelationStorageContext();
    			List<TRGT> targetEntities = stream(entities)
    					.map(this::getTarget)
    					.filter(Objects::nonNull)
    					.peek(trgt -> giveRelationStorageContext().add(trgt, null))
    					.collect(Collectors.toList());
    			this.targetPersister.updateById(targetEntities);
    			clearRelationStorageContext();
    		}

    		private TRGT getTarget(SRC src) {
    			return targetEntityProvider.get(src);
    		}
    	}

    	/**
    	 * Small class that helps to maintain foreign key (reverse column), and only it
     	 */
    	private class ForeignKeyUpdateOrderProvider {

    		private final WriteOperation<UpwhereColumn<RIGHTTABLE>> foreignKeyUpdateOperation;

    		public ForeignKeyUpdateOrderProvider(Dialect dialect, ConnectionConfiguration connectionConfiguration, Key<RIGHTTABLE, SRCID> rightKey) {
    			PreparedUpdate<RIGHTTABLE> tablePreparedUpdate = dialect.getDmlGenerator().buildUpdate(
    					(Set<Column<RIGHTTABLE, Object>>) rightKey.getColumns(),
    					targetPersister.<RIGHTTABLE>getMapping().getVersionedKeys());
    			foreignKeyUpdateOperation = dialect.getWriteOperationFactory().createInstance(tablePreparedUpdate,
    					connectionConfiguration.getConnectionProvider());
    		}

    		private WriteOperation<UpwhereColumn<RIGHTTABLE>> getOperation() {
    			return this.foreignKeyUpdateOperation;
    		}

    		private <C> void addValuesToUpdateBatch(Iterable<? extends C> entities,
    												Function<C, SRCID> idProvider,
    												IdentifierAssembler<SRCID, LEFTTABLE> identifierAssembler,
    												Function<C, SRC> sourceProvider,
    												WriteOperation<UpwhereColumn<RIGHTTABLE>> updateOrder) {
    			Map<UpwhereColumn<RIGHTTABLE>, Object> values = new HashMap<>();
    			entities.forEach(e -> {
    				Map<Column<RIGHTTABLE, ?>, ?> columnValues = Maps.innerJoin(keyColumnsMapping, identifierAssembler.getColumnValues(idProvider.apply(e)));
    				columnValues.forEach((key, value) -> values.put(new UpwhereColumn<>(key, true), value));
    				targetPersister.<RIGHTTABLE>getMapping().getVersionedKeyValues(targetAccessor.get(sourceProvider.apply(e)))
    						.forEach((c, o) -> values.put(new UpwhereColumn<>(c, false), o));
    				updateOrder.addBatch(values);
    				values.clear();
    			});
    		}
    	}
    }