Package com.saasovation.common.port.adapter.persistence.eventsourcing.leveldb

Source Code of com.saasovation.common.port.adapter.persistence.eventsourcing.leveldb.LevelDBJournal

//   Copyright 2012,2013 Vaughn Vernon
//
//   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 com.saasovation.common.port.adapter.persistence.eventsourcing.leveldb;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;

import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBFactory;
import org.iq80.leveldb.DBIterator;
import org.iq80.leveldb.Options;
import org.iq80.leveldb.WriteBatch;
import org.iq80.leveldb.impl.Iq80DBFactory;

import com.saasovation.common.event.sourcing.EventStoreAppendException;
import com.saasovation.common.event.sourcing.EventStoreException;

public class LevelDBJournal {

    protected static final String ES_METADATA_DELIMITER = "#";

    private static final byte[] ES_JOURNAL_SEQUENCE_KEY =
            (JournalKeyProvider.ES_JOURNAL_PREFIX_KEY+"0").getBytes();

    private static LevelDBJournal instance;
    private static Map<String, Object> lock = new HashMap<String, Object>();

    private DB database;
    private String databasePath;
    private AtomicLong journalSequence;

    public static LevelDBJournal initializeInstance(String aDirectoryPath) {
        synchronized (lock) {
            if (instance == null) {
                instance = new LevelDBJournal(aDirectoryPath);
            } else {

                // for test
                if (instance.database() == null) {
                    instance.openDatabase(aDirectoryPath);
                }
            }
        }

        return instance;
    }

    public static LevelDBJournal instance() {
        if (instance == null) {
            throw new IllegalStateException("There is no LevelDBJournalProvider instance.");
        }

        return initializeInstance(instance.databasePath());
    }

    public void close() {
        synchronized (lock) {
            if (instance != null) {
                try {
                    this.saveJournalSequence();

                    this.database().close();

                } catch (Throwable t) {
                    throw new EventStoreException(
                            "Cannot clsoe LevelDB database: "
                                    + this.databasePath()
                                    + " because: "
                                    + t.getMessage(),
                            t);
                } finally {
                    instance = null;
                }
            }
        }
    }

    public String databasePath() {
        return this.databasePath;
    }

    public void logEntries(LoggableJournalEntry[] aJournalEntries) {

        WriteBatch batch = this.database().createWriteBatch();

        try {
            synchronized (this.lockFor(aJournalEntries[0].primaryResourceName())) {
                for (LoggableJournalEntry journalEntry : aJournalEntries) {

                    long journalSequence = this.nextJournalSequence();

                    this.confirmNonExistingReference(journalEntry.referenceKey());

                    String jounralKey =
                            JournalKeyProvider.ES_JOURNAL_PREFIX_KEY
                            + journalSequence;

                    String referenceKey =
                            journalEntry.referenceKey();

                    byte[] journalSequenceBytes = (""+journalSequence).getBytes();

                    String journalValue =
                            this.valueWithMetadata(
                                    journalEntry.value(),
                                    referenceKey);

                    // journal entry points to reference

                    batch.put(
                            jounralKey.getBytes(),
                            journalValue.getBytes());

                    // reference points to journal entry

                    batch.put(
                            referenceKey.getBytes(),
                            journalSequenceBytes);
                }

                this.database().write(batch);
            }

        } catch (Throwable t) {
            throw new EventStoreAppendException(
                    "Could not append to journal because: "
                            + t.getMessage(),
                    t);
        } finally {
            try {
                batch.close();
            } catch (Throwable t) {
                // ignore
            }
        }
    }

    public List<LoggedJournalEntry> loggedJournalEntriesSince(
            long aJournalSequence) {

        List<LoggedJournalEntry> entries = new ArrayList<LoggedJournalEntry>();

        boolean done = false;

        for (long journalSequence = aJournalSequence + 1; !done; ++journalSequence) {
            String journalKey =
                    JournalKeyProvider.ES_JOURNAL_PREFIX_KEY
                    + journalSequence;

            byte[] rawJournalValue =
                    this.database()
                        .get(journalKey.getBytes());

            if (rawJournalValue != null) {

                LoggedJournalEntry loggedJournalEntry =
                    new LoggedJournalEntry(
                            journalSequence,
                            null,
                            new String(rawJournalValue));

                // discard the reference key
                loggedJournalEntry.discardNextMetadataValue();

                entries.add(loggedJournalEntry);
            } else {
                done = true;
            }
        }

        return entries;
    }

    public void purge() {

        DBIterator iterator = this.database().iterator();

        try {
            iterator.seekToFirst();

            while (iterator.hasNext()) {
                Entry<byte[],byte[]> entry = iterator.next();

                this.database().delete(entry.getKey());
            }

        } catch (Throwable t) {
            throw new EventStoreException(
                    "Cannot purge journal LevelDB database: "
                        + this.databasePath()
                        + " because: "
                        + t.getMessage(),
                    t);
        } finally {
            try {
                iterator.close();
            } catch (Throwable t) {
                // ignore
            }

            this.setJournalSequence(0L);
        }
    }

    public List<LoggedJournalEntry> referencedLoggedJournalEntries(
            JournalKeyProvider aReferenceKeyProvider) {

        List<LoggedJournalEntry> entries = new ArrayList<LoggedJournalEntry>();

        boolean done = false;

        while (!done) {
            String referenceKey = aReferenceKeyProvider.nextReferenceKey();

            byte[] rawJournalSequenceValue =
                    this.database().get(referenceKey.getBytes());

            if (rawJournalSequenceValue != null) {
                long journalSequence =
                        Long.parseLong(new String(rawJournalSequenceValue));

                String journalKey =
                        JournalKeyProvider.ES_JOURNAL_PREFIX_KEY
                        + journalSequence;

                byte[] rawJournalValue =
                        this.database()
                            .get(journalKey.getBytes());

                LoggedJournalEntry loggedJournalEntry =
                    new LoggedJournalEntry(
                            journalSequence,
                            referenceKey,
                            new String(rawJournalValue));

                // discard the stream key
                loggedJournalEntry.discardNextMetadataValue();

                entries.add(loggedJournalEntry);

            } else {
                done = true;
            }
        }

        return entries;
    }

    public String valueWithMetadata(String aValue, String aMetadata) {
        String valueWithMetadata =
                aMetadata + ES_METADATA_DELIMITER + aValue;

        return valueWithMetadata;
    }

    private LevelDBJournal(String aDirectoryPath) {
        super();

        this.openDatabase(aDirectoryPath);
    }

    private boolean cacheJournalSequence() {
        boolean cached = false;

        byte[] journalSequenceValue =
                this.database().get(ES_JOURNAL_SEQUENCE_KEY);

        if (journalSequenceValue != null) {
            this.setJournalSequence(
                    Long.parseLong(new String(journalSequenceValue)));

            // only a successful close() will save the journal sequence.
            // a missing journal sequence on open indicates the need for
            // a repair (unless the database is empty).

            this.database().delete(ES_JOURNAL_SEQUENCE_KEY);

            cached = true;

        } else {
            this.setJournalSequence(0L);
        }

        return cached;
    }

    private void confirmNonExistingReference(String aReferenceKey) {
        // this implementation will not stand up to race conditions

        if (this.database().get(aReferenceKey.getBytes()) != null) {
            throw new EventStoreAppendException("Journal concurrency violation.");
        }
    }

    private DB database() {
        return this.database;
    }

    private void setDatabase(DB aDatabase) {
        this.database = aDatabase;
    }

    private void setDatabasePath(String aDatabasePath) {
        this.databasePath = aDatabasePath;
    }

    private Object lockFor(String aPrimaryResourceName) {
        // need a reaper to remove the lock after some
        // size threshold and LRU

        synchronized (lock) {
            Object resourceLock = lock.get(aPrimaryResourceName);

            if (resourceLock == null) {
                resourceLock = new Object();

                lock.put(aPrimaryResourceName, resourceLock);
            }

            return resourceLock;
        }
    }

    private long nextJournalSequence() {
        long nextJournalSequence = this.journalSequence.incrementAndGet();

        return nextJournalSequence;
    }

    private void setJournalSequence(long aJournalSequence) {
        this.journalSequence =
                new AtomicLong(aJournalSequence);
    }

    private void openDatabase(String aDirectoryPath) {

        try {
            this.setDatabasePath(aDirectoryPath);

            DBFactory factory = new Iq80DBFactory();

            Options options = new Options();

            options.createIfMissing(true);

            this.setDatabase(factory.open(new File(aDirectoryPath), options));

            if (!this.cacheJournalSequence()) {
                this.repair();
            }

        } catch (Throwable t) {
            throw new EventStoreException(
                    "Cannot open LevelDB database: "
                        + aDirectoryPath
                        + " because: "
                        + t.getMessage(),
                    t);
        }
    }

    private void repair() {
        LevelDBJournalRepairTool repairTool =
                new LevelDBJournalRepairTool(this.database());

        repairTool.repairDatabase();

        long lastConfirmedKey = repairTool.lastConfirmedSequence();

        if (lastConfirmedKey > 0) {
            this.setJournalSequence(lastConfirmedKey);
        }
    }

    private void saveJournalSequence() {

        byte[] journalSequenceBytes = (""+journalSequence.get()).getBytes();

        this.database().put(
                ES_JOURNAL_SEQUENCE_KEY,
                journalSequenceBytes);
    }
}
TOP

Related Classes of com.saasovation.common.port.adapter.persistence.eventsourcing.leveldb.LevelDBJournal

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.