MapEntryKeyAndValueEntitiesUpdater.java
package org.codefilarete.stalactite.engine.configurer.map;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.codefilarete.stalactite.dsl.property.CascadeOptions.RelationMode;
import org.codefilarete.stalactite.engine.EntityPersister;
import org.codefilarete.stalactite.engine.diff.AbstractDiff;
import org.codefilarete.stalactite.engine.runtime.CollectionUpdater;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.tool.Duo;
import org.codefilarete.tool.collection.Iterables;
import org.codefilarete.tool.collection.KeepOrderSet;
import org.codefilarete.tool.collection.StreamSplitter;
/**
* Class aimed at doing same thing as {@link CollectionUpdater} but for {@link Map} containing entities as keys and values :
* requires to update {@link Entry} as well as propagate insert / update /delete operation to key-entities and value-entities.
*
* @param <SRC> entity type owning the relation
* @param <SRCID> entity owning the relation identifier type
* @param <K> Map key entity type
* @param <V> Map value entity type
* @param <KK> type of {@link KeyValueRecord} key when transforming initial Map entries to {@link KeyValueRecord} to be persisted
* @param <VV> type of {@link KeyValueRecord} value when transforming initial Map entries to {@link KeyValueRecord} to be persisted
* @author Guillaume Mary
*/
class MapEntryKeyAndValueEntitiesUpdater<SRC, SRCID, K, V, KK, VV> extends CollectionUpdater<SRC, KeyValueRecord<K, V, SRCID>, Set<KeyValueRecord<K, V, SRCID>>> {
private final ConfiguredRelationalPersister<K, ?> keyEntityPersister;
private final ConfiguredRelationalPersister<V, ?> valueEntityPersister;
private final RelationMode keyEntityMaintenanceMode;
private final RelationMode valueEntityMaintenanceMode;
private final boolean associationRecordWritable;
public MapEntryKeyAndValueEntitiesUpdater(Function<SRC, Set<KeyValueRecord<K, V, SRCID>>> targetEntityGetter,
Function<K, KK> keyMapper,
Function<V, VV> valueMapper,
ConfiguredRelationalPersister<K, ?> keyEntityPersister,
ConfiguredRelationalPersister<V, ?> valueEntityPersister,
EntityPersister<KeyValueRecord<KK, VV, SRCID>, RecordId<KK, SRCID>> keyValueRecordPersister,
RelationMode keyMaintenanceMode,
RelationMode valueMaintenanceMode) {
super(targetEntityGetter,
new RelationalPersisterAsEntityWriter<>(keyValueRecordPersister, keyMapper, valueMapper),
(o, i) -> { /* no reverse setter because we store only raw values */ },
true,
// Notice that the way this computation is done as a huge impact on what's done in the updateTargets(..) method
(KeyValueRecord<K, V, SRCID> entry) -> {
// We don't take value into account to trigger updateTargets(..) : if it wa taken into account then
// we would have only insert and delete actions on association records, which leads to not optimal
// database actions.
int result = entry.getId().getId().hashCode();
result = 31 * result + keyEntityPersister.getId(entry.getKey()).hashCode();
return result;
});
this.keyEntityPersister = keyEntityPersister;
this.valueEntityPersister = valueEntityPersister;
this.keyEntityMaintenanceMode = keyMaintenanceMode;
this.valueEntityMaintenanceMode = valueMaintenanceMode;
this.associationRecordWritable = this.keyEntityMaintenanceMode != RelationMode.READ_ONLY;
}
@Override
protected void insertTargets(UpdateContext updateContext) {
if (keyEntityMaintenanceMode == RelationMode.ALL || keyEntityMaintenanceMode == RelationMode.ALL_ORPHAN_REMOVAL) {
keyEntityPersister.insert(updateContext.getAddedElements().stream().map(KeyValueRecord::getKey).collect(Collectors.toSet()));
}
if (valueEntityMaintenanceMode == RelationMode.ALL || valueEntityMaintenanceMode == RelationMode.ALL_ORPHAN_REMOVAL) {
valueEntityPersister.insert(updateContext.getAddedElements().stream().map(KeyValueRecord::getValue).collect(Collectors.toSet()));
}
// we insert association records after targets to satisfy integrity constraint
if (associationRecordWritable) {
super.insertTargets(updateContext);
}
}
@Override
protected void updateTargets(UpdateContext updateContext, boolean allColumnsStatement) {
// Here we only manage entry values because keys are not modified since they are part of the primary key and overall
// are in CollectionUpdate identifier (see constructor) so they are already managed through insertTargets(..) and deleteTargets(..)
// Hence code below focuses only on value removal, update and addition
if (valueEntityMaintenanceMode != RelationMode.READ_ONLY) {
Set<V> removedValues = new KeepOrderSet<>();
Set<V> addedValues = new KeepOrderSet<>();
Set<AbstractDiff<KeyValueRecord<K, V, SRCID>>> modifiedValues = new KeepOrderSet<>();
new StreamSplitter<>(updateContext.getHeldElements().stream())
.dispatch(diff -> diff.getReplacingInstance() == null, entry -> {
removedValues.add(entry.getSourceInstance().getValue());
})
.dispatch(diff -> diff.getReplacingInstance() != null, entry -> {
if (!valueEntityPersister.getId(entry.getSourceInstance().getValue())
.equals(valueEntityPersister.getId(entry.getReplacingInstance().getValue()))) {
modifiedValues.add(entry);
addedValues.add(entry.getReplacingInstance().getValue());
removedValues.add(entry.getSourceInstance().getValue());
}
})
.split();
if (valueEntityMaintenanceMode == RelationMode.ALL || valueEntityMaintenanceMode == RelationMode.ALL_ORPHAN_REMOVAL) {
valueEntityPersister.persist(addedValues);
}
Set<Duo<KeyValueRecord<K, V, SRCID>, KeyValueRecord<K, V, SRCID>>> recordsToBeUpdated = modifiedValues.stream()
.map(diff -> new Duo<>(diff.getReplacingInstance(), diff.getSourceInstance())).collect(Collectors.toSet());
elementPersister.update(recordsToBeUpdated, allColumnsStatement);
if (valueEntityMaintenanceMode == RelationMode.ALL_ORPHAN_REMOVAL) {
valueEntityPersister.delete(removedValues);
}
}
}
@Override
protected void deleteTargets(UpdateContext updateContext) {
// we delete association records before targets to satisfy integrity constraint
if (associationRecordWritable) {
super.deleteTargets(updateContext);
}
if (keyEntityMaintenanceMode == RelationMode.ALL_ORPHAN_REMOVAL) {
keyEntityPersister.delete(updateContext.getRemovedElements().stream().map(KeyValueRecord::getKey).collect(Collectors.toSet()));
}
if (valueEntityMaintenanceMode == RelationMode.ALL_ORPHAN_REMOVAL) {
valueEntityPersister.delete(updateContext.getRemovedElements().stream().map(KeyValueRecord::getValue).collect(Collectors.toSet()));
}
}
/**
* Class that redirects {@link EntityWriter} methods to a given {@link EntityPersister}
* @param <K>
* @param <V>
* @param <KK>
* @param <VV>
* @param <SRCID>
* @author Guillaume Mary
*/
private static class RelationalPersisterAsEntityWriter<K, V, KK, VV, SRCID> implements EntityWriter<KeyValueRecord<K, V, SRCID>> {
private final EntityPersister<KeyValueRecord<KK, VV, SRCID>, RecordId<KK, SRCID>> relationEntityPersister;
private final Function<K, KK> keyMapper;
private final Function<V, VV> valueMapper;
public RelationalPersisterAsEntityWriter(EntityPersister<KeyValueRecord<KK, VV, SRCID>, RecordId<KK, SRCID>> relationEntityPersister,
Function<K, KK> keyMapper,
Function<V, VV> valueMapper) {
this.relationEntityPersister = relationEntityPersister;
this.keyMapper = keyMapper;
this.valueMapper = valueMapper;
}
@Override
public void update(Iterable<? extends Duo<KeyValueRecord<K, V, SRCID>, KeyValueRecord<K, V, SRCID>>> differencesIterable, boolean allColumnsStatement) {
relationEntityPersister.update(Iterables.stream(differencesIterable)
.map(keyValueRecordKeyValueRecordDuo -> {
KeyValueRecord<K, V, SRCID> left = keyValueRecordKeyValueRecordDuo.getLeft();
KeyValueRecord<K, V, SRCID> right = keyValueRecordKeyValueRecordDuo.getRight();
return new Duo<>(
new KeyValueRecord<>(left.getId().getId(),
keyMapper.apply(left.getKey()), valueMapper.apply(left.getValue())),
new KeyValueRecord<>(right.getId().getId(),
keyMapper.apply(right.getKey()), valueMapper.apply(right.getValue())));
})
.collect(Collectors.toSet()), allColumnsStatement);
}
@Override
public void delete(Iterable<? extends KeyValueRecord<K, V, SRCID>> entities) {
relationEntityPersister.delete(Iterables.stream(entities)
.map(entity -> new KeyValueRecord<>(entity.getId().getId(),
keyMapper.apply(entity.getKey()), valueMapper.apply(entity.getValue())))
.collect(Collectors.toSet()));
}
@Override
public void insert(Iterable<? extends KeyValueRecord<K, V, SRCID>> entities) {
relationEntityPersister.insert(Iterables.stream(entities)
.map(entity -> new KeyValueRecord<>(entity.getId().getId(),
keyMapper.apply(entity.getKey()), valueMapper.apply(entity.getValue())))
.collect(Collectors.toSet()));
}
@Override
public void persist(Iterable<? extends KeyValueRecord<K, V, SRCID>> entities) {
relationEntityPersister.persist(Iterables.stream(entities)
.map(entity -> new KeyValueRecord<>(entity.getId().getId(),
keyMapper.apply(entity.getKey()), valueMapper.apply(entity.getValue())))
.collect(Collectors.toSet()));
}
@Override
public void updateById(Iterable<? extends KeyValueRecord<K, V, SRCID>> entities) {
relationEntityPersister.updateById(Iterables.stream(entities)
.map(entity -> new KeyValueRecord<>(entity.getId().getId(),
keyMapper.apply(entity.getKey()), valueMapper.apply(entity.getValue())))
.collect(Collectors.toSet()));
}
@Override
public boolean isNew(KeyValueRecord<K, V, SRCID> entity) {
// all records are persisted (not new) since we've just build them from database above
return false;
}
}
}