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

com.tangosol.persistence.ldb.LevelDBManager Maven / Gradle / Ivy

There is a newer version: 24.03
Show newest version
package com.tangosol.persistence.ldb;

import com.oracle.coherence.persistence.FatalAccessException;
import com.oracle.coherence.persistence.PersistenceException;
import com.oracle.coherence.persistence.PersistentStore;

import com.oracle.datagrid.persistence.OfflinePersistenceInfo;
import com.oracle.datagrid.persistence.PersistenceTools;

import com.tangosol.io.ByteArrayReadBuffer;
import com.tangosol.io.FileHelper;
import com.tangosol.io.ReadBuffer;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.GuardSupport;

import com.tangosol.persistence.AbstractPersistenceManager;

import com.tangosol.util.Binary;
import com.tangosol.util.BitHelper;
import com.tangosol.util.Unsafe;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;

import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.fusesource.leveldbjni.JniDBFactory;

import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBException;
import org.iq80.leveldb.DBIterator;
import org.iq80.leveldb.Options;
import org.iq80.leveldb.WriteBatch;

/**
 * PersistenceManager implementation that uses LevelDB.
 *
 * @author jh  2012.10.04
 */
public class LevelDBManager
        extends AbstractPersistenceManager
    {

    // ----- constructors ---------------------------------------------------

    /**
     * Create a new LevelDBManager.
     *
     * @param fileData   the directory containing the LevelDB databases
     *                   managed by this LevelDBManager
     * @param fileTrash  an optional trash directory
     * @param sName      an optional name to give the new manager
     *
     * @throws IOException on error creating the data or trash directory
     */
    public LevelDBManager(File fileData, File fileTrash, String sName)
            throws IOException
        {
        super(fileData, fileTrash, sName);
        }

    // ----- AbstractPersistenceManager methods -----------------------------

    /**
     * {@inheritDoc}
     */
    @Override
    protected int getImplVersion()
        {
        return 0;
        }

    /**
     * {@inheritDoc}
     */
    @Override
    protected String getStorageFormat()
        {
        return "LDB";
        }

    /**
     * {@inheritDoc}
     */
    @Override
    protected int getStorageVersion()
        {
        return 0;
        }

    // ----- helper methods -------------------------------------------------

    /**
     * Create a snapshot of this manager.
     *
     * @param fileSnapshot  the directory of the snapshot
     *
     * @throws PersistenceException if a general persistence error occurs
     */
    protected void createSnapshot(final File fileSnapshot)
        {
        // create a snapshot of the specified manager
        executeTaskExclusive(new Task()
            {
            @Override
            public void execute()
                {
                Map map = getPersistentStoreMap();
                for (LevelDBStore store : map.values())
                    {
                    GuardSupport.heartbeat();

                    File fileDirFrom = store.getDataDirectory();
                    File fileDirTo   = new File(fileSnapshot, fileDirFrom.getName());
                    try
                        {
                        FileHelper.copyDir(fileDirFrom, fileDirTo);
                        }
                    catch (IOException e)
                        {
                        throw ensurePersistenceException(e, "error creating snapshot \""
                                + fileSnapshot + "\" while copying persistent store \""
                                + fileDirFrom + '"');
                        }
                    }
                }
            });
        }

    /**
     * {@inheritDoc}
     */
    @Override
    protected PersistenceTools instantiatePersistenceTools(OfflinePersistenceInfo info)
        {
        return new AbstractPersistenceSnapshotTools(getDataDirectory(), info)
            {
            // ----- PersistenceTools methods -------------------------------

            @Override
            public void validate()
                {
                // currently there is no way to "validate" a LevelDB environment like
                // we can do with BDB, so getting the statistics is the next best thing.
                // A issue (COH-12673) has been created to track this
                getStatistics();
                }
            };
        }

    // ----- inner class: LevelDBStore --------------------------------------

    /**
     * Factory method for LevelDBStore implementations managed by this
     * LevelDBManager.
     *
     * @param sId  the identifier of the store to create
     *
     * @return a new LevelDBStore with the given identifier
     */
    @Override
    protected LevelDBStore instantiatePersistentStore(String sId)
        {
        return new LevelDBStore(sId);
        }

    /**
     * PersistentStore implementation that uses LevelDB.
     *
     * @author jh  2012.10.04
     */
    protected class LevelDBStore
            extends AbstractPersistenceManager.AbstractPersistentStore
        {
        // ----- constructors -----------------------------------------------

        /**
         * Create a new LevelDBStore that is backed by a LevelDB database.
         *
         * @param sId  the identifier for this store
         */
        protected LevelDBStore(String sId)
            {
            super(sId);
            }

        // ----- AbstractPersistentStore methods ----------------------------

        /**
         * {@inheritDoc}
         */
        @Override
        protected void validateExtentId(long lExtentId)
            {
            if (lExtentId == METADATA_EXTENT)
                {
                throw new IllegalArgumentException("reserved extent identifier");
                }
            super.validateExtentId(lExtentId);
            }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void openInternal()
            {
            // open the database
            if (m_db == null)
                {
                Options options = new Options();
                options.createIfMissing(true);
                options.cacheSize(0);

                try
                    {
                    // open the database
                    m_db = JniDBFactory.factory.open(f_dirStore, options);
                    }
                catch (IOException e)
                    {
                    throw ensurePersistenceException(
                            new FatalAccessException(
                                    "error opening the LevelDB database in directory \""
                                            + f_dirStore + '"', e));
                    }
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void releaseInternal()
            {
            // close the database
            DB db = m_db;
            if (db != null)
                {
                try
                    {
                    db.close();
                    }
                catch (Throwable e)
                    {
                    // ignore
                    }
                m_db = null;
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        protected boolean deleteInternal()
            {
            try
                {
                JniDBFactory.factory.destroy(f_dirStore, new Options());
                return true;
                }
            catch (IOException e)
                {
                return false;
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void loadExtentIdsInternal(Set set)
            {
            ReadBuffer buf = loadInternal(METADATA_EXTENT, EXTENT_IDS_KEY);

            byte[] ab = buf == null ? null : getByteArrayUnsafe(buf);
            if (ab != null && ab.length > 0)
                {
                // read the list of longs
                for (int i = 0, c = BitHelper.toInt(ab, 0), of = 4; i < c; ++i, of += 8)
                    {
                    set.add(Long.valueOf(BitHelper.toLong(ab, of)));
                    }
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void createExtentInternal(long lExtentId)
            {
            saveExtentIds(null /*oToken*/);
            }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void deleteExtentInternal(long lExtentId)
            {
            DB db = m_db;
            if (db != null)
                {
                WriteBatch batch  = (WriteBatch) begin();
                boolean    fAbort = true;
                DBIterator iter   = db.iterator();
                try
                    {
                    iter.seekToFirst();
                    while (iter.hasNext())
                        {
                        Map.Entry entry = iter.next();

                        // extract the extent identifier
                        byte[] abKey = entry.getKey();
                        long   lId   = extractExtentId(abKey);

                        // delete the entry if necessary
                        if (lExtentId == lId)
                            {
                            batch.delete(abKey);
                            }
                        }

                    // update the set of known extent identifiers
                    saveExtentIds(batch);
                    commit(batch);
                    }
                finally
                    {
                    try
                        {
                        iter.close();
                        }
                    catch (IOException e)
                        {
                        // ignore
                        }
                    if (fAbort)
                        {
                        abort(batch);
                        }
                    }
                }
            }


        /**
         * {@inheritDoc}
         */
        @Override
        protected void truncateExtentInternal(long lExtentId)
            {
            deleteExtentInternal(lExtentId);
            }

        /**
         * {@inheritDoc}
         */
        @Override
        protected ReadBuffer loadInternal(long lExtentId, ReadBuffer bufKey)
            {
            try
                {
                byte[] ab = ensureDatabase().get(createKey(lExtentId, bufKey));
                return ab == null ? null : UNSAFE.newBinary(ab, 0, ab.length);
                }
            catch (DBException e)
                {
                throw ensurePersistenceException(e);
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void storeInternal(long lExtentId, ReadBuffer bufKey, ReadBuffer bufValue, Object oToken)
            {
            try
                {
                ensureWriteBatch(oToken).put(createKey(lExtentId, bufKey),
                        getByteArrayUnsafe(bufValue));
                }
            catch (DBException e)
                {
                // WriteBatch#put isn't documented as throwing DBException,
                // but handle it just in case...
                throw ensurePersistenceException(e);
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        protected void eraseInternal(long lExtentId, ReadBuffer bufKey, Object oToken)
            {
            try
                {
                ensureWriteBatch(oToken).delete(createKey(lExtentId, bufKey));
                }
            catch (DBException e)
                {
                // WriteBatch#delete isn't documented as throwing DBException,
                // but handle it just in case...
                throw ensurePersistenceException(e);
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        public void iterateInternal(Visitor visitor)
            {
            DB db = ensureDatabase();
            try
                {
                try (DBIterator iter = db.iterator())
                    {
                    iter.seekToFirst();
                    while (iter.hasNext())
                        {
                        Map.Entry entry = iter.next();

                        // extract the extent identifier
                        byte[] abKey = entry.getKey();
                        long lId = extractExtentId(abKey);
                        Long LId = Long.valueOf(lId);

                        // visit the entry iff the extent identifier is known
                        if (f_setExtentIds.contains(LId))
                            {
                            byte[] abValue = entry.getValue();
                            ReadBuffer bufKey = extractKeyContent(abKey);
                            ReadBuffer bufValue = UNSAFE.newBinary(abValue, 0, abValue.length);
                            if (!visitor.visit(lId, bufKey, bufValue))
                                {
                                return;
                                }
                            }
                        }
                    }
                }
            catch (Throwable e)
                {
                // DBException or Throwable from Visitor#vist call
                throw ensurePersistenceException(e);
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        public Object beginInternal()
            {
            try
                {
                return ensureDatabase().createWriteBatch();
                }
            catch (DBException e)
                {
                // DB#createWriteBatch isn't documented as throwing DBException,
                // but handle it just in case...
                throw ensurePersistenceException(e);
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        public void commitInternal(Object oToken)
            {
            try
                {
                WriteBatch batch = ensureWriteBatch(oToken);
                try
                    {
                    ensureDatabase().write(batch);
                    }
                finally
                    {
                    try
                        {
                        batch.close();
                        }
                    catch (IOException e)
                        {
                        // ignore
                        }
                    }
                }
            catch (DBException e)
                {
                throw ensurePersistenceException(e);
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        public void abortInternal(Object oToken)
            {
            try
                {
                ensureWriteBatch(oToken).close();
                }
            catch (IOException e)
                {
                throw ensurePersistenceException(e);
                }
            }

        // ----- helpers ----------------------------------------------------

        /**
         * Return the content of the given buffer as a byte array, using an
         * unsafe method if the given buffer is a Binary.
         *
         * @param buf  the buffer
         *
         * @return the contents of the given buffer as a byte array
         */
        private byte[] getByteArrayUnsafe(ReadBuffer buf)
            {
            if (buf instanceof Binary)
                {
                Binary bin = (Binary) buf;
                if (UNSAFE.getArrayOffset(bin) == 0)
                    {
                    byte[] ab = UNSAFE.getByteArray(bin);
                    if (ab.length == bin.length())
                        {
                        return ab;
                        }
                    }
                }
            else if (buf instanceof ByteArrayReadBuffer)
                {
                ByteArrayReadBuffer babuf = (ByteArrayReadBuffer) buf;
                if (babuf.getRawOffset() == 0)
                    {
                    byte[] ab = babuf.getRawByteArray();
                    if (ab.length == babuf.length())
                        {
                        return ab;
                        }
                    }
                }
            return buf.toByteArray();
            }

        /**
         * Ensure that the underlying LevelDB database has been opened before
         * returning it.
         *
         * @return the opened LevelDB database
         */
        protected DB ensureDatabase()
            {
            DB db = m_db;
            if (db == null)
                {
                throw new IllegalStateException("the LevelDB database \""
                        + f_dirStore + "\" is not open");
                }
            return db;
            }

        /**
         * Ensure that the given token is a WriteBatch.
         *
         * @param oToken  the token
         *
         * @return the token cast to a WriteBatch
         */
        protected WriteBatch ensureWriteBatch(Object oToken)
            {
            if (oToken instanceof WriteBatch)
                {
                return (WriteBatch) oToken;
                }
            throw new IllegalArgumentException("illegal token: " + oToken);
            }

        /**
         * Create and return a byte array that can be used as a key to
         * represent the given extent identifier and key content in the
         * underlying store.
         *
         * @param lExtentId  the extent identifier for the key
         * @param bufKey     the key content
         *
         * @return a new key
         */
        protected byte[] createKey(long lExtentId, ReadBuffer bufKey)
            {
            assert bufKey != null;

            int    cb = bufKey.length();
            byte[] ab = new byte[cb + 8];

            // extent identifier
            BitHelper.toBytes(lExtentId, ab, 0);

            // copy key content
            bufKey.copyBytes(0, cb, ab, 8);

            return ab;
            }

        /**
         * Extract and return the extent identifier associated with the
         * specified {@link #createKey key}.
         *
         * @param abKey  the key
         *
         * @return the extent identifier for the given key
         */
        protected long extractExtentId(byte[] abKey)
            {
            assert abKey != null && abKey.length >= 8;
            return BitHelper.toLong(abKey);
            }

        /**
         * Extract and return the key content associated with the specified
         * {@link #createKey key}.
         *
         * @param abKey  the key
         *
         * @return the key content for the key
         */
        protected ReadBuffer extractKeyContent(byte[] abKey)
            {
            assert abKey != null && abKey.length >= 8;
            return UNSAFE.newBinary(abKey, 8, abKey.length - 8);
            }

        /**
         * Save the set of known extent identifiers to the persistent store.
         *
         * @param oToken  optional token that represents a set of mutating
         *                operations to be committed as an atomic unit; if
         *                null, the update will be committed to the store
         *                automatically by this method
         * 

* Note: this method is guaranteed to only be called by a thread that * holds a write lock on this persistent store. */ protected void saveExtentIds(Object oToken) { int c = f_setExtentIds.size(); byte[] ab = new byte[4 + c * 8]; // 4 bytes for the count, 8 bytes per long // write the list of longs BitHelper.toBytes(c, ab, 0); int of = 4; for (Long LId : f_setExtentIds) { BitHelper.toBytes(LId.longValue(), ab, of); of += 8; } // perform the store either by adding the operation to the // supplied batch or to a newly created batch if one hasn't // been supplied boolean fAbort = oToken == null; boolean fCommit = fAbort; if (fCommit) { oToken = begin(); } try { storeInternal(METADATA_EXTENT, EXTENT_IDS_KEY, UNSAFE.newBinary(ab, 0, ab.length), oToken); // commit the change, if necessary if (fCommit) { commit(oToken); fAbort = false; } } finally { // abort the change, if necessary if (fAbort) { abort(oToken); } } } // ----- data members ----------------------------------------------- /** * The underlying LevelDB database. */ protected DB m_db; } // ----- constants ------------------------------------------------------ /** * The extent identifier used to store metadata. */ public static final long METADATA_EXTENT = Long.MIN_VALUE; /** * The key of the LevelDB entry that stores the set of valid extent * identifiers. */ protected static final ReadBuffer EXTENT_IDS_KEY = new Binary(); /** * Unsafe singleton. */ private static final Unsafe UNSAFE = Unsafe.getUnsafe(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy