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

com.eventsourcing.h2.MVStoreJournal Maven / Gradle / Ivy

The 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.h2;

import com.eventsourcing.*;
import com.eventsourcing.layout.Layout;
import com.eventsourcing.layout.ObjectSerializer;
import com.eventsourcing.layout.Serialization;
import com.eventsourcing.layout.binary.BinarySerialization;
import com.google.common.collect.Iterators;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.Bytes;
import com.google.common.util.concurrent.AbstractService;
import com.googlecode.cqengine.index.support.CloseableIterator;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.db.TransactionStore;
import org.h2.mvstore.db.TransactionStore.TransactionMap;
import org.h2.mvstore.type.ObjectDataType;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;

import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

@Component(
        service = Journal.class,
        property = {"filename=journal.db", "type=MVStoreJournal", "jmx.objectname=com.eventsourcing:type=journal,name=MVStoreJournal"})
@Slf4j
public class MVStoreJournal extends AbstractService implements Journal {
    @Getter @Setter
    private Repository repository;

    private final EntityLayoutExtractor entityLayoutExtractor = new EntityLayoutExtractor();

    @Getter(AccessLevel.PACKAGE) @Setter(AccessLevel.PACKAGE) // getter and setter for tests
    private MVStore store;
    private TransactionMap commandPayloads;
    private TransactionMap commandHashes;
    private TransactionMap hashCommands;
    private TransactionMap eventPayloads;
    private TransactionMap hashEvents;
    private TransactionMap eventHashes;

    private TransactionStore transactionStore;
    TransactionStore.Transaction readTx;

    public MVStoreJournal(MVStore store) {
        this();
        this.store = store;
    }

    private final static Serialization serialization = BinarySerialization.getInstance();

    @SneakyThrows
    public MVStoreJournal() {
    }

    @Activate
    protected void activate(ComponentContext ctx) {
        store = MVStore.open((String) ctx.getProperties().get("filename"));
    }

    @Deactivate
    protected void deactivate(ComponentContext ctx) {
        store.close();
    }

    @Override
    protected void doStart() {
        if (repository == null) {
            notifyFailed(new IllegalStateException("repository == null"));
        }

        if (store == null) {
            notifyFailed(new IllegalStateException("store == null"));
        }

        initializeStore();

        notifyStarted();
    }

    @Override
    public void onCommandsAdded(Set> commands) {
        commands.forEach(entityLayoutExtractor);
    }

    @Override
    public void onEventsAdded(Set> events) {
        events.forEach(entityLayoutExtractor);
    }


    void initializeStore() {
        MVMap info = store.openMap("info");
        info.putIfAbsent("version", 1);
        store.commit();

        transactionStore = new TransactionStore(this.store);
        transactionStore.init();

        initReadTx();
    }

    private void initReadTx() {
        readTx = transactionStore.begin();
        commandPayloads = readTx.openMap("commandPayloads", new ObjectDataType(), new ByteBufferDataType());
        commandHashes = readTx.openMap("commandHashes");
        hashCommands = readTx.openMap("hashCommands");
        eventPayloads = readTx.openMap("eventPayloads", new ObjectDataType(), new ByteBufferDataType());
        eventHashes = readTx.openMap("eventHashes");
        hashEvents = readTx.openMap("hashEvents");
    }

    @Override
    protected void doStop() {
        transactionStore.close();
        store.close();
        notifyStopped();
    }

    private Map layoutsByHash = new HashMap<>();
    private Map layoutsByClass = new HashMap<>();
    
    private Layout getLayout(Class klass) {
        Layout layout = layoutsByClass.computeIfAbsent(klass.getName(), new LayoutFunction<>(klass));
        if (getLayout(layout.getHash()) == null) {
            String encoded = BaseEncoding.base16().encode(layout.getHash());
            layoutsByHash.put(encoded, layout);
        }
        return layout;
    }

    private Layout getLayout(byte[] hash) {
        String encoded = BaseEncoding.base16().encode(hash);
        return layoutsByHash.get(encoded);
    }

    static class Transaction implements Journal.Transaction {
        @Getter
        final TransactionStore.Transaction tx;

        private final TransactionMap txEventHashes;
        private final TransactionMap txHashEvents;
        private final TransactionMap txEventPayloads;

        Transaction(TransactionStore.Transaction tx) {
            this.tx = tx;
            txEventPayloads = tx.openMap("eventPayloads", new ObjectDataType(), new ByteBufferDataType());
            txHashEvents = tx.openMap("hashEvents");
            txEventHashes = tx.openMap("eventHashes");
        }

        @Override public void commit() {
            tx.prepare();
            tx.commit();
        }

        @Override public void rollback() {
            tx.rollback();
        }
    }

    @Override public Transaction beginTransaction() {
        return new Transaction(transactionStore.begin());
    }

    @Override public  Command journal(Journal.Transaction tx, Command command) {
        TransactionStore.Transaction tx0 = ((Transaction) tx).getTx();
        TransactionMap txCommandPayloads = tx0.openMap("commandPayloads", new ObjectDataType(),
                                                                        new ByteBufferDataType());
        TransactionMap txHashCommands = tx0.openMap("hashCommands");
        TransactionMap txCommandHashes = tx0.openMap("commandHashes");

        Layout commandLayout = getLayout(command.getClass());

        ByteBuffer hashBuffer = ByteBuffer.allocate(16 + 20); // based on SHA-1
        hashBuffer.put(commandLayout.getHash());
        hashBuffer.putLong(command.uuid().getMostSignificantBits());
        hashBuffer.putLong(command.uuid().getLeastSignificantBits());

        ByteBuffer buffer = serialization.getSerializer(command.getClass()).serialize(command);
        buffer.rewind();

        txCommandPayloads.tryPut(command.uuid(), ByteBuffer.wrap(buffer.array()));

        txHashCommands.tryPut(hashBuffer.array(), true);
        txCommandHashes.tryPut(command.uuid(), commandLayout.getHash());
        buffer.rewind();
        Command command1 = (Command) serialization.getDeserializer(command.getClass()).deserialize(buffer);
        command1.uuid(command.uuid());
        return command1;
    }

    @SneakyThrows
    @Override public Event journal(Journal.Transaction tx, Event event) {
        Transaction tx0 = ((Transaction) tx);
        Layout layout = getLayout(event.getClass());

        ObjectSerializer serializer = serialization.getSerializer(event.getClass());
        int size = serializer.size(event);

        ByteBuffer payloadBuffer = ByteBuffer.allocate(size);
        serializer.serialize(event, payloadBuffer);
        payloadBuffer.rewind();

        tx0.txEventPayloads.tryPut(event.uuid(), ByteBuffer.wrap(payloadBuffer.array()));

        ByteBuffer hashBuffer = ByteBuffer.allocate(20 + 16); // Based on SHA-1

        hashBuffer.rewind();
        hashBuffer.put(layout.getHash());
        hashBuffer.putLong(event.uuid().getMostSignificantBits());
        hashBuffer.putLong(event.uuid().getLeastSignificantBits());


        tx0.txHashEvents.tryPut(hashBuffer.array(), true);
        tx0.txEventHashes.tryPut(event.uuid(), layout.getHash());
        payloadBuffer.rewind();
        Event event1 = (Event) serialization.getDeserializer(event.getClass()).deserialize(payloadBuffer);
        event1.uuid(event.uuid());
        return event1;
    }



    @Override
    @SneakyThrows @SuppressWarnings("unchecked")
    public  Optional get(UUID uuid) {
        if (commandPayloads.containsKey(uuid)) {
            ByteBuffer payload = commandPayloads.get(uuid);
            payload.rewind();
            assert payload.array().length > 0;
            byte[] bytes = commandHashes.get(uuid);
            Layout> layout = getLayout(bytes);
            Command command = (Command) serialization.getDeserializer(layout.getLayoutClass()).deserialize(payload);
            command.uuid(uuid);
            return Optional.of((T) command);
        }
        if (eventPayloads.containsKey(uuid)) {
            ByteBuffer payload = eventPayloads.get(uuid);
            payload.rewind();
            assert payload.array().length > 0;
            byte[] bytes = eventHashes.get(uuid);
            Layout layout = getLayout(bytes);
            Event event = (Event) serialization.getDeserializer(layout.getLayoutClass()).deserialize(payload);
            event.uuid(uuid);
            return Optional.of((T) event);
        }
        return Optional.empty();

    }

    @Override
    public > CloseableIterator> commandIterator(Class klass) {
        return entityIterator(klass, hashCommands);
    }

    @Override
    public  CloseableIterator> eventIterator(Class klass) {
        return entityIterator(klass, hashEvents);
    }

    @SneakyThrows
    private  CloseableIterator> entityIterator(Class klass,  TransactionMap map) {
        Layout layout = getLayout(klass);
        if (layout == null) {
            layout = Layout.forClass(klass);
            layoutsByClass.put(klass.getName(), layout);
        }
        byte[] hash = layout.getHash();
        Iterator> iterator = map.entryIterator(map.higherKey(hash));
        return new EntityHandleIterator<>(iterator, bytes -> Bytes.indexOf(bytes, hash) == 0,
                                          new EntityFunction<>(hash));
    }

    @Override
    public void clear() {
        commandPayloads.clear();
        hashCommands.clear();
        eventPayloads.clear();
        eventHashes.clear();
        hashEvents.clear();
    }

    @Override @SuppressWarnings("unchecked")
    public  long size(Class klass) {
        if (Event.class.isAssignableFrom(klass)) {
            return Iterators.size(eventIterator((Class) klass));
        }
        if (Command.class.isAssignableFrom(klass)) {
            return Iterators.size(commandIterator((Class>) klass));
        }
        throw new IllegalArgumentException();
    }

    @Override
    public  boolean isEmpty(Class klass) {
        if (Event.class.isAssignableFrom(klass)) {
            return !eventIterator((Class) klass).hasNext();
        }
        if (Command.class.isAssignableFrom(klass)) {
            return !commandIterator((Class>) klass).hasNext();
        }
        throw new IllegalArgumentException();
    }


    private static class LayoutFunction implements Function {
        private final Class klass;

        public LayoutFunction(Class klass) {this.klass = klass;}

        @SneakyThrows
        @Override public Layout apply(X n) {return Layout.forClass(klass);}
    }


    private class EntityLayoutExtractor implements Consumer> {

        @Override
        @SneakyThrows
        public void accept(Class aClass) {
            Layout layout = Layout.forClass(aClass);
            byte[] hash = layout.getHash();
            String encodedHash = BaseEncoding.base16().encode(hash);
            layoutsByHash.put(encodedHash, layout);
            layoutsByClass.put(aClass.getName(), layout);
        }

    }

    static private class EntityHandleIterator implements CloseableIterator {

        private final Iterator> iterator;
        private Function hasNext;
        private final BiFunction function;
        private Map.Entry entry;

        public EntityHandleIterator(Iterator> iterator, Function hasNext,
                                    BiFunction function) {
            this.iterator = iterator;
            this.hasNext = hasNext;
            this.function = function;
        }

        @Override
        public boolean hasNext() {
            if (iterator.hasNext()) {
                entry = iterator.next();
                return hasNext.apply(entry.getKey());
            } else {
                return false;
            }
        }

        @Override
        public R next() {
            if (entry == null) {
                entry = iterator.next();
            }
            R result = function.apply(entry.getKey(), entry.getValue());
            entry = null;
            return result;
        }

        @Override
        public void close() {

        }
    }

    private class EntityFunction implements BiFunction> {
        private final byte[] hash;

        public EntityFunction(byte[] hash) {
            this.hash = hash;
        }

        @Override
        public EntityHandle apply(byte[] bytes, V value) {
            ByteBuffer buffer = ByteBuffer.wrap(bytes);
            UUID uuid = new UUID(buffer.getLong(hash.length), buffer.getLong(hash.length + 8));
            return new JournalEntityHandle<>(MVStoreJournal.this, uuid);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy