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

com.devsmart.microdb.MicroDB Maven / Gradle / Ivy

There is a newer version: 0.3.17
Show newest version
package com.devsmart.microdb;


import com.devsmart.ubjson.UBObject;
import com.devsmart.ubjson.UBValue;
import com.devsmart.ubjson.UBValueFactory;
import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

public class MicroDB {

    private static final Logger logger = LoggerFactory.getLogger(MicroDB.class);

    private Driver mDriver;
    private int mSchemaVersion;
    private DBCallback mCallback;
    private final HashMap> mLiveObjects = new HashMap>();
    private final WriteQueue mWriteQueue = new WriteQueue();
    private ArrayList mChangeListeners = new ArrayList();
    private Map mConstructorMap;

    @Override
    protected void finalize() throws Throwable {
        mWriteQueue.enqueue(createShutdownOperation());
        super.finalize();
    }

    public void shutdown() {
        mWriteQueue.enqueue(createShutdownOperation());
        try {
            mWriteQueue.mWriteThread.join();
        } catch (InterruptedException e) {
            logger.warn("", e);
        }
    }

    public enum OperationType {
        Write,
        NoOp,
        Shutdown
    }

    abstract static class Operation implements Runnable {
        public final OperationType mCommandType;
        private Exception mException;
        private boolean mCompleted = false;

        Operation(OperationType type) {
            mCommandType = type;
        }

        synchronized void complete() {
            mCompleted = true;
            notifyAll();
        }

        public synchronized void waitForCompletion() {
            while (!mCompleted) {
                try {
                    wait(1000);
                } catch (InterruptedException e) {
                    logger.warn("", e);
                }
            }
            if (mException != null) {
                Throwables.propagate(mException);
            }
        }

        abstract void doIt() throws IOException;

        @Override
        public void run() {
            try {
                doIt();
            } catch (Exception e) {
                logger.error("uncaught exception while performing write operation", e);
                mException = e;
            }
        }
    }

    private class WriteQueue implements Runnable {
        private static final long DEFAULT_WAIT = 2000;
        private final Queue mOperationQueue = new ConcurrentLinkedQueue();
        private Thread mWriteThread = new Thread(this, "MicroDB Write Thread");

        @Override
        public void run() {
            while (true) {
                Operation op = mOperationQueue.poll();
                if (op == null) {
                    waitForNextCommand();
                } else {
                    try {
                        switch (op.mCommandType) {
                            case Write:
                                op.run();
                                break;

                            case NoOp:
                                break;

                            case Shutdown:
                                logger.info("Write Thread exiting");
                                return;
                        }
                    } finally {
                        op.complete();
                    }
                }
            }


        }

        private synchronized void waitForNextCommand() {
            try {
                wait(DEFAULT_WAIT);
            } catch (InterruptedException e) {
                logger.warn("unexpected interrupt", e);
            }
        }


        public void start() {
            //mWriteThread.setDaemon(true);
            mWriteThread.start();
        }

        public void enqueue(Operation op) {
            mOperationQueue.offer(op);
        }

        public synchronized void kick() {
            notify();
        }
    }


    void enqueueOperation(Operation op) {
        mWriteQueue.enqueue(op);
    }

    private Operation createShutdownOperation() {
        return new Operation(OperationType.Shutdown) {
            @Override
            void doIt() throws IOException {
            }
        };
    }

    private Operation createNoOp() {
        return new Operation(OperationType.NoOp) {
            @Override
            void doIt() throws IOException {
            }
        };
    }

    private Operation createInsertOperation(final DBObject obj) {
        return new Operation(OperationType.Write) {
            @Override
            void doIt() throws IOException {
                final UUID id;
                UBObject data = UBValueFactory.createObject();
                synchronized (obj) {
                    id = obj.getId();
                    obj.beforeWrite();
                    obj.writeToUBObject(data);
                    obj.mDirty = false;
                }

                mDriver.insert(id, data);

                for (ChangeListener listener : mChangeListeners) {
                    listener.onAfterInsert(mDriver, id, data);
                }
            }
        };
    }

    private Operation createWriteObject(final DBObject obj) {
        return new Operation(OperationType.Write) {
            @Override
            void doIt() throws IOException {
                final UUID id;
                UBObject data = UBValueFactory.createObject();
                synchronized (obj) {
                    id = obj.getId();
                    obj.beforeWrite();
                    obj.writeToUBObject(data);
                    obj.mDirty = false;
                }

                for (ChangeListener listener : mChangeListeners) {
                    listener.onBeforeUpdate(mDriver, id, data);
                }

                mDriver.update(id, data);
            }
        };
    }

    private Operation createSaveOperation(final UUID id, final UBValue data) {
        return new Operation(OperationType.Write) {
            @Override
            void doIt() throws IOException {
                mDriver.update(id, data);
            }
        };
    }

    private Operation createDeleteOperation(final DBObject obj) {
        return new Operation(OperationType.Write) {
            @Override
            void doIt() throws IOException {
                final UUID id;
                synchronized (obj) {
                    id = obj.getId();
                    obj.mDirty = false;
                }

                for (ChangeListener listener : mChangeListeners) {
                    listener.onBeforeDelete(mDriver, id);
                }
                mDriver.delete(id);
            }
        };
    }

    private Operation createCommitOperation() {
        return new Operation(OperationType.Write) {
            @Override
            void doIt() throws IOException {
                mDriver.commitTransaction();
            }
        };
    }

    private Operation createCompactOperation() {
        return new Operation(OperationType.Write) {
            @Override
            void doIt() throws IOException {
                mDriver.compact();
            }
        };
    }

    private AtomicBoolean mAutoSave = new AtomicBoolean(true);

    static final MapFunction INDEX_OBJECT_TYPE = new MapFunction() {
        @Override
        public void map(UBValue value, Emitter emitter) {
            if (value != null && value.isObject()) {
                UBObject obj = value.asObject();
                UBValue typevar = obj.get("type");
                if (typevar != null && typevar.isString()) {
                    emitter.emit(typevar.asString());
                }
            }
        }
    };

    public Driver getDriver() {
        return mDriver;
    }


    MicroDB(Driver driver, int schemaVersion, DBCallback cb, Map constructorMap) throws IOException {
        mDriver = driver;
        mSchemaVersion = schemaVersion;
        mCallback = cb;
        mConstructorMap = constructorMap;

        mWriteQueue.start();
        init();
    }

    private static final String METAKEY_DBVERSION = "schema_version";
    private static final String METAKEY_INSTANCE = "instance";

    private void init() throws IOException {

        mDriver.addIndex("type", INDEX_OBJECT_TYPE);

        UBObject metaObj = mDriver.getMeta();
        if (!metaObj.containsKey(METAKEY_INSTANCE)) {
            mDriver.beginTransaction();
            metaObj.put(METAKEY_INSTANCE, UBValueFactory.createString(UUID.randomUUID().toString()));
            metaObj.put(METAKEY_DBVERSION, UBValueFactory.createInt(mSchemaVersion));
            mDriver.saveMeta(metaObj);
            mDriver.commitTransaction();

            mDriver.beginTransaction();
            mCallback.onUpgrade(this, -1, mSchemaVersion);
            mDriver.commitTransaction();

        } else {
            int currentVersion = metaObj.get(METAKEY_DBVERSION).asInt();
            if (currentVersion < mSchemaVersion) {
                mDriver.beginTransaction();
                mCallback.onUpgrade(this, currentVersion, mSchemaVersion);
                metaObj.put(METAKEY_DBVERSION, UBValueFactory.createInt(mSchemaVersion));
                mDriver.saveMeta(metaObj);
                mDriver.commitTransaction();
            }
        }

    }


    /**
     * called when a dbobject is being finalized by the GC
     *
     * @param obj
     */
    protected void finalizing(DBObject obj) {
        if (mAutoSave.get() && obj.mDirty) {
            mWriteQueue.enqueue(createWriteObject(obj));
        }
        synchronized (this) {
            mLiveObjects.remove(obj.getId());
        }

    }

    public synchronized void close() throws IOException {
        flush();
        mLiveObjects.clear();
        mDriver.close();
    }

    /**
     * Saves all DBObjects that are marked dirty
     */
    public void flush() {
        synchronized (this) {
            for (WeakReference ref : mLiveObjects.values()) {
                DBObject obj = ref.get();
                if (obj != null) {
                    synchronized (obj) {
                        if(obj.mDirty) {
                            mWriteQueue.enqueue(createWriteObject(obj));
                        }
                    }
                }
            }
        }
        sync();
    }

    /**
     * creates and inserts into the database a new object of type {@code classType}.
     *
     * @param classType
     * @param 
     * @return newly created object
     */
    public synchronized  T insert(Class classType) {
        try {
            T retval = create(classType);
            final UUID key = mDriver.genId();
            retval.setId(key);

            UBObject data = UBValueFactory.createObject();
            retval.writeToUBObject(data);

            for(ChangeListener l : mChangeListeners) {
                l.onBeforeInsert(mDriver, data);
            }

            retval.readFromUBObject(data);

            retval.setDirty();
            mWriteQueue.enqueue(createInsertOperation(retval));
            mLiveObjects.put(key, new WeakReference(retval));

            return retval;

        } catch (Exception e) {
            throw new RuntimeException("", e);
        }
    }

    /**
     * Constructs a new proxy instance of {@code classType}
     *
     * @param classType
     * @param 
     * @return a new object of type T
     */
    public  T create(Class classType) {
        try {
            T retval = classType.newInstance();
            retval.init(this);
            return retval;
        } catch (Exception e) {
            Throwables.propagate(e);
            return null;
        }
    }

    public interface Constructor {
        T build();
    }


    public synchronized  T get(UUID id) {
        try {

            T retval;
            DBObject cached;

            WeakReference ref = mLiveObjects.get(id);
            if (ref != null && (cached = ref.get()) != null) {
                retval = (T) cached;
            } else {

                UBValue data = mDriver.get(id);
                if (data == null) {
                    return null;
                } else {

                    if (!data.isObject()) {
                        throw new RuntimeException("database entry with id: " + id + " is not an object");
                    }

                    final String dataType = data.asObject().get("type").asString();
                    retval = (T) mConstructorMap.get(dataType).build();

                    retval.init(this);
                    retval.setId(id);
                    retval.readFromUBObject(data.asObject());
                    retval.afterRead();
                    mLiveObjects.put(id, new WeakReference(retval));
                }
            }

            return retval;
        } catch (Exception e) {
            throw new RuntimeException("", e);
        }

    }

    /**
     * fetch and load database object with primary key {@code id}.
     *
     * @param id
     * @param shell new object of type
     * @param 
     * @return dbobject
     */
    public synchronized  T get(UUID id, T shell) {
        try {

            T retval;
            DBObject cached;

            WeakReference ref = mLiveObjects.get(id);
            if (ref != null && (cached = ref.get()) != null) {
                retval = (T) cached;
            } else {

                UBValue data = mDriver.get(id);
                if (data == null) {
                    return null;
                } else {

                    if (!data.isObject()) {
                        throw new RuntimeException("database entry with id: " + id + " is not an object");
                    }

                    shell.init(this);
                    shell.setId(id);
                    shell.readFromUBObject(data.asObject());
                    shell.afterRead();
                    retval = shell;
                    mLiveObjects.put(id, new WeakReference(retval));
                }
            }

            return retval;
        } catch (Exception e) {
            throw new RuntimeException("", e);
        }
    }

    public UBValue get(String key) throws IOException {
        UUID id = UUID.nameUUIDFromBytes(key.getBytes(Charsets.UTF_8));
        return mDriver.get(id);
    }

    /**
     * saves/updates {@code obj} to the database. This method is not normally necessary for users to call
     * because database objects will automatically be saved when the garbage collector collects them if
     * they are marked dirty.
     *
     * @param obj the data to be saved
     */
    public Operation save(DBObject obj) {
        checkValid(obj);
        Operation op = createWriteObject(obj);
        mWriteQueue.enqueue(op);
        return op;
    }

    public Operation save(String key, UBValue data) {
        UUID id = UUID.nameUUIDFromBytes(key.getBytes(Charsets.UTF_8));
        Operation op = createSaveOperation(id, data);
        mWriteQueue.enqueue(op);
        return op;
    }

    private void checkValid(DBObject obj) {
        if (obj == null || obj.getDB() != this || obj.getId() == null) {
            throw new RuntimeException("DBObject is invalid. DBObjects must be created with MicroDB.insert() method");
        }
    }

    public synchronized Operation delete(DBObject obj) {
        checkValid(obj);
        Operation op = createDeleteOperation(obj);
        mWriteQueue.enqueue(op);
        mLiveObjects.remove(obj.getId());
        return op;
    }

    public Operation commit() {
        Operation op = createCommitOperation();
        mWriteQueue.enqueue(op);
        return op;
    }

    public void waitForCompletion(Operation op) {
        mWriteQueue.kick();
        op.waitForCompletion();
    }

    /**
     * This method blocks until all queued write operation are completed.
     */
    public void sync() {
        //Operation op = createNoOp();
        Operation op = createCommitOperation();
        mWriteQueue.enqueue(op);
        waitForCompletion(op);
    }

    public void compact() {
        Operation op = createCompactOperation();
        mWriteQueue.enqueue(op);
        waitForCompletion(op);
    }

    public > void addIndex(String indexName, MapFunction mapFunction) throws IOException {
        mDriver.addIndex(indexName, mapFunction);
    }

    public void addChangeListener(ChangeListener listener) {
        mChangeListeners.add(listener);
    }

    public > Cursor queryIndex(String indexName, T min, boolean minInclusive, T max, boolean maxInclusive) throws IOException {
        return mDriver.queryIndex(indexName, min, minInclusive, max, maxInclusive);
    }

    public  Iterable getAllOfType(final Class classType) throws IOException {
        final String className = classType.getSimpleName();

        return new Iterable() {
            @Override
            public Iterator iterator() {
                try {
                    final Cursor cursor = queryIndex("type", className, true, className, true);
                    return new RowIterator(cursor, MicroDB.this, classType);
                } catch (IOException e) {
                    Throwables.propagate(e);
                    return null;
                }
            }
        };

    }

    private static class RowIterator implements Iterator {

        private final MicroDB mDB;
        private final Class mClassType;
        private Cursor mCursor;
        private Row mCurrentRow;

        public RowIterator(Cursor cursor, MicroDB db, Class classType) {
            mCursor = cursor;
            mDB = db;
            mClassType = classType;
            mCurrentRow = mCursor.get();
        }

        @Override
        public boolean hasNext() {
            return mCurrentRow != null;
        }

        @Override
        public T next() {
            try {
                final UUID objId = mCurrentRow.getPrimaryKey();
                T retval = mDB.get(objId, mClassType.newInstance());

                mCursor.next();
                mCurrentRow = mCursor.get();
                return retval;
            } catch (Exception e) {
                Throwables.propagate(e);
                return null;
            }
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("remove not implemented");
        }
    }

    /*
    public > Iterable queryIndex(String indexName, final Class classType, K min, boolean minInclusive, K max, boolean maxInclusive) throws IOException {
        final Cursor rowCursor = queryIndex(indexName, min, minInclusive, max, maxInclusive);
        return Iterables.transform(rowCursor, new Function() {
            @Override
            public T apply(Row input) {
                try {
                    T shell = classType.newInstance();
                    return get(input.getPrimaryKey(), shell);
                } catch (Exception e) {
                    Throwables.propagate(e);
                    return null;
                }
            }
        });
    }


    */

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy