InMemoryRelationHolder.java
package org.codefilarete.stalactite.engine.configurer.map;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.codefilarete.stalactite.engine.listener.SelectListener;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.stalactite.engine.runtime.SimpleRelationalEntityPersister;
import org.codefilarete.stalactite.sql.ddl.structure.ForeignKey;
import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
import org.codefilarete.tool.Duo;
import org.codefilarete.tool.bean.Objects;
import static org.codefilarete.tool.Nullable.nullable;
/**
* Made to store links between :
* - source entity and [key-entity-id, value] pairs on one hand
* - key-value records and key entity on the other hand
* which let caller seam source entity and its [key entity, value] pairs afterward.
* This is made necessary due to double join creation between
* - source entity table and association table on one hand
* - association table and key-entity table on one hand
* Look at joinAsMany(..) invocations in {@link EntityAsValueMapRelationConfigurer#addSelectCascade(ConfiguredRelationalPersister, SimpleRelationalEntityPersister, PrimaryKey, ForeignKey, BiConsumer, Function, Supplier)}
* This is the goal and need, implementation differ due to simplification made after first intent.
*
* Expected to be used in a {@link SelectListener} to {@link #init()} it before select and {@link #clear()} it after select.
*
* @param <I>
* @param <KEY_LOOKUP>
* @param <ENTRY_VALUE>
* @param <ENTITY>
* @author Guillaume Mary
*/
class InMemoryRelationHolder<I, KEY_LOOKUP, ENTRY_VALUE, ENTITY> {
public class Trio {
private KEY_LOOKUP keyLookup; // K or KID
private ENTRY_VALUE entryValue; // VID or KID
private ENTITY entity;
public KEY_LOOKUP getKeyLookup() {
return keyLookup;
}
public ENTRY_VALUE getEntryValue() {
return entryValue;
}
public ENTITY getEntity() {
return entity;
}
}
/**
* In memory and temporary Map storage.
*/
private final ThreadLocal<Map<I, Set<Trio>>> relationCollectionPerEntity = new ThreadLocal<>();
private final Function<Trio, Duo<Object, Object>> mapper;
public InMemoryRelationHolder(Function<Trio, Duo<Object, Object>> mapper) {
this.mapper = mapper;
}
public void storeRelation(I source, KEY_LOOKUP keyLookup, ENTRY_VALUE entryValue) {
Map<I, Set<Trio>> srcidcMap = relationCollectionPerEntity.get();
Set<Trio> relatedDuos = srcidcMap.computeIfAbsent(source, id -> new HashSet<>());
Trio trio = relatedDuos.stream().filter(pawn -> Objects.equals(pawn.keyLookup, keyLookup)).findAny().orElseGet(() -> {
Trio result = new Trio();
relatedDuos.add(result);
return result;
});
trio.keyLookup = keyLookup;
trio.entryValue = entryValue;
}
public void storeEntity(I source, KEY_LOOKUP keyLookup, ENTITY entity) {
Map<I, Set<Trio>> srcidcMap = relationCollectionPerEntity.get();
Set<Trio> relatedDuos = srcidcMap.computeIfAbsent(source, id -> new HashSet<>());
// storeRelation(..) should have been invoked before this method (according to join building) so no need to build a Trio, null can be returned
Trio trio = relatedDuos.stream().filter(pawn -> Objects.equals(pawn.keyLookup, keyLookup)).findAny().orElseGet(Trio::new);
trio.entity = entity;
}
public Collection<Duo<Object, Object>> get(I src) {
Map<I, Set<Trio>> currentMap = relationCollectionPerEntity.get();
return nullable(currentMap)
.map(map -> map.get(src))
.map(map -> map.stream().map(mapper::apply)
.collect(Collectors.toSet()))
.get();
}
public void init() {
this.relationCollectionPerEntity.set(new HashMap<>());
}
public void clear() {
this.relationCollectionPerEntity.remove();
}
}