/*
* Copyright (c) 2010-2016. Axon Framework
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.axonframework.mongo.eventsourcing.eventstore;
import org.axonframework.eventsourcing.eventstore.TrackingToken;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import static java.util.Collections.unmodifiableSet;
/**
* Tracking token implementation produced by the {@link MongoEventStorageEngine} to keep track of the position in an
* event stream. Order is determined by comparing timestamp, sequence number and event id.
* <p>
* This tracking token implementation keeps track of all events retrieved in a period of time before the furthest
* position in the stream. This makes it possible to detect event entries that have a lower timestamp than that
* with the highest timestamp but are published at a later time (due to time differences between nodes).
*
* @author Rene de Waele
*/
public class MongoTrackingToken implements TrackingToken {
private final long timestamp;
private final Map<String, Long> trackedEvents;
/**
* Returns a new instance of a {@link MongoTrackingToken} with given {@code timestamp}, {@code eventIdentifier} and
* {@code sequenceNumber} for the initial event in a stream.
*
* @param timestamp the event's timestamp
* @param eventIdentifier the event's identifier
* @return initial Mongo tracking token instance
*/
public static MongoTrackingToken of(Instant timestamp, String eventIdentifier) {
return new MongoTrackingToken(timestamp.toEpochMilli(),
Collections.singletonMap(eventIdentifier, timestamp.toEpochMilli()));
}
private MongoTrackingToken(long timestamp, Map<String, Long> trackedEvents) {
this.timestamp = timestamp;
this.trackedEvents = trackedEvents;
}
/**
* Returns a new {@link MongoTrackingToken} instance based on this token but which has advanced to the event with
* given {@code timestamp}, {@code eventIdentifier} and {@code sequenceNumber}. Prior events with a timestamp
* smaller or equal than the latest event timestamp minus the given {@code lookBackTime} will not be included in the
* new token.
*
* @param timestamp the timestamp of the next event
* @param eventIdentifier the maximum distance between a gap and the token's index
* @param lookBackTime the maximum time between the latest and oldest event stored in the new key
* @return the new token that has advanced from the current token
*/
public MongoTrackingToken advanceTo(Instant timestamp, String eventIdentifier, Duration lookBackTime) {
if (trackedEvents.containsKey(eventIdentifier)) {
throw new IllegalArgumentException(
String.format("The event to advance to [%s] should not be one of the token's known events",
eventIdentifier));
}
long millis = timestamp.toEpochMilli();
LinkedHashMap<String, Long> trackedEvents = new LinkedHashMap<>(this.trackedEvents);
trackedEvents.put(eventIdentifier, millis);
long newTimestamp = Math.max(millis, this.timestamp);
return new MongoTrackingToken(newTimestamp, trim(trackedEvents, newTimestamp, lookBackTime));
}
private Map<String, Long> trim(LinkedHashMap<String, Long> priorEvents, long currentTime, Duration lookBackTime) {
Long cutOffTimestamp = currentTime - lookBackTime.toMillis();
Iterator<Long> iterator = priorEvents.values().iterator();
while (iterator.hasNext()) {
if (iterator.next().compareTo(cutOffTimestamp) < 0) {
iterator.remove();
} else {
return priorEvents;
}
}
return priorEvents;
}
/**
* Get the timestamp of the last event tracked by this token.
*
* @return the timestamp of the event with this token
*/
public Instant getTimestamp() {
return Instant.ofEpochMilli(timestamp);
}
/**
* Returns an {@link Iterable} with all known identifiers of events tracked before and including this token. Note,
* the token only stores ids of prior events if they are not too old, see
* {@link #advanceTo(Instant, String, Duration)}.
*
* @return all known event identifiers
*/
public Set<String> getKnownEventIds() {
return unmodifiableSet(trackedEvents.keySet());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MongoTrackingToken that = (MongoTrackingToken) o;
return timestamp == that.timestamp && Objects.equals(trackedEvents, that.trackedEvents);
}
@Override
public int hashCode() {
return Objects.hash(timestamp, trackedEvents);
}
@Override
public String toString() {
return "MongoTrackingToken{" + "timestamp=" + timestamp + ", trackedEvents=" + trackedEvents + '}';
}
}