com.eventsourcing.repository.StandardRepository 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.events.CommandTerminatedExceptionally;
import com.eventsourcing.events.EventCausalityEstablished;
import com.eventsourcing.events.JavaExceptionOccurred;
import com.eventsourcing.hlc.HybridTimestamp;
import com.eventsourcing.hlc.NTPServerTimeProvider;
import com.eventsourcing.hlc.PhysicalTimeProvider;
import com.eventsourcing.index.IndexEngine;
import com.eventsourcing.index.IndexLoader;
import com.eventsourcing.index.JavaStaticFieldIndexLoader;
import com.eventsourcing.migrations.events.EntityLayoutIntroduced;
import com.eventsourcing.repository.commands.IntroduceEntityLayouts;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.AbstractService;
import com.google.common.util.concurrent.Service;
import com.google.common.util.concurrent.ServiceManager;
import com.googlecode.cqengine.IndexedCollection;
import com.googlecode.cqengine.index.Index;
import lombok.Builder;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@Component(configurationPolicy = ConfigurationPolicy.REQUIRE,
property = {"Journal.target=", "IndexEngine.target=", "LockProvider.target=", "jmx.objectname=com.eventsourcing:type=repository"})
@Slf4j
public class StandardRepository extends AbstractService implements Repository, RepositoryMBean {
@SneakyThrows
public StandardRepository() {
setPhysicalTimeProvider(new NTPServerTimeProvider(new String[]{"localhost"}));
setLockProvider(new LocalLockProvider());
bindIndexLoader(new JavaStaticFieldIndexLoader());
}
@Builder
@SneakyThrows
public StandardRepository(Journal journal, PhysicalTimeProvider physicalTimeProvider,
IndexEngine indexEngine, LockProvider lockProvider) {
setPhysicalTimeProvider(physicalTimeProvider == null ?
new NTPServerTimeProvider(new String[]{"localhost"}) : physicalTimeProvider);
setLockProvider(lockProvider == null ? new LocalLockProvider() : lockProvider);
setJournal(journal);
setIndexEngine(indexEngine);
bindIndexLoader(new JavaStaticFieldIndexLoader());
}
@Getter
private Journal journal;
@Getter
private Set> commands = new HashSet<>();
@Getter
private Set> events = new HashSet<>();
@Getter
private PhysicalTimeProvider physicalTimeProvider;
@Getter
private IndexEngine indexEngine;
@Getter
private LockProvider lockProvider;
private ServiceManager services;
private CommandConsumer commandConsumer;
private List entitySubscribers = new ArrayList<>();
@Activate
protected void activate(ComponentContext ctx) {
if (!isRunning()) {
startAsync().awaitRunning();
}
}
private List initialization = new ArrayList<>();
@Override @SuppressWarnings("unchecked")
protected void doStart() {
if (journal == null) {
notifyFailed(new IllegalStateException("journal == null"));
}
if (physicalTimeProvider == null) {
notifyFailed(new IllegalStateException("physicalTimeProvider == null"));
}
if (indexEngine == null) {
notifyFailed(new IllegalStateException("indexEngine == null"));
}
if (lockProvider == null) {
notifyFailed(new IllegalStateException("lockProvider == null"));
}
addEventSetProvider(() -> {
List> classes = Arrays
.asList(CommandTerminatedExceptionally.class, EventCausalityEstablished.class,
EntityLayoutIntroduced.class, JavaExceptionOccurred.class);
return new HashSet<>(classes);
});
addCommandSetProvider(() -> {
List> classes = Arrays
.asList(IntroduceEntityLayouts.class);
return new HashSet<>(classes);
});
journal.setRepository(this);
indexEngine.setJournal(journal);
indexEngine.setRepository(this);
services = new ServiceManager(Arrays.asList(journal, indexEngine, lockProvider, physicalTimeProvider).stream().
filter(s -> !s.isRunning()).collect(Collectors.toSet()));
services.startAsync().awaitHealthy();
initialization.forEach(Runnable::run);
initialization.clear();
commandConsumer = new CommandConsumerImpl(commands, physicalTimeProvider, this, journal, indexEngine,
lockProvider);
commandConsumer.startAsync().awaitRunning();
journal.onCommandsAdded(commands);
journal.onEventsAdded(events);
publish(new IntroduceEntityLayouts(Iterables.concat(commands, events))).join();
notifyStarted();
}
private Set indicesConfiguredFor = new HashSet<>();
@SneakyThrows
private boolean configureIndices(Class extends Entity> klass) {
try {
if (!indicesConfiguredFor.contains(klass.getName())) {
for (IndexLoader loader : indexLoaders) {
Iterable indices = loader.load(indexEngine, klass);
for (Index i : indices) {
IndexedCollection extends EntityHandle extends Entity>> collection =
indexEngine.getIndexedCollection(klass);
boolean hasIndex = StreamSupport.stream(collection.getIndexes().spliterator(), false)
.anyMatch(index -> index.equals(i));
if (!hasIndex) {
collection.addIndex(i);
}
}
}
indicesConfiguredFor.add(klass.getName());
}
} catch (IndexEngine.IndexNotSupported e) {
notifyFailed(e);
return true;
}
return false;
}
@Override
protected void doStop() {
commandConsumer.stopAsync().awaitTerminated();
services.stopAsync().awaitStopped();
// Try stopping services that were started beforehand and didn't
// make it into `services`
Arrays.asList(journal, indexEngine, lockProvider, physicalTimeProvider).stream().
filter(Service::isRunning).forEach(s -> s.stopAsync().awaitTerminated());
notifyStopped();
}
@Reference
@Override
public void setJournal(Journal journal) throws IllegalStateException {
if (isRunning()) {
throw new IllegalStateException();
}
this.journal = journal;
}
@Reference
@Override
public void setIndexEngine(IndexEngine indexEngine) throws IllegalStateException {
if (isRunning()) {
throw new IllegalStateException();
}
this.indexEngine = indexEngine;
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
@Override
public void addCommandSetProvider(CommandSetProvider provider) {
final Set> newCommands = provider.getCommands();
Runnable runnable = () -> {
for (Class extends Entity> klass : newCommands) {
if (configureIndices(klass)) return;
}
};
this.commands.addAll(newCommands);
if (isRunning()) {
// apply immediately
runnable.run();
journal.onCommandsAdded(newCommands);
publish(new IntroduceEntityLayouts(Iterables.concat(newCommands))).join();
} else {
initialization.add(runnable);
}
}
@Override
public void removeCommandSetProvider(CommandSetProvider provider) {
final Set> providedCommands = provider.getCommands();
commands.removeAll(providedCommands);
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
@Override
public void addEntitySubscriber(EntitySubscriber subscriber) {
entitySubscribers.add(subscriber);
}
@Override
public void removeEntitySubscriber(EntitySubscriber subscriber) {
entitySubscribers.remove(subscriber);
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
@Override
public void addEventSetProvider(EventSetProvider provider) {
final Set> newEvents = provider.getEvents();
Runnable runnable = () -> {
for (Class extends Entity> klass : newEvents) {
if (configureIndices(klass)) return;
}
};
this.events.addAll(newEvents);
if (isRunning()) {
// apply immediately
runnable.run();
journal.onEventsAdded(newEvents);;
publish(new IntroduceEntityLayouts(Iterables.concat(newEvents))).join();
} else {
initialization.add(runnable);
}
}
@Override
public void removeEventSetProvider(EventSetProvider provider) {
final Set> providedEvents = provider.getEvents();
events.removeAll(providedEvents);
}
private final Set indexLoaders = new LinkedHashSet<>();
@Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE)
public void bindIndexLoader(IndexLoader loader) {
indexLoaders.add(loader);
}
public void unbindIndexLoader(IndexLoader loader) {
indexLoaders.remove(loader);
}
@Reference
@Override
public void setPhysicalTimeProvider(PhysicalTimeProvider timeProvider) throws IllegalStateException {
if (isRunning()) {
throw new IllegalStateException();
}
this.physicalTimeProvider = timeProvider;
}
@Reference
@Override
public void setLockProvider(LockProvider lockProvider) throws IllegalStateException {
if (isRunning()) {
throw new IllegalStateException();
}
this.lockProvider = lockProvider;
}
@Override
public , S, C> CompletableFuture publish(T command) {
return this.commandConsumer.publish(command, entitySubscribers);
}
@Override public HybridTimestamp getTimestamp() {
return commandConsumer.getTimestamp();
}
@Override
public String[] getInstalledCommands() {
return commands.stream().map(Class::getName).toArray(String[]::new);
}
@Override
public String[] getInstalledEvents() {
return events.stream().map(Class::getName).toArray(String[]::new);
}
}