# OneToOneOwnedBySourceEngine.java

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

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

    import org.codefilarete.reflection.Accessor;
    import org.codefilarete.stalactite.engine.cascade.AfterDeleteByIdSupport;
    import org.codefilarete.stalactite.engine.cascade.AfterDeleteSupport;
    import org.codefilarete.stalactite.engine.cascade.BeforeInsertSupport;
    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.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.tool.Duo;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.Maps;
    import org.codefilarete.tool.function.Functions.NullProofFunction;
    import org.codefilarete.tool.function.Predicates;

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

    	private final ShadowColumnValueProvider<SRC, LEFTTABLE> foreignKeyValueProvider;

    	public OneToOneOwnedBySourceEngine(ConfiguredPersister<SRC, SRCID> sourcePersister,
    									   ConfiguredPersister<TRGT, TRGTID> targetPersister,
    									   Accessor<SRC, TRGT> targetAccessor,
    									   Map<Column<LEFTTABLE, ?>, Column<RIGHTTABLE, ?>> keyColumnsMapping) {
    		super(sourcePersister, targetPersister, targetAccessor, keyColumnsMapping);
    		// whatever kind of relation maintenance mode asked, we have to insert and update source-to-target link, because we are in relation-owned-by-source
    		Function<SRC, TRGTID> targetIdProvider = src -> {
    			TRGT trgt = targetAccessor.get(src);
    			return trgt == null ? null : targetPersister.getMapping().getId(trgt);
    		};
    		foreignKeyValueProvider = new ShadowColumnValueProvider<SRC, LEFTTABLE>() {
    			@Override
    			public Set<Column<LEFTTABLE, ?>> getColumns() {
    				return new HashSet<>(keyColumnsMapping.keySet());
    			}

    			@Override
    			public Map<Column<LEFTTABLE, ?>, ?> giveValue(SRC bean) {
    				TRGTID trgtid = targetIdProvider.apply(bean);
    				Map<Column<RIGHTTABLE, ?>, ?> columnValues = targetPersister.getMapping().getIdMapping().<RIGHTTABLE>getIdentifierAssembler().getColumnValues(trgtid);
    				return Maps.innerJoinOnValuesAndKeys(keyColumnsMapping, columnValues);
    			}
    		};
    	}

    	@Override
    	public void addInsertCascade() {
    		sourcePersister.<LEFTTABLE>getMapping().addShadowColumnInsert(foreignKeyValueProvider);
    		// adding cascade treatment: before source insert, target is inserted to comply with foreign key constraint
    		sourcePersister.addInsertListener(new BeforeInsertSupport<>(targetPersister::persist, targetAccessor::get, Objects::nonNull));
    	}

    	@Override
    	public void addUpdateCascade(boolean orphanRemoval) {
    		super.addUpdateCascade(orphanRemoval);
    		sourcePersister.<LEFTTABLE>getMapping().addShadowColumnUpdate(foreignKeyValueProvider);
    		// adding cascade treatment
    		// - insert non-persisted target instances to fulfill foreign key requirement
    		Function<SRC, TRGT> targetProviderAsFunction = new NullProofFunction<>(targetAccessor::get);
    		sourcePersister.addUpdateListener(new UpdateListener<SRC>() {

    			private final Predicate<TRGT> newInstancePredicate = Predicates.<TRGT>predicate(Objects::nonNull).and(targetPersister.getMapping()::isNew);

    			@Override
    			public void beforeUpdate(Iterable<? extends Duo<SRC, SRC>> payloads, boolean allColumnsStatement) {
    				// we only insert new instances
    				targetPersister.persist(Iterables.stream(payloads).map(duo -> targetProviderAsFunction.apply(duo.getLeft()))
    						.filter(newInstancePredicate).collect(Collectors.toSet()));
    			}
    		});
    		// - after source update, target is updated too
    		sourcePersister.addUpdateListener(new UpdateListener<SRC>() {

    			@Override
    			public void afterUpdate(Iterable<? extends Duo<SRC, SRC>> payloads, boolean allColumnsStatement) {
    				List<Duo<TRGT, TRGT>> targetsToUpdate = Iterables.collect(payloads,
    						// targets of nullified relations don't need to be updated
    						e -> getTarget(e.getLeft()) != null,
    						e -> getTargets(e.getLeft(), e.getRight()),
    						ArrayList::new);
    				targetPersister.update(targetsToUpdate, allColumnsStatement);
    			}

    			private Duo<TRGT, TRGT> getTargets(SRC modifiedTrigger, SRC unmodifiedTrigger) {
    				return new Duo<>(getTarget(modifiedTrigger), getTarget(unmodifiedTrigger));
    			}

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

    	@Override
    	public void addDeleteCascade(boolean orphanRemoval) {
    		if (orphanRemoval) {
    			// adding cascade treatment: target is deleted after source deletion (because of foreign key constraint)
    			sourcePersister.addDeleteListener(new AfterDeleteSupport<>(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 AfterDeleteByIdSupport<>(targetPersister::delete, targetAccessor::get, Objects::nonNull));
    		} // else : no target entities deletion asked (no delete orphan) : nothing more to do than deleting the source entity
    	}

    	public void addForeignKeyMaintainer() {
    		sourcePersister.<LEFTTABLE>getMapping().addShadowColumnInsert(foreignKeyValueProvider);
    		sourcePersister.<LEFTTABLE>getMapping().addShadowColumnUpdate(foreignKeyValueProvider);
    	}
    }