All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.eventsourcing.repository.StandardRepository Maven / Gradle / Ivy

There is a newer version: 0.4.6
Show newest version
/**
 * 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 klass) {
        try {
            if (!indicesConfiguredFor.contains(klass.getName())) {
                for (IndexLoader loader : indexLoaders) {
                    Iterable indices = loader.load(indexEngine, klass);
                    for (Index i : indices) {
                        IndexedCollection> 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 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 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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy