/**
* 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.Entity;
import com.eventsourcing.EntityHandle;
import com.eventsourcing.Model;
import com.eventsourcing.hlc.HybridTimestamp;
import com.eventsourcing.index.EntityIndex;
import com.googlecode.cqengine.query.Query;
import com.googlecode.cqengine.query.option.EngineThresholds;
import com.googlecode.cqengine.resultset.ResultSet;
import java.util.Iterator;
import java.util.Optional;
import java.util.UUID;
import static com.eventsourcing.queries.QueryFactory.*;
/**
* Provides a query for retrieving the latest entry, associated with a model. For example,
* to retrieve the latest e-mail change:
*
* <code>
* latestAssociatedEntity(EmailChanged.class, EmailChanged.REFERENCE_ID, EmailChanged.TIMESTAMP)
* </code>
*/
public interface LatestAssociatedEntryQuery extends Model {
/**
* Invokes {@link #latestAssociatedEntity(Class, EntityIndex, EntityIndex, Query[])} with no additional queries
*
* @param klass Entity class
* @param keyAttribute Entity attribute that references model's ID
* @param timestampAttribute Entity attribute that holds the timestamp
* @param <T> Entity type
* @return Non-empty {@link Optional} if the entity is found, an empty one otherwise.
*/
default <T extends Entity> Optional<T> latestAssociatedEntity(Class<T> klass,
EntityIndex<T, UUID> keyAttribute,
EntityIndex<T, HybridTimestamp> timestampAttribute) {
@SuppressWarnings("unchecked")
Optional<T> last = latestAssociatedEntity(klass, keyAttribute, timestampAttribute, (Query<EntityHandle<T>>[]) new Query[]{});
return last;
}
/**
* Invokes {@link #latestAssociatedEntity(Class, EntityIndex, EntityIndex, Query[])} with one additional query
* @param klass Entity class
* @param keyAttribute Entity attribute that references model's ID
* @param timestampAttribute Entity attribute that holds the timestamp
* @param additionalQuery An additional condition
* @param <T> Entity type
* @return Non-empty {@link Optional} if the entity is found, an empty one otherwise.
*/
default <T extends Entity> Optional<T> latestAssociatedEntity(Class<T> klass,
EntityIndex<T, UUID> keyAttribute,
EntityIndex<T, HybridTimestamp> timestampAttribute,
Query<EntityHandle<T>> additionalQuery
) {
@SuppressWarnings("unchecked")
Optional<T> last = latestAssociatedEntity(klass, keyAttribute, timestampAttribute, (Query<EntityHandle<T>>[]) new Query[]{additionalQuery});
return last;
}
/**
* Queries the latest entity associated with the model. For example,
* to retrieve the latest e-mail change:
*
* <code>
* latestAssociatedEntity(EmailChanged.class, EmailChanged.REFERENCE_ID, EmailChanged.TIMESTAMP)
* </code>
*
* If additional conditions are required, they can be added to the end of the method call:
*
* <code>
* latestAssociatedEntity(EmailChanged.class, EmailChanged.REFERENCE_ID, EmailChanged.TIMESTAMP,
* equal(EmailChanged.APPROVED, true))
* </code>
*
* @param klass Entity class
* @param keyAttribute Entity attribute that references model's ID
* @param timestampAttribute Entity attribute that holds the timestamp
* @param additionalQueries Additional conditions
* @param <T> Entity type
* @return Non-empty {@link Optional} if the entity is found, an empty one otherwise.
*/
default <T extends Entity> Optional<T>
latestAssociatedEntity(Class<T> klass,
EntityIndex<T, UUID> keyAttribute, EntityIndex<T, HybridTimestamp> timestampAttribute,
Query<EntityHandle<T>> ...additionalQueries) {
Query<EntityHandle<T>> query = equal(keyAttribute, getId());
for (Query<EntityHandle<T>> q : additionalQueries) {
query = and(query, q);
}
try (ResultSet<EntityHandle<T>> resultSet = getRepository()
.query(klass, query,
queryOptions(orderBy(descending(timestampAttribute)),
applyThresholds(threshold(EngineThresholds.INDEX_ORDERING_SELECTIVITY, 0.5))))) {
Iterator<EntityHandle<T>> iterator = resultSet.iterator();
if (!iterator.hasNext()) {
return Optional.empty();
} else {
return Optional.of(iterator.next().get());
}
}
}
}