/**
* Copyright (c) 2016, All Contributors (see CONTRIBUTORS file)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.eventsourcing.queries;
import com.eventsourcing.EntityHandle;
import com.eventsourcing.hlc.HybridTimestamp;
import com.eventsourcing.index.EntityIndex;
import com.googlecode.cqengine.IndexedCollection;
import com.googlecode.cqengine.attribute.Attribute;
import com.googlecode.cqengine.attribute.SimpleAttribute;
import com.googlecode.cqengine.query.Query;
import com.googlecode.cqengine.query.logical.Or;
import com.googlecode.cqengine.query.option.QueryOptions;
import com.googlecode.cqengine.query.simple.SimpleQuery;
import com.googlecode.cqengine.resultset.ResultSet;
import lombok.Value;
import lombok.experimental.Accessors;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static com.googlecode.cqengine.query.QueryFactory.and;
import static com.googlecode.cqengine.query.QueryFactory.greaterThan;
/**
* IsLatestEntity filters for entities that are the most recent against a queryFunction.
*
* For example, to query the latest NameChanged across all entities:
*
* <code>
* isLatestEntity((h) -> equal(NameChanged.REFERENCE_ID, h.get().reference()),
* NameChanged.TIMESTAMP)
* </code>
*
* @see QueryFactory#isLatestEntity(Function, EntityIndex)
* @see QueryFactory#isLatestEntity(Query, EntityIndex)
*
* @deprecated See {@link Max}
*
* @param <O>
*/
@Deprecated
public class IsLatestEntity<O extends EntityHandle> extends SimpleQuery<O, HybridTimestamp> {
private final IndexedCollection<O> collection;
private final Attribute<O, HybridTimestamp> timestampAttribute;
private Function<O, Query<O>> queryFunction;
private Query<O> query;
/**
* @deprecated
* @param collection collection to query against
* @param queryFunction query returning function
* @param timestampAttribute timestamp attribute.
*/
@Deprecated
public IsLatestEntity(IndexedCollection<O> collection,
Function<O, Query<O>> queryFunction,
Attribute<O, HybridTimestamp> timestampAttribute) {
super(timestampAttribute);
this.collection = collection;
this.queryFunction = queryFunction;
this.timestampAttribute = timestampAttribute;
}
/**
* @deprecated
* @param collection collection to query against
* @param query query
* @param timestampAttribute timestamp attribute.
*/
@Deprecated
public IsLatestEntity(IndexedCollection<O> collection,
Query<O> query,
Attribute<O, HybridTimestamp> timestampAttribute) {
super(timestampAttribute);
this.collection = collection;
this.query = query;
this.timestampAttribute = timestampAttribute;
}
/**
* @param queryFunction query returning function
* @param timestampAttribute timestamp attribute.
*/
public IsLatestEntity(Function<O, Query<O>> queryFunction,
Attribute<O, HybridTimestamp> timestampAttribute) {
super(timestampAttribute);
this.collection = null;
this.queryFunction = queryFunction;
this.timestampAttribute = timestampAttribute;
}
/**
* @param query query
* @param timestampAttribute timestamp attribute.
*/
public IsLatestEntity(Query<O> query,
Attribute<O, HybridTimestamp> timestampAttribute) {
super(timestampAttribute);
this.collection = null;
this.query = query;
this.timestampAttribute = timestampAttribute;
}
@Value
@Accessors(fluent = true)
private static class TerminatedRecords<O> {
private Map<Query<O>, UUID> queries = new HashMap<>();
}
private Optional<Boolean> terminatedQuery(O object, Query<O> query, QueryOptions queryOptions) {
if (queryOptions.get(TerminatedRecords.class) == null) {
queryOptions.put(TerminatedRecords.class, new TerminatedRecords<>());
}
TerminatedRecords terminatedRecords = queryOptions.get(TerminatedRecords.class);
UUID record = (UUID) terminatedRecords.queries().get(query);
if (record == null) {
return Optional.empty();
}
return Optional.of(record == object.uuid());
}
private boolean matches(ResultSet<O> resultSet, Query<O> query, O object, QueryOptions queryOptions) {
boolean matches = resultSet.size() == 0;
if (matches) {
@SuppressWarnings("unchecked")
TerminatedRecords<O> terminatedRecords = queryOptions.get(TerminatedRecords.class);
terminatedRecords.queries().put(query, object.uuid());
}
return matches;
}
@Override
protected boolean matchesSimpleAttribute(SimpleAttribute<O, HybridTimestamp> attribute, O object, QueryOptions
queryOptions) {
Query<O> actualQuery = query == null ? queryFunction.apply(object) : query;
Optional<Boolean> terminatedQuery = terminatedQuery(object, actualQuery, queryOptions);
if (terminatedQuery.isPresent()) {
return terminatedQuery.get();
}
HybridTimestamp value = attribute.getValue(object, queryOptions);
IndexedCollection<O> collection = (IndexedCollection<O>) getCollection(queryOptions);
try (ResultSet<O> resultSet = collection.retrieve(and(
actualQuery,
greaterThan(timestampAttribute, value)))) {
return matches(resultSet, actualQuery, object, queryOptions);
}
}
private Iterable<O> getCollection(QueryOptions queryOptions) {
return this.collection == null ? queryOptions.get(Iterable.class) : this
.collection;
}
@Override
protected boolean matchesNonSimpleAttribute(Attribute<O, HybridTimestamp> attribute, O object, QueryOptions
queryOptions) {
Query<O> actualQuery = query == null ? queryFunction.apply(object) : query;
Optional<Boolean> terminatedQuery = terminatedQuery(object, actualQuery, queryOptions);
if (terminatedQuery.isPresent()) {
return terminatedQuery.get();
}
Iterable<HybridTimestamp> values = attribute.getValues(object, queryOptions);
List<Query<O>> conditions = StreamSupport.stream(values.spliterator(), false)
.map(v -> greaterThan(timestampAttribute, v))
.collect(Collectors.toList());
Query<O> timestampQuery = conditions.size() == 1 ? conditions.get(0) : new Or<>(conditions);
IndexedCollection<O> collection = (IndexedCollection<O>) getCollection(queryOptions);
try (ResultSet<O> resultSet = collection.retrieve(and(
actualQuery,
timestampQuery))) {
return matches(resultSet, actualQuery, object, queryOptions);
}
}
@Override protected int calcHashCode() {
int result = attribute.hashCode();
result = 31 * result + (query == null ? queryFunction : query).hashCode();
result = 31 * result + timestampAttribute.hashCode();
return result;
}
@Override
public String toString() {
return "isLatestEntity(" +
"IndexedCollection<" + timestampAttribute.getObjectType().getSimpleName() + ">" +
", query=" + query == null ? queryFunction.toString() : query +
", timestamp=" + asLiteral(timestampAttribute.getAttributeName()) +
")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof IsLatestEntity)) return false;
IsLatestEntity latestReference = (IsLatestEntity) o;
if (query != null && !query.equals(query)) return false;
if (queryFunction != null && !queryFunction.equals(latestReference.queryFunction)) return false;
if (!timestampAttribute.equals(latestReference.timestampAttribute)) return false;
return true;
}
}