package org.javers.repository.jql;
import org.javers.common.collections.Consumer;
import org.javers.core.commit.CommitMetadata;
import org.javers.core.metamodel.object.*;
import org.javers.repository.api.JaversExtendedRepository;
import org.javers.repository.api.QueryParams;
import org.javers.repository.api.QueryParamsBuilder;
import org.javers.shadow.Shadow;
import org.javers.shadow.ShadowFactory;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
/**
* @author bartosz.walacik
*/
public class ShadowQueryRunner {
private final JaversExtendedRepository repository;
private final ShadowFactory shadowFactory;
public ShadowQueryRunner(JaversExtendedRepository repository, ShadowFactory shadowFactory) {
this.repository = repository;
this.shadowFactory = shadowFactory;
}
public List<Shadow> queryForShadows(JqlQuery query, List<CdoSnapshot> coreSnapshots) {
final CommitTable commitTable = new CommitTable(coreSnapshots);
if (query.getShadowScope() == ShadowScope.COMMIT_DEPTH) {
commitTable.loadFullCommits();
}
return commitTable.rootsForQuery(query).stream()
.map(r -> shadowFactory.createShadow(r.root, r.context, (cm, targetId) -> commitTable.findLatestTo(cm, targetId)))
.collect(toList());
}
private static class ShadowRoot {
final CommitMetadata context;
final CdoSnapshot root;
ShadowRoot(CdoSnapshot root) {
this.context = root.getCommitMetadata();
this.root = root;
}
ShadowRoot(CommitMetadata context, CdoSnapshot root) {
this.context = context;
this.root = root;
}
}
class CommitTable {
private final Map<CommitMetadata, CommitEntry> commitsMap = new HashMap<>();
private final List<CommitEntry> commitsList = new ArrayList<>();
private final List<CdoSnapshot> coreSnapshots;
CommitTable(List<CdoSnapshot> coreSnapshots) {
this.coreSnapshots = coreSnapshots;
if (coreSnapshots.isEmpty()) {
return;
}
coreSnapshots.forEach(s -> {
CommitEntry current = commitsMap.get(s.getCommitMetadata());
if (current == null) {
current = nextCommit(s);
}
current.append(s);
});
}
List<ShadowRoot> rootsForQuery(JqlQuery query) {
fillMissingParents();
return commitsList.stream()
.flatMap(e -> e.getAllStream()
.filter(s -> query.matches(s.getGlobalId()))
.map(s -> new ShadowRoot(e.commitMetadata, s)))
.collect(Collectors.toList());
}
void loadFullCommits() {
QueryParams params = QueryParamsBuilder
.withLimit(Integer.MAX_VALUE)
.commitIds(commitsMap.keySet().stream().map(cm -> cm.getId()).collect(toSet()))
.build();
repository.getSnapshots(params).stream().forEach(s -> commitsMap.get(s.getCommitMetadata()).append(s));
}
CdoSnapshot findLatestTo(CommitMetadata rootContext, GlobalId targetId) {
if (!commitsMap.containsKey(rootContext)) {
return null;
}
final List<CdoSnapshot> found = new ArrayList<>();
iterateReverseUntil(ce -> {
if (ce.getAny(targetId) != null) {
found.add(ce.getAny(targetId));
}
}, rootContext);
if (found.size() == 0) {
return null;
}
return found.get(found.size() - 1);
}
void fillMissingParents() {
Map<GlobalId, CdoSnapshot> movingLatest = new HashMap<>();
iterateReverse(commitEntry -> {
commitEntry.getMissingParents().stream()
.filter(movingLatest::containsKey)
.forEach(voId -> {
System.out.println("adding missing parent: " + commitEntry.commitMetadata.getId()+" -> " +voId);
commitEntry.append(movingLatest.get(voId));
});
//update movingLatest
commitEntry.getAllStream().forEach(e -> movingLatest.put(e.getGlobalId(), e));
});
}
CommitEntry nextCommit(CdoSnapshot snapshot) {
CommitEntry entry = new CommitEntry(snapshot.getCommitMetadata());
commitsMap.put(entry.commitMetadata, entry);
commitsList.add(entry);
return entry;
}
void iterateReverse(Consumer<CommitEntry> consumer) {
ListIterator<CommitEntry> it = commitsList.listIterator(commitsList.size());
while (it.hasPrevious()) {
consumer.consume(it.previous());
}
}
void iterateReverseUntil(Consumer<CommitEntry> consumer, CommitMetadata bound) {
ListIterator<CommitEntry> it = commitsList.listIterator(commitsList.size());
while (it.hasPrevious()) {
CommitEntry ce = it.previous();
consumer.consume(ce);
if (ce.commitMetadata.equals(bound)) {
break;
}
}
}
}
private static class CommitEntry {
private final CommitMetadata commitMetadata;
private final Map<GlobalId, CdoSnapshot> entities = new HashMap<>();
private final Map<ValueObjectId, CdoSnapshot> valueObjects = new HashMap<>();
CommitEntry(CommitMetadata commitMetadata) {
this.commitMetadata = commitMetadata;
}
void append(CdoSnapshot snapshot){
if (snapshot.getGlobalId() instanceof InstanceId) {
entities.put(snapshot.getGlobalId(), snapshot);
}
if (snapshot.getGlobalId() instanceof ValueObjectId) {
valueObjects.put((ValueObjectId)snapshot.getGlobalId(), snapshot);;
}
}
CdoSnapshot getAny(GlobalId globalId) {
if (entities.containsKey(globalId)) {
return entities.get(globalId);
}
return valueObjects.get(globalId);
}
Collection<CdoSnapshot> getEntities() {
return entities.values();
}
Stream<CdoSnapshot> getAllStream() {
return Stream.concat(valueObjects.values().stream(), entities.values().stream());
}
Set<GlobalId> getMissingParents() {
Set<GlobalId> result = valueObjects.keySet().stream()
.map(voId -> voId.getOwnerId())
.filter(instanceId -> !entities.containsKey(instanceId))
.collect(toSet());
result.addAll(valueObjects.keySet().stream()
.flatMap(voId -> voId.getParentValueObjectIds().stream())
.filter(voId -> ! valueObjects.containsKey(voId))
.collect(toSet()));
return result;
}
}
}