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

de.javakaffee.simplequeue.BDBQueue Maven / Gradle / Ivy

The newest version!
/*
 * 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 de.javakaffee.simplequeue;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;

import javax.annotation.Nonnull;

import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.util.RuntimeExceptionWrapper;

/**
 *
 * Fast queue implementation on top of Berkley DB Java Edition. This class is thread-safe.
 * 

* This is based on * . *

* * Created on Jun 27, 2011 * * @author Martin Grotzke (initial creation) */ public class BDBQueue { /** * Berkley DB environment. */ private final Environment dbEnv; /** * Berkley DB instance for the queue. */ private final Database queueDatabase; /** * Queue cache size - number of element operations it is allowed to loose in case of system crash. */ private final int cacheSize; /** * This queue name. */ private final String queueName; /** * Queue operation counter, which is used to sync the queue database to disk periodically. */ private int opsCounter; /** * Creates instance of persistent queue. * * @param queueEnvPath queue database environment directory path * @param queueName descriptive queue name * @param cacheSize how often to sync the queue to disk * @throws IOException thrown when the given queueEnvPath does not exist and cannot be created. */ public BDBQueue(final String queueEnvPath, final String queueName, final int cacheSize) throws IOException { this(queueEnvPath, queueName, cacheSize, false, true); } /** * Creates instance of persistent queue. * * @param queueEnvPath queue database environment directory path * @param queueName descriptive queue name * @param cacheSize how often to sync the queue to disk * @param readOnly if the db shall be accessed readonly * @param allowCreate if true, the db environment is created if it does not exist. * @throws IOException thrown when the given queueEnvPath does not exist and cannot be created. */ public BDBQueue(final String queueEnvPath, final String queueName, final int cacheSize, final boolean readOnly, final boolean allowCreate) throws IOException { // Create parent dirs for queue environment directory mkdir(new File(queueEnvPath), allowCreate); // Setup database environment final EnvironmentConfig dbEnvConfig = new EnvironmentConfig(); dbEnvConfig.setTransactional(false); dbEnvConfig.setAllowCreate(allowCreate); dbEnvConfig.setReadOnly(readOnly); this.dbEnv = new Environment(new File(queueEnvPath), dbEnvConfig); // Setup non-transactional deferred-write queue database final DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setTransactional(false); dbConfig.setAllowCreate(allowCreate); dbConfig.setReadOnly(readOnly); dbConfig.setDeferredWrite(true); dbConfig.setBtreeComparator(new KeyComparator()); this.queueDatabase = dbEnv.openDatabase(null, queueName, dbConfig); this.queueName = queueName; this.cacheSize = cacheSize; this.opsCounter = 0; } /** * Asserts that the given directory exists and creates it if necessary. * @param dir the directory that shall exist * @param createDirectoryIfNotExisting specifies if the directory shall be created if it does not exist. * @throws IOException thrown if the directory could not be created. */ public static void mkdir( @Nonnull final File dir, final boolean createDirectoryIfNotExisting ) throws IOException { // commons io FileUtils.forceMkdir would be useful here, we just want to omit this dependency if (!dir.exists()) { if(!createDirectoryIfNotExisting) { throw new IOException( "The directory " + dir.getAbsolutePath() + " does not exist." ); } if(!dir.mkdirs()) { throw new IOException( "Could not create directory " + dir.getAbsolutePath() ); } } if (!dir.isDirectory()) { throw new IOException("File " + dir + " exists and is not a directory. Unable to create directory."); } } /** * Retrieves and and removes element from the head of this queue. * * @return element from the head of the queue or null if queue is empty * * @throws IOException in case of disk IO failure */ public synchronized byte[] poll() throws IOException { final DatabaseEntry key = new DatabaseEntry(); final DatabaseEntry data = new DatabaseEntry(); final Cursor cursor = queueDatabase.openCursor(null, null); try { cursor.getFirst(key, data, LockMode.RMW); if (data.getData() == null) { return null; } cursor.delete(); opsCounter++; if (opsCounter >= cacheSize) { queueDatabase.sync(); opsCounter = 0; } return data.getData(); } finally { cursor.close(); } } /** * Returns an iterator over the elements in this queue. * * @return an iterator over elemtns of the queue. * * @throws IOException in case of disk IO failure */ public synchronized CloseableIterator iterator() throws IOException { final DatabaseEntry key = new DatabaseEntry(); final DatabaseEntry data = new DatabaseEntry(); final Cursor cursor = queueDatabase.openCursor(null, null); return new CloseableIterator() { private byte[] nextValue; @Override public void close() { cursor.close(); } @Override public boolean hasNext() { if (nextValue == null) { try { final OperationStatus status = cursor.getNext(key, data, LockMode.READ_UNCOMMITTED); if(status != OperationStatus.SUCCESS && status != OperationStatus.NOTFOUND) { throw new IllegalStateException("Getting next element did not return successfully: " + status); } nextValue = status == OperationStatus.SUCCESS ? data.getData() : null; } catch (final DatabaseException e) { throw RuntimeExceptionWrapper.wrapIfNeeded(e); } return nextValue != null; } else { return true; } } @Override public byte[] next() { if (hasNext()) { final byte[] v = nextValue; nextValue = null; return v; } else { throw new NoSuchElementException(); } } @Override public void remove() { cursor.delete(); } }; } /** * Retrieves element from the head of this queue. * * @return element from the head of the queue or null if queue is empty * * @throws IOException in case of disk IO failure */ public synchronized byte[] peek() throws IOException { final DatabaseEntry key = new DatabaseEntry(); final DatabaseEntry data = new DatabaseEntry(); final Cursor cursor = queueDatabase.openCursor(null, null); try { cursor.getFirst(key, data, LockMode.RMW); if (data.getData() == null) { return null; } return data.getData(); } finally { cursor.close(); } } /** * Removes the eldest element. * * @throw NoSuchElementException if the queue is empty */ public synchronized void remove() throws NoSuchElementException { final DatabaseEntry key = new DatabaseEntry(); final DatabaseEntry data = new DatabaseEntry(); final Cursor cursor = queueDatabase.openCursor(null, null); try { cursor.getFirst(key, data, LockMode.RMW); if (data.getData() == null) { throw new NoSuchElementException(); } cursor.delete(); opsCounter++; if (opsCounter >= cacheSize) { queueDatabase.sync(); opsCounter = 0; } } finally { cursor.close(); } } /** * Pushes element to the tail of this queue. * * @param element element * * @throws IOException in case of disk IO failure */ public synchronized void push(final byte[] element) throws IOException { final DatabaseEntry key = new DatabaseEntry(); final DatabaseEntry data = new DatabaseEntry(); final Cursor cursor = queueDatabase.openCursor(null, null); try { cursor.getLast(key, data, LockMode.RMW); BigInteger prevKeyValue; if (key.getData() == null) { prevKeyValue = BigInteger.valueOf(-1); } else { prevKeyValue = new BigInteger(key.getData()); } final BigInteger newKeyValue = prevKeyValue.add(BigInteger.ONE); final DatabaseEntry newKey = new DatabaseEntry( newKeyValue.toByteArray()); final DatabaseEntry newData = new DatabaseEntry(element); queueDatabase.put(null, newKey, newData); opsCounter++; if (opsCounter >= cacheSize) { queueDatabase.sync(); opsCounter = 0; } } finally { cursor.close(); } } public synchronized int clear() { final DatabaseEntry key = new DatabaseEntry(); final DatabaseEntry data = new DatabaseEntry(); final Cursor cursor = queueDatabase.openCursor(null, null); try { int itemsRemoved = 0; while(cursor.getNext(key, data, LockMode.RMW) == OperationStatus.SUCCESS && data.getData() != null) { cursor.delete(); itemsRemoved++; } queueDatabase.sync(); opsCounter = 0; return itemsRemoved; } finally { cursor.close(); } } /** * Returns the size of this queue. * * @return the size of the queue */ public long size() { return queueDatabase.count(); } /** * Determines if this queue is empty (equivalent to {@link #size()} == 0). * * @return true if this queue is empty, otherwise false. */ public boolean isEmpty() { return queueDatabase.count() == 0; } /** * Returns this queue name. * * @return this queue name */ public String getQueueName() { return queueName; } /** * Closes this queue and frees up all resources associated to it. */ public void close() { // When the current thread was interrupted db.close and dbEnv.close will complain: // "InterruptedException may cause incorrect internal state, unable to continue. Environment is invalid and must be closed." // First closing the environment also does not help, therefore we reset the interrupted state // and restore it after closing if it was set final boolean interrupted = Thread.interrupted(); queueDatabase.close(); dbEnv.close(); if(interrupted) { Thread.currentThread().interrupt(); } } public static interface CloseableIterator extends Iterator { /** * Must be invoked when the iterator is no longer used. */ void close(); } } /** * Key comparator for DB keys. */ class KeyComparator implements Comparator, Serializable { private static final long serialVersionUID = -7403144993786576375L; /** * Compares two DB keys. * * @param key1 first key * @param key2 second key * * @return comparison result */ @Override public int compare(final byte[] key1, final byte[] key2) { return new BigInteger(key1).compareTo(new BigInteger(key2)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy