com.eventsourcing.repository.CommandConsumerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eventsourcing-repository Show documentation
Show all versions of eventsourcing-repository Show documentation
Event capture and querying framework for Java
/**
* 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.repository;
import com.eventsourcing.*;
import com.eventsourcing.cep.events.DescriptionChanged;
import com.eventsourcing.events.CommandTerminatedExceptionally;
import com.eventsourcing.events.EventCausalityEstablished;
import com.eventsourcing.events.JavaExceptionOccurred;
import com.eventsourcing.hlc.HybridTimestamp;
import com.eventsourcing.hlc.PhysicalTimeProvider;
import com.eventsourcing.index.IndexEngine;
import com.google.common.util.concurrent.AbstractService;
import com.googlecode.cqengine.ConcurrentIndexedCollection;
import com.googlecode.cqengine.IndexedCollection;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Stream;
@Slf4j
class CommandConsumerImpl extends AbstractService implements CommandConsumer {
private Executor threadPool = Executors.newWorkStealingPool(Runtime.getRuntime().availableProcessors());
private final Repository repository;
private final Journal journal;
private final IndexEngine indexEngine;
private final LockProvider lockProvider;
@Getter
private HybridTimestamp timestamp;
@SneakyThrows
public CommandConsumerImpl(Iterable> commandClasses,
PhysicalTimeProvider timeProvider,
Repository repository, Journal journal, IndexEngine indexEngine,
LockProvider lockProvider) {
this.repository = repository;
this.journal = journal;
this.indexEngine = indexEngine;
this.lockProvider = lockProvider;
this.timestamp = new HybridTimestamp(timeProvider);
timestamp.update();
}
private void timestamp(Entity entity, HybridTimestamp timestamp) {
if (entity.timestamp() == null) {
timestamp.update();
entity.timestamp(timestamp.clone());
} else {
timestamp.update(timestamp.clone());
}
}
private EventStream exceptionalTerminationStream(Exception e) {
CommandTerminatedExceptionally commandTerminatedExceptionally = new CommandTerminatedExceptionally();
return EventStream.of(Stream.of( new CommandTerminatedExceptionally(),
DescriptionChanged.builder()
.description(e.getMessage())
.reference(commandTerminatedExceptionally.uuid()).build(),
new JavaExceptionOccurred(e)));
}
private void onEvent(Event event,
Map, IndexedCollection>> txCollections,
Map> subscriptions,
Collection subscribers
) {
IndexedCollection> coll = txCollections
.computeIfAbsent(event.getClass(), klass -> new ConcurrentIndexedCollection<>());
coll.add(new ResolvedEntityHandle<>(event));
subscribers.stream()
.filter(s -> s.matches(event))
.forEach(s -> subscriptions.get(s).add(event.uuid()));
}
@Override
public > CompletableFuture publish(C command, Collection
subscribers) {
Map> subscriptions = new HashMap<>();
subscribers.forEach(s -> subscriptions.put(s, new HashSet<>()));
Map, IndexedCollection>> txCollections = new HashMap<>();
CompletableFuture future = new CompletableFuture<>();
HybridTimestamp txTimestamp;
synchronized (timestamp) {
timestamp(command, timestamp);
txTimestamp = timestamp.clone();
}
final HybridTimestamp commandTimestamp = txTimestamp.clone();
threadPool.execute(
new CommandHandler<>(commandTimestamp, command, txCollections, subscriptions, subscribers,
future,
txTimestamp));
return future;
}
@Override @SuppressWarnings("unchecked")
protected void doStart() {
notifyStarted();
}
@Override
protected void doStop() {
notifyStopped();
}
private class CommandHandler> implements Runnable {
private final HybridTimestamp commandTimestamp;
private final C command;
private final Map, IndexedCollection>> txCollections;
private final Map> subscriptions;
private final Collection subscribers;
private final CompletableFuture future;
private final HybridTimestamp txTimestamp;
public CommandHandler(HybridTimestamp commandTimestamp, C command,
Map, IndexedCollection>> txCollections,
Map> subscriptions, Collection subscribers,
CompletableFuture future, HybridTimestamp txTimestamp) {
this.commandTimestamp = commandTimestamp;
this.command = command;
this.txCollections = txCollections;
this.subscriptions = subscriptions;
this.subscribers = subscribers;
this.future = future;
this.txTimestamp = txTimestamp;
}
@Override public void run() {
HybridTimestamp ts = commandTimestamp.clone();
HybridTimestamp startingTxTimestamp = ts.clone();
TrackingLockProvider lockProvider = new TrackingLockProvider(CommandConsumerImpl.this.lockProvider);
lockProvider.startAsync().awaitRunning();
EventStream eventStream;
Exception exception = null;
try {
eventStream = command.events(repository, lockProvider);
} catch (Exception e) {
eventStream = CommandConsumerImpl.this.exceptionalTerminationStream(e);
exception = e;
}
boolean pending = true;
main:
while (pending) {
Journal.Transaction tx = journal.beginTransaction();
Stream extends Event> stream = eventStream.getStream();
Iterator extends Event> iterator = stream.iterator();
try {
while (iterator.hasNext()) {
Event event = iterator.next();
CommandConsumerImpl.this.timestamp(event, ts);
event = journal.journal(tx, event);
EventCausalityEstablished causalityEstablished = EventCausalityEstablished.builder()
.event(event.uuid())
.command(
command.uuid())
.build();
CommandConsumerImpl.this.timestamp(causalityEstablished, ts);
causalityEstablished = (EventCausalityEstablished) journal.journal(tx, causalityEstablished);
CommandConsumerImpl.this.onEvent(event, txCollections, subscriptions, subscribers);
CommandConsumerImpl.this
.onEvent(causalityEstablished, txCollections, subscriptions, subscribers);
}
} catch (Exception e) {
txCollections.clear();
tx.rollback();
eventStream = CommandConsumerImpl.this.exceptionalTerminationStream(e);
ts = startingTxTimestamp;
exception = e;
continue main;
}
pending = false;
Command command_ = journal.journal(tx, command);
tx.commit();
for (Map.Entry, IndexedCollection>> pair :
txCollections.entrySet()) {
IndexedCollection> value = pair.getValue();
indexEngine.getIndexedCollection((Class) pair.getKey()).addAll(value);
}
IndexedCollection>> coll = indexEngine
.getIndexedCollection((Class>) command_.getClass());
EntityHandle> commandHandle = new JournalEntityHandle<>(journal, command_.uuid());
coll.add(new ResolvedEntityHandle<>(command_));
subscriptions.entrySet().stream()
.forEach(entry -> entry.getKey()
.accept(entry.getValue()
.stream()
.map(uuid -> new JournalEntityHandle<>(journal,
uuid))));
subscribers.stream()
.filter(s -> s.matches(command_))
.forEach(s -> s.accept(Stream.of(commandHandle)));
synchronized (timestamp) {
timestamp.update(txTimestamp);
}
T result = command.result(eventStream.getState(), repository, lockProvider);
lockProvider.release();
if (exception == null) {
future.complete(result);
} else {
future.completeExceptionally(exception);
}
}
}
}
}