/**
* Copyright (c) 2015 The original author or authors
*
* 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.reveno.atp.core;
import org.reveno.atp.api.*;
import org.reveno.atp.api.Configuration.CpuConsumption;
import org.reveno.atp.api.commands.CommandContext;
import org.reveno.atp.api.commands.EmptyResult;
import org.reveno.atp.api.commands.Result;
import org.reveno.atp.api.domain.RepositoryData;
import org.reveno.atp.api.domain.WriteableRepository;
import org.reveno.atp.api.dynamic.AbstractDynamicTransaction;
import org.reveno.atp.api.dynamic.DirectTransactionBuilder;
import org.reveno.atp.api.dynamic.DynamicCommand;
import org.reveno.atp.api.query.QueryManager;
import org.reveno.atp.api.query.ViewsMapper;
import org.reveno.atp.api.transaction.TransactionContext;
import org.reveno.atp.api.transaction.TransactionInterceptor;
import org.reveno.atp.api.transaction.TransactionStage;
import org.reveno.atp.commons.NamedThreadFactory;
import org.reveno.atp.core.api.*;
import org.reveno.atp.core.api.serialization.EventsInfoSerializer;
import org.reveno.atp.core.api.serialization.TransactionInfoSerializer;
import org.reveno.atp.core.api.storage.FoldersStorage;
import org.reveno.atp.core.api.storage.JournalsStorage;
import org.reveno.atp.core.api.storage.SnapshotStorage;
import org.reveno.atp.core.disruptor.DisruptorEventPipeProcessor;
import org.reveno.atp.core.disruptor.DisruptorTransactionPipeProcessor;
import org.reveno.atp.core.disruptor.ProcessorContext;
import org.reveno.atp.core.engine.WorkflowEngine;
import org.reveno.atp.core.engine.components.*;
import org.reveno.atp.core.engine.components.DefaultIdGenerator.NextIdTransaction;
import org.reveno.atp.core.engine.processor.PipeProcessor;
import org.reveno.atp.core.engine.processor.TransactionPipeProcessor;
import org.reveno.atp.core.events.Event;
import org.reveno.atp.core.events.EventHandlersManager;
import org.reveno.atp.core.events.EventPublisher;
import org.reveno.atp.core.impl.EventsCommitInfoImpl;
import org.reveno.atp.core.impl.TransactionCommitInfoImpl;
import org.reveno.atp.core.repository.HashMapRepository;
import org.reveno.atp.core.repository.MutableModelRepository;
import org.reveno.atp.core.repository.SnapshotBasedModelRepository;
import org.reveno.atp.core.restore.DefaultSystemStateRestorer;
import org.reveno.atp.core.serialization.SimpleEventsSerializer;
import org.reveno.atp.core.snapshots.SnapshottersManager;
import org.reveno.atp.core.storage.FileSystemStorage;
import org.reveno.atp.core.views.ViewsDefaultStorage;
import org.reveno.atp.core.views.ViewsManager;
import org.reveno.atp.core.views.ViewsProcessor;
import org.reveno.atp.utils.Exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
public class Engine implements Reveno {
public Engine(FoldersStorage foldersStorage, JournalsStorage journalsStorage,
SnapshotStorage snapshotStorage, ClassLoader classLoader) {
this.classLoader = classLoader;
this.foldersStorage = foldersStorage;
this.journalsStorage = journalsStorage;
this.snapshotStorage = snapshotStorage;
this.snapshotsManager = new SnapshottersManager(snapshotStorage, classLoader);
serializer = new SerializersChain(classLoader);
}
public Engine(File baseDir) {
this(baseDir, Engine.class.getClassLoader());
}
public Engine(File baseDir, ClassLoader classLoader) {
FileSystemStorage storage = new FileSystemStorage(baseDir, config.revenoJournaling());
this.classLoader = classLoader;
this.foldersStorage = storage;
this.journalsStorage = storage;
this.snapshotStorage = storage;
this.snapshotsManager = new SnapshottersManager(snapshotStorage, classLoader);
serializer = new SerializersChain(classLoader);
}
public Engine(String baseDir) {
this(new File(baseDir), Engine.class.getClassLoader());
}
public Engine(String baseDir, ClassLoader classLoader) {
this(new File(baseDir), classLoader);
}
@Override
public boolean isStarted() {
return isStarted;
}
@Override
public void startup() {
log.info("Engine startup initiated.");
if (isStarted)
throw new IllegalStateException("Can't startup engine which is already started.");
init();
connectSystemHandlers();
JournalsStorage.JournalStore temp = journalsManager.rollTemp();
eventPublisher.getPipe().start();
workflowEngine.init();
viewsProcessor.process(repository);
workflowEngine.setLastTransactionId(restorer.restore(journalVersionAfterSnapshot(), repository).getLastTransactionId());
workflowEngine.getPipe().sync();
eventPublisher.getPipe().sync();
journalsManager.rollFrom(temp, workflowEngine.getLastTransactionId());
log.info("Engine is started.");
isStarted = true;
postInit();
}
@Override
public void shutdown() {
log.info("Shutting down engine.");
isStarted = false;
workflowEngine.shutdown();
eventPublisher.getPipe().shutdown();
interceptors.getInterceptors(TransactionStage.JOURNALING).forEach(TransactionInterceptor::destroy);
interceptors.getInterceptors(TransactionStage.REPLICATION).forEach(TransactionInterceptor::destroy);
interceptors.getInterceptors(TransactionStage.TRANSACTION).forEach(TransactionInterceptor::destroy);
journalsManager.destroy();
eventsManager.close();
snapshotterIntervalExecutor.shutdown();
if (config.revenoSnapshotting().atShutdown()) {
log.info("Preforming shutdown snapshotting...");
snapshotAll();
}
if (repository instanceof Destroyable) {
((Destroyable)repository).destroy();
}
log.info("Engine was stopped.");
}
@Override
public RevenoManager domain() {
return new RevenoManager() {
protected RepositorySnapshotter lastSnapshotter;
@Override
public DirectTransactionBuilder transaction(String name,
BiConsumer<AbstractDynamicTransaction, TransactionContext> handler) {
return new DirectTransactionBuilder(name, handler, serializer, transactionsManager,
commandsManager, classLoader);
}
@Override
public <E, V> void viewMapper(Class<E> entityType, Class<V> viewType, ViewsMapper<E, V> mapper) {
viewsManager.register(entityType, viewType, mapper);
}
@Override
public <T> void transactionAction(Class<T> transaction, BiConsumer<T, TransactionContext> handler) {
serializer.registerTransactionType(transaction);
transactionsManager.registerTransaction(transaction, handler);
}
@Override
public <T> void transactionWithCompensatingAction(Class<T> transaction,
BiConsumer<T, TransactionContext> handler,
BiConsumer<T, TransactionContext> compensatingAction) {
serializer.registerTransactionType(transaction);
transactionsManager.registerTransaction(transaction, handler);
transactionsManager.registerTransaction(transaction, compensatingAction, true);
}
@Override
public RevenoManager snapshotWith(RepositorySnapshotter snapshotter) {
snapshotsManager.registerSnapshotter(snapshotter);
lastSnapshotter = snapshotter;
return this;
}
@Override
public void restoreWith(RepositorySnapshotter snapshotter) {
restoreWith = snapshotter;
}
@Override
public void andRestoreWithIt() {
restoreWith = lastSnapshotter;
}
@Override
public void resetSnapshotters() {
snapshotsManager.resetSnapshotters();
}
@Override
public <C> void command(Class<C> commandType, BiConsumer<C, CommandContext> handler) {
serializer.registerTransactionType(commandType);
commandsManager.register(commandType, handler);
}
@Override
public <C, U> void command(Class<C> commandType, Class<U> resultType, BiFunction<C, CommandContext, U> handler) {
serializer.registerTransactionType(commandType);
commandsManager.register(commandType, resultType, handler);
}
@Override
public void serializeWith(List<TransactionInfoSerializer> serializers) {
serializer = new SerializersChain(serializers);
}
};
}
@Override
public QueryManager query() {
return viewsStorage;
}
@Override
public EventsManager events() {
return eventsManager;
}
@Override
public ClusterManager cluster() {
return null;
}
@Override
public Configuration config() {
return config;
}
@Override
public <R> CompletableFuture<Result<R>> executeCommand(Object command) {
checkIsStarted();
return workflowEngine.getPipe().execute(command);
}
@Override
public CompletableFuture<EmptyResult> performCommands(List<Object> commands) {
checkIsStarted();
return workflowEngine.getPipe().process(commands);
}
@Override
public <R> CompletableFuture<Result<R>> execute(DynamicCommand command, Map<String, Object> args) {
try {
return executeCommand(command.newCommand(args));
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Failed to execute command.", e);
}
}
@SuppressWarnings("unchecked")
@Override
public <R> R executeSync(DynamicCommand command, Map<String, Object> args) {
try {
Result<? extends R> r = (Result<? extends R>) execute(command, args).get();
if (!r.isSuccess()) {
throw new RuntimeException("Failed to execute command.", r.getException());
}
return r.getResult();
} catch (InterruptedException | ExecutionException e) {
throw Exceptions.runtime(e);
}
}
@SuppressWarnings("unchecked")
@Override
public <R> R executeSync(Object command) {
try {
Result<? extends R> r = (Result<? extends R>) executeCommand(command).get();
if (!r.isSuccess()) {
throw new RuntimeException("Failed to execute command.", r.getException());
}
return r.getResult();
} catch (InterruptedException | ExecutionException e) {
throw Exceptions.runtime(e);
}
}
@Override
public <R> R executeSync(String command, Map<String, Object> args) {
Optional<DynamicCommand> dc = getDynamicCommand(command);
return executeSync(dc.get(), args);
}
@Override
public <R> R executeSync(String command) {
return executeSync(command, Collections.emptyMap());
}
@Override
public <R> CompletableFuture<Result<R>> execute(String command, Map<String, Object> args) {
Optional<DynamicCommand> dc = getDynamicCommand(command);
return execute(dc.get(), args);
}
@Override
public <R> CompletableFuture<Result<R>> execute(String command) {
return execute(command, Collections.emptyMap());
}
public InterceptorCollection interceptors() {
return interceptors;
}
public boolean isClustered() {
return !failoverManager().isSingleNode();
}
protected void postInit() {
if (config.revenoSnapshotting().interval() > 0) {
snapshotterIntervalExecutor.scheduleAtFixedRate(() -> {
workflowEngine.getPipe().process((c,f) -> {
c.reset().systemFlag(SnapshottingInterceptor.SNAPSHOTTING_FLAG).future(f);
});
}, config.revenoSnapshotting().interval(), config.revenoSnapshotting().interval(), TimeUnit.MILLISECONDS);
}
}
protected Optional<DynamicCommand> getDynamicCommand(String command) {
Optional<DynamicCommand> dc = DirectTransactionBuilder.loadExistedCommand(command, classLoader);
if (!dc.isPresent()) {
throw new IllegalArgumentException(String.format("Command %s can't be found! Make sure it was registered" +
" by domain().transaction(..).command() call!", command));
}
return dc;
}
protected void checkIsStarted() {
if (!isStarted)
throw new IllegalStateException("The Engine must be started first.");
}
protected WriteableRepository repository() {
return new HashMapRepository(config.mapCapacity(), config.mapLoadFactor());
}
protected void init() {
repository = factory.create(loadLastSnapshot());
viewsStorage = new ViewsDefaultStorage(config.mapCapacity(), config.mapLoadFactor());
viewsProcessor = new ViewsProcessor(viewsManager, viewsStorage);
processor = new DisruptorTransactionPipeProcessor(txBuilder, config.cpuConsumption(), config.revenoDisruptor().bufferSize(), executor);
eventProcessor = new DisruptorEventPipeProcessor(CpuConsumption.NORMAL, config.revenoDisruptor().bufferSize(), eventExecutor);
journalsManager = new JournalsManager(journalsStorage, config.revenoJournaling());
EngineEventsContext eventsContext = new EngineEventsContext().serializer(eventsSerializer)
.eventsCommitBuilder(eventBuilder).eventsJournaler(journalsManager.getEventsJournaler()).manager(eventsManager);
eventPublisher = new EventPublisher(eventProcessor, eventsContext);
workflowContext = new EngineWorkflowContext().serializers(serializer).repository(repository).classLoader(classLoader)
.viewsProcessor(viewsProcessor).transactionsManager(transactionsManager).commandsManager(commandsManager)
.eventPublisher(eventPublisher).transactionCommitBuilder(txBuilder).transactionJournaler(journalsManager.getTransactionsJournaler())
.idGenerator(idGenerator).journalsManager(journalsManager).snapshotsManager(snapshotsManager).interceptorCollection(interceptors)
.configuration(config).failoverManager(failoverManager());
workflowEngine = new WorkflowEngine(processor, workflowContext, config.modelType());
restorer = new DefaultSystemStateRestorer(journalsStorage, workflowContext, eventsContext, workflowEngine);
}
protected long journalVersionAfterSnapshot() {
if (restoreWith != null) {
return restoreWith.lastJournalVersionSnapshotted();
}
return snapshotsManager.getAll().stream()
.filter(RepositorySnapshotter::hasAny)
.findFirst()
.map(RepositorySnapshotter::lastJournalVersionSnapshotted)
.orElse(0L);
}
protected Optional<RepositoryData> loadLastSnapshot() {
if (restoreWith != null && restoreWith.hasAny()) {
return Optional.of(restoreWith.load());
}
return Optional.ofNullable(snapshotsManager.getAll().stream()
.filter(RepositorySnapshotter::hasAny).findFirst()
.map(RepositorySnapshotter::load).orElse(null));
}
protected void connectSystemHandlers() {
domain().transactionAction(NextIdTransaction.class, idGenerator);
if (config.revenoSnapshotting().every() != -1 || config.revenoSnapshotting().interval() > 0) {
TransactionInterceptor nTimeSnapshotter = new SnapshottingInterceptor(config,
snapshotsManager, snapshotStorage, journalsStorage, journalsManager);
interceptors.add(TransactionStage.TRANSACTION, nTimeSnapshotter);
interceptors.add(TransactionStage.JOURNALING, nTimeSnapshotter);
}
}
protected TxRepository createRepository() {
switch (config.modelType()) {
case IMMUTABLE : return new SnapshotBasedModelRepository(repository());
case MUTABLE : return new MutableModelRepository(repository(), new SerializersChain(classLoader), classLoader);
}
return null;
}
/**
* Since it is very unsecure to obtain Repository data out of transaction workflow,
* this method should be used *only* when engine is stopped.
*/
protected synchronized void snapshotAll() {
snapshotsManager.getAll().forEach(s -> {
RepositorySnapshotter.SnapshotIdentifier id = s.prepare();
s.snapshot(repository.getData(), id);
s.commit(journalsStorage.getLastStoreVersion(), id);
});
}
protected FailoverManager failoverManager() {
return new UnclusteredFailoverManager();
}
protected volatile boolean isStarted = false;
protected TxRepository repository;
protected SerializersChain serializer;
protected SystemStateRestorer restorer;
protected ViewsProcessor viewsProcessor;
protected WorkflowEngine workflowEngine;
protected EventPublisher eventPublisher;
protected TransactionPipeProcessor<ProcessorContext> processor;
protected PipeProcessor<Event> eventProcessor;
protected JournalsManager journalsManager;
protected EngineWorkflowContext workflowContext;
protected ViewsDefaultStorage viewsStorage;
protected RepositorySnapshotter restoreWith;
protected EventsInfoSerializer eventsSerializer = new SimpleEventsSerializer();
protected TransactionCommitInfo.Builder txBuilder = new TransactionCommitInfoImpl.PojoBuilder();
protected EventsCommitInfo.Builder eventBuilder = new EventsCommitInfoImpl.PojoBuilder();
protected EventHandlersManager eventsManager = new EventHandlersManager();
protected ViewsManager viewsManager = new ViewsManager();
protected TransactionsManager transactionsManager = new TransactionsManager();
protected CommandsManager commandsManager = new CommandsManager();
protected InterceptorCollection interceptors = new InterceptorCollection();
protected DefaultIdGenerator idGenerator = new DefaultIdGenerator();
protected RevenoConfiguration config = new RevenoConfiguration();
protected ClassLoader classLoader;
protected JournalsStorage journalsStorage;
protected FoldersStorage foldersStorage;
protected SnapshotStorage snapshotStorage;
protected SnapshottersManager snapshotsManager;
protected final ExecutorService executor = Executors.newFixedThreadPool(7, new NamedThreadFactory("tx"));
protected final ExecutorService eventExecutor = Executors.newFixedThreadPool(3, new NamedThreadFactory("evn"));
protected final ScheduledExecutorService snapshotterIntervalExecutor = Executors.newSingleThreadScheduledExecutor();
protected static final Logger log = LoggerFactory.getLogger(Engine.class);
protected final TxRepositoryFactory factory = (repositoryData) -> {
final TxRepository repository = createRepository();
repositoryData.ifPresent(d -> repository.load(d.data));
return repository;
};
}