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

com.cloudhopper.datastore.leveldb.LevelDbDataStore Maven / Gradle / Ivy

The newest version!
package com.cloudhopper.datastore.leveldb;

/*
 * #%L
 * ch-datastore
 * %%
 * Copyright (C) 2012 Cloudhopper by Twitter
 * %%
 * 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.
 * #L%
 */

import com.cloudhopper.commons.util.HexUtil;
import com.cloudhopper.commons.util.StringUtil;
import com.cloudhopper.datastore.*;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Timer;
import java.util.TimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.iq80.leveldb.CompressionType;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBException;
import org.iq80.leveldb.DBFactory;
import org.iq80.leveldb.DBIterator;
import org.iq80.leveldb.Options;
import org.iq80.leveldb.ReadOptions;
import org.iq80.leveldb.WriteOptions;
import static org.fusesource.leveldbjni.JniDBFactory.*;

/**
 * LevelDb backed implementation of a DataStore.
 * 
 * @author garth
 */
public class LevelDbDataStore extends BaseDataStore implements AscendingIteratorSupport {
    private final static Logger logger = LoggerFactory.getLogger(LevelDbDataStore.class);

    // path used to generate a full path to the database file
    private File path;
    // the underlying db
    private DB db;

    // for performance, the database is not synchronized on every put() and take()
    // if -1, sync() is disabled
    // if 0, sync() is called on ever put() and take()
    // if > 0, the amount of ms between a sync() call is made, set to 5 seconds by default
    private long synchronizeInterval;
    private Timer synchronizeTimer;
    private TimerTask synchronizeTimerTask;

    public LevelDbDataStore() {
        super("LDB-JNI");
        // defaults
        synchronizeInterval = 5000;
    }

    /**
     * Returns the underlying database.  If not opened, this will return
     * null.
     * @return Null if not open, otherwise the B-Tree database.
     */
    public DB getDatabase() {
        return db;
    }

    /**
     * Gets the interval between sync() calls to underlying LevelDb cabinet db.
     * For performance, by default, the database is not synchronized on every
     * put() and take().  If -1, sync() is disabled. If 0, sync() is called on
     * every put() and take().  Throughput will be dramatically decreased, but
     * it'll guarantee the data makes it into the file.  If > 0, its the length
     * of ms between a sync() call is made, set to 5 seconds by default
     * @return The synchronization interval in milliseconds
     */
    public long getSynchronizeInterval() {
        return synchronizeInterval;
    }

    /**
     * 
     * @throws QueueStoreException Thrown if the data store is already open
     *      when this method is called.
     */
    public void setSynchronizeInterval(long value) {
        this.synchronizeInterval = value;
    }

    private File getDatabaseFile() {
        return new File(getDirectory(), getName() + ".ldb");
    }

    @Override
    public void doDelete() throws DataStoreFatalException {
        File databaseFile = getDatabaseFile();
        try {
	    factory.destroy(databaseFile, new Options());
            databaseFile.delete();
        } catch (Exception e) {
            throw new DataStoreFatalException("Unable to delete " + databaseFile.getPath(), e);
        }
    }

    @Override
    public void doOpen() throws DataStoreFatalException {
	path = getDatabaseFile();
	Options options = new Options();
	options.createIfMissing(true);
	options.logger(new org.iq80.leveldb.Logger() {
		public void log (String message) {
		    logger.trace("leveldb: {}", message);
		}
	    });
	try {
	    db = factory.open(path, options);
	} catch (IOException e) {
	    throw new DataStoreFatalException("Unable to open " + toString() + " @ " + path + " {message=" + e.getMessage() + "}", e);
	}

        if (this.synchronizeInterval < 0) {
            logger.debug("Automatic sync disabled {synchronizeInterval < 0} for " + toString());
        } else if (this.synchronizeInterval == 0) {
            logger.debug("Automatic sync enabled for every transaction {synchronizeInterval == 0} for " + toString());
        } else {
            // schedule task to print results every 5 seconds
            synchronizeTimer = new Timer("LevelDbDataStoreSync-" + getName(), true);
            synchronizeTimerTask = new TimerTask() {
                @Override
                public void run() {
                    try {
                        if (db != null) {
                            logger.trace("Synchronized data store [name=" + getName() + "]");
			    // Implement this in LevelDB by just writing a special key
			    byte[] syncKey = new byte[] { 0x00 };
			    byte[] syncValue = new byte[8];
			    ByteBuffer.wrap(syncValue).putLong(System.currentTimeMillis());
			    db.put(syncKey, syncValue, new WriteOptions().sync(true));
                        } else {
                            // cancel this task!
                            logger.warn("Cancelling synchronizeTimerTask since db was null, this may be normal if data store shutting down");
                            this.cancel();
                        }
                    } catch (Exception e) {
                        logger.error("", e);
                    }
                }
            };

            synchronizeTimer.schedule(synchronizeTimerTask, synchronizeInterval, synchronizeInterval);
            logger.debug("Automatic sync enabled every " + synchronizeInterval + " ms for " + toString());
        }
    }

    @Override
    public void doClose() throws DataStoreFatalException {
        try {
            // make sure sync thread is cancelled
            if (synchronizeTimerTask != null) {
                try {
                    synchronizeTimerTask.cancel();
                } catch (Exception e) {
                    // ignore this
                }
                synchronizeTimerTask = null;
            }

            if (synchronizeTimer != null) {
                try {
                    synchronizeTimer.cancel();
                } catch (Exception e) {
                    // ignore this
                }
                synchronizeTimer = null;
            }

            if (db != null) {
		try {
		    db.close();
                } catch (Exception e) {
                    throw new DataStoreFatalException("Unable to properly close DataStore {message=" + e.getMessage() + "}", e);
		}
            }
        } finally {
            db = null;
        }
    }

    @Override
    public void doSetRecord(byte[] key, byte[] value) throws DataStoreFatalException {
	try {
	    if (this.synchronizeInterval == 0) {
		db.put(key, value, new WriteOptions().sync(true));
	    } else {
		db.put(key, value, new WriteOptions().sync(false));
	    }
	} catch (DBException e) {
	    throw new DataStoreFatalException("Unable to set value and key [0x" + HexUtil.toHexString(key) + "] [message=" + e.getMessage() + "]", e);
	}
    }

    @Override
    public byte[] doGetRecord(byte[] key) throws RecordNotFoundException, DataStoreFatalException {
	byte[] value = null;
	try {
	    value = db.get(key);
	} catch (DBException e) {
	    throw new DataStoreFatalException("Unable to get value at key [0x" + HexUtil.toHexString(key) + "] [message=" + e.getMessage() + "]", e);
	}
        if (value == null) {
            throw new RecordNotFoundException("The record for key [0x" + HexUtil.toHexString(key) + "] was not found");
        }
        return value;
    }

    @Override
    public void doDeleteRecord(byte[] key) throws RecordNotFoundException, DataStoreFatalException {
	try {
	    // TODO: need to throw a RecordNotFoundException if the key isn't there
	    doGetRecord(key);
	    if (this.synchronizeInterval == 0) {
		db.delete(key, new WriteOptions().sync(true));
	    } else {
		db.delete(key, new WriteOptions().sync(false));
	    }
	} catch (DBException e) {
	    throw new DataStoreFatalException("Unable to delete record with key [0x" + HexUtil.toHexString(key) + "] [message=" + e.getMessage() + "]", e);
	}
    }

    public DataStoreIterator doGetAscendingIterator() throws DataStoreFatalException {
        return new LevelDbDataStoreIterator(getDatabase());
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy