/*
* JBoss, Home of Professional Open Source.
* Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.cache.loader.bdbje;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.bind.tuple.TupleInput;
import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DeadlockException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.JEVersion;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import net.jcip.annotations.ThreadSafe;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.Modification;
import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
import org.jboss.cache.loader.AbstractCacheLoader;
import org.jboss.cache.util.reflect.ReflectionUtil;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* A persistent <code>CacheLoader</code> based on Berkeley DB Java Edition.
* <p/>
* <p>The configuration string format is:</p>
* <pre>environmentDirectoryName[#databaseName]</pre>
* <p>where databaseName, if omitted, defaults to the ClusterName property
* of the CacheImpl.</p>
* <p/>
* <p>A je.properties file may optionally be placed in the JE environment
* directory and used to customize the default JE configuration.</p>
*
* @author Mark Hayes May 16, 2004
* @author Bela Ban
* @version $Id: BdbjeCacheLoader.java 6806 2008-09-26 21:38:03Z genman $
*/
@ThreadSafe
public class BdbjeCacheLoader extends AbstractCacheLoader
{
private static final int MAX_TXN_RETRIES = 10;
private static final char LOWEST_UTF_CHAR = '\u0001';
private static final Log log = LogFactory.getLog(BdbjeCacheLoader.class);
private BdbjeCacheLoaderConfig config;
private Environment env;
private String cacheDbName;
private String catalogDbName;
private Database cacheDb;
private Database catalogDb;
private StoredClassCatalog catalog;
private SerialBinding serialBinding;
private Map<Object, Transaction> txnMap;
private boolean transactional;
/*
* Service implementation -- lifecycle methods.
* Note that setConfig() and setCache() are called before create().
*/
/**
* Does nothing since start() does all the work.
*/
@Override
public void create() throws Exception
{
String license = "\n*************************************************************************************\n" +
"Berkeley DB Java Edition version: " + JEVersion.CURRENT_VERSION.toString() + "\n" +
"JBoss Cache can use Berkeley DB Java Edition from Oracle \n" +
"(http://www.oracle.com/database/berkeley-db/je/index.html)\n" +
"for persistent, reliable and transaction-protected data storage.\n" +
"If you choose to use Berkeley DB Java Edition with JBoss Cache, you must comply with the terms\n" +
"of Oracle's public license, included in the file LICENSE.txt.\n" +
"If you prefer not to release the source code for your own application in order to comply\n" +
"with the Oracle public license, you may purchase a different license for use of\n" +
"Berkeley DB Java Edition with JBoss Cache.\n" +
"See http://www.oracle.com/database/berkeley-db/je/index.html for pricing and license terms\n" +
"*************************************************************************************";
System.out.println(license);
log.trace("Creating BdbjeCacheLoader instance.");
checkNotOpen();
}
/**
* Opens the JE environment and the database specified by the configuration
* string. The environment and databases are created if necessary.
*/
@Override
public void start()
throws Exception
{
log.trace("Starting BdbjeCacheLoader instance.");
checkNotOpen();
if (cache == null)
{
throw new IllegalStateException(
"A non-null Cache property (CacheSPI object) is required");
}
String configStr = config.getLocation();
if (config.getLocation() == null)
{
configStr = System.getProperty("java.io.tmpdir");
ReflectionUtil.setValue(config, "accessible", true);
config.setLocation(configStr);
}
// test location
File location = new File(configStr);
if (!location.exists())
{
boolean created = location.mkdirs();
if (!created) throw new IOException("Unable to create cache loader location " + location);
}
if (!location.isDirectory())
{
throw new IOException("Cache loader location [" + location + "] is not a directory!");
}
/* Parse config string. */
File homeDir;
int offset = configStr.indexOf('#');
if (offset >= 0 && offset < configStr.length() - 1)
{
homeDir = new File(configStr.substring(0, offset));
cacheDbName = configStr.substring(offset + 1);
}
else
{
homeDir = new File(configStr);
cacheDbName = cache.getClusterName();
}
catalogDbName = cacheDbName + "_class_catalog";
/*
* If the CacheImpl is transactional, we will create transactional
* databases. However, we always create a transactional environment
* since it may be shared by transactional and non-transactional caches.
*/
transactional = cache.getTransactionManager() != null;
try
{
/* Open the environment, creating it if it doesn't exist. */
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);
envConfig.setTransactional(true);
envConfig.setLockTimeout(1000 * cache.getConfiguration().getLockAcquisitionTimeout()); // these are in nanos
if (log.isTraceEnabled()) log.trace("Creating JE environment with home dir " + homeDir);
env = new Environment(homeDir, envConfig);
if (log.isDebugEnabled()) log.debug("Created JE environment " + env + " for cache loader " + this);
/* Open cache and catalog databases. */
openDatabases();
}
catch (Exception e)
{
destroy();
throw e;
}
}
/**
* Opens all databases and initializes database related information.
*/
private void openDatabases()
throws Exception
{
/* Use a generic database config, with no duplicates allowed. */
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
dbConfig.setTransactional(transactional);
/* Create/open the cache database and associated catalog database. */
cacheDb = env.openDatabase(null, cacheDbName, dbConfig);
catalogDb = env.openDatabase(null, catalogDbName, dbConfig);
/* Use the catalog for the serial binding. */
catalog = new StoredClassCatalog(catalogDb);
serialBinding = new SerialBinding(catalog, null);
/* Start with a fresh transaction map. */
txnMap = new ConcurrentHashMap<Object, Transaction>();
}
/**
* Closes all databases, ignoring exceptions, and nulls references to all
* database related information.
*/
private void closeDatabases()
{
if (cacheDb != null)
{
try
{
cacheDb.close();
}
catch (Exception shouldNotOccur)
{
log.warn("Caught unexpected exception", shouldNotOccur);
}
}
if (catalogDb != null)
{
try
{
catalogDb.close();
}
catch (Exception shouldNotOccur)
{
log.warn("Caught unexpected exception", shouldNotOccur);
}
}
cacheDb = null;
catalogDb = null;
catalog = null;
serialBinding = null;
txnMap = null;
}
/**
* Closes the JE databases and environment, and nulls references to them.
* The environment and databases are not removed from the file system.
* Exceptions during close are ignored.
*/
@Override
public void stop()
{
closeDatabases();
if (env != null)
{
try
{
env.close();
}
catch (Exception shouldNotOccur)
{
log.warn("Unexpected exception", shouldNotOccur);
}
}
env = null;
}
/*
* CacheLoader implementation.
*/
/**
* Sets the configuration string for this cache loader.
*/
public void setConfig(IndividualCacheLoaderConfig base)
{
checkNotOpen();
if (base instanceof BdbjeCacheLoaderConfig)
{
this.config = (BdbjeCacheLoaderConfig) base;
}
else
{
config = new BdbjeCacheLoaderConfig(base);
}
if (log.isTraceEnabled()) log.trace("Configuring cache loader with location = " + config.getLocation());
}
public IndividualCacheLoaderConfig getConfig()
{
return config;
}
/**
* Sets the CacheImpl owner of this cache loader.
*/
@Override
public void setCache(CacheSPI c)
{
super.setCache(c);
checkNotOpen();
}
/**
* Returns an unmodifiable set of relative children names (strings), or
* returns null if the parent node is not found or if no children are found.
* This is a fairly expensive operation, and is assumed to be performed by
* browser applications. Calling this method as part of a run-time
* transaction is not recommended.
*/
public Set<String> getChildrenNames(Fqn name)
throws Exception
{
checkOpen();
checkNonNull(name, "name");
DatabaseEntry prefixEntry = makeKeyEntry(name);
DatabaseEntry dataEntry = new DatabaseEntry();
dataEntry.setPartial(0, 0, true);
String namePart = "";
int namePartIndex = name.size();
Set<String> set = null;
Cursor cursor = cacheDb.openCursor(null, null);
try
{
while (true)
{
DatabaseEntry keyEntry = makeKeyEntry(prefixEntry, namePart);
OperationStatus status =
cursor.getSearchKeyRange(keyEntry, dataEntry, null);
if (status != OperationStatus.SUCCESS ||
!startsWith(keyEntry, prefixEntry))
{
break;
}
if (set == null)
{
set = new HashSet<String>();
}
Fqn childName = makeKeyObject(keyEntry);
namePart = childName.get(namePartIndex).toString();
set.add(namePart);
namePart += LOWEST_UTF_CHAR;
}
}
finally
{
cursor.close();
}
if (set != null)
{
return Collections.unmodifiableSet(set);
}
else
{
return null;
}
}
/**
* Returns a map containing all key-value pairs for the given FQN, or null
* if the node is not present.
* This operation is always non-transactional, even in a transactional
* environment.
*/
public Map<Object, Object> get(Fqn name)
throws Exception
{
checkOpen();
checkNonNull(name, "name");
DatabaseEntry keyEntry = makeKeyEntry(name);
DatabaseEntry foundData = new DatabaseEntry();
OperationStatus status = cacheDb.get(null, keyEntry, foundData, null);
if (status == OperationStatus.SUCCESS)
{
// changed createIfNull param to true
// See http://jira.jboss.com/jira/browse/JBCACHE-118
return makeDataObject(foundData);
}
else
{
return null;
}
}
// See http://jira.jboss.com/jira/browse/JBCACHE-118 for why this is commented out.
/**
* Returns the data object stored under the given FQN and key, or null if
* the FQN and key are not present.
* This operation is always non-transactional, even in a transactional
* environment.
*/
// public Object get(Fqn name, Object key)
// throws Exception {
//
// Map map = get(name);
// if (map != null) {
// return map.get(key);
// } else {
// return null;
// }
// }
/**
* Returns whether the given node exists.
*/
public boolean exists(Fqn name)
throws Exception
{
checkOpen();
checkNonNull(name, "name");
DatabaseEntry keyEntry = makeKeyEntry(name);
DatabaseEntry foundData = new DatabaseEntry();
foundData.setPartial(0, 0, true);
OperationStatus status = cacheDb.get(null, keyEntry, foundData, null);
return (status == OperationStatus.SUCCESS);
}
/**
* Stores a single FQN-key-value record.
* Intended to be used in a non-transactional environment, but will use
* auto-commit in a transactional environment.
*/
public Object put(Fqn name, Object key, Object value) throws Exception
{
checkOpen();
checkNonNull(name, "name");
Object oldVal;
if (transactional)
{
Modification mod =
new Modification(Modification.ModificationType.PUT_KEY_VALUE, name, key, value);
commitModification(mod);
oldVal = mod.getOldValue();
}
else
{
oldVal = doPut(null, name, key, value);
}
return oldVal;
}
/**
* Internal version of store(String,Object,Object) that allows passing a
* transaction.
*/
private Object doPut(Transaction txn, Fqn name, Object key, Object value)
throws Exception
{
Object oldVal = null;
/* To update-or-insert, try putNoOverwrite first, then a RMW cycle. */
Map<Object, Object> map = new HashMap<Object, Object>();
map.put(key, value);
DatabaseEntry dataEntry = makeDataEntry(map);
DatabaseEntry keyEntry = makeKeyEntry(name);
Cursor cursor = cacheDb.openCursor(txn, null);
try
{
OperationStatus status = cursor.putNoOverwrite(keyEntry, dataEntry);
if (status == OperationStatus.SUCCESS)
{
createParentNodes(cursor, name);
}
else
{
DatabaseEntry foundData = new DatabaseEntry();
status = cursor.getSearchKey(keyEntry, foundData, LockMode.RMW);
if (status == OperationStatus.SUCCESS)
{
map = makeDataObject(foundData);
oldVal = map.put(key, value);
cursor.putCurrent(makeDataEntry(map));
}
}
}
finally
{
cursor.close();
}
return oldVal;
}
/**
* Stores a map of key-values for a given FQN, but does not delete existing
* key-value pairs (that is, it does not erase).
* Intended to be used in a non-transactional environment, but will use
* auto-commit in a transactional environment.
*/
public void put(Fqn name, Map values)
throws Exception
{
checkOpen();
checkNonNull(name, "name");
if (transactional)
{
commitModification(
new Modification(Modification.ModificationType.PUT_DATA, name, values));
}
else
{
doPut(null, name, values);
}
}
/**
* Internal version of put(Fqn,Map) that allows passing a
* transaction.
*/
private void doPut(Transaction txn, Fqn name, Map values)
throws Exception
{
// JBCACHE-769 -- make a defensive copy
if (values != null && !(values instanceof HashMap)) {
values = new HashMap(values);
}
/* To update-or-insert, try putNoOverwrite first, then a RMW cycle. */
DatabaseEntry dataEntry = makeDataEntry(values);
DatabaseEntry keyEntry = makeKeyEntry(name);
Cursor cursor = cacheDb.openCursor(txn, null);
try
{
OperationStatus status = cursor.put(keyEntry, dataEntry);
if (status == OperationStatus.SUCCESS)
{
createParentNodes(cursor, name);
}
}
finally
{
cursor.close();
}
}
/**
* Internal version of put(Fqn,Map) that allows passing a
* transaction and erases existing data.
*/
private void doPutErase(Transaction txn, Fqn name, Map<Object, Object> values)
throws Exception
{
// JBCACHE-769 -- make a defensive copy
values = (values == null ? null : new HashMap<Object, Object>(values));
DatabaseEntry dataEntry = makeDataEntry(values);
DatabaseEntry keyEntry = makeKeyEntry(name);
Cursor cursor = cacheDb.openCursor(txn, null);
try
{
cursor.put(keyEntry, dataEntry);
createParentNodes(cursor, name);
}
finally
{
cursor.close();
}
}
/**
* Applies the given modifications.
* Intended to be used in a non-transactional environment, but will use
* auto-commit in a transactional environment.
*/
@Override
public void put(List<Modification> modifications) throws Exception
{
checkOpen();
checkNonNull(modifications, "modifications");
if (transactional)
{
commitModifications(modifications);
}
else
{
doPut(null, modifications);
}
}
/**
* Internal version of put(List) that allows passing a transaction.
*/
private void doPut(Transaction txn, List<Modification> modifications)
throws Exception
{
/* This could be optimized by grouping modifications by Fqn, and
* performing a single database operation for each Fqn (record). */
for (Modification mod : modifications)
{
Fqn name = mod.getFqn();
Object oldVal;
switch (mod.getType())
{
case PUT_KEY_VALUE:
oldVal = doPut(txn, name, mod.getKey(), mod.getValue());
mod.setOldValue(oldVal);
break;
case PUT_DATA:
doPut(txn, name, mod.getData());
break;
case PUT_DATA_ERASE:
doPutErase(txn, name, mod.getData());
break;
case REMOVE_KEY_VALUE:
oldVal = doRemove(txn, name, mod.getKey());
mod.setOldValue(oldVal);
break;
case REMOVE_NODE:
doRemove(txn, name);
break;
case REMOVE_DATA:
doRemoveData(txn, name);
break;
default:
throw new IllegalArgumentException(
"Unknown Modification type: " + mod.getType());
}
}
}
/**
* Creates parent nodes of the given Fqn, moving upward until an existing
* node is found.
*/
private void createParentNodes(Cursor cursor, Fqn name)
throws Exception
{
DatabaseEntry dataEntry = makeDataEntry(null);
for (int nParts = name.size() - 1; nParts >= 1; nParts -= 1)
{
DatabaseEntry keyEntry = makeKeyEntry(name, nParts);
OperationStatus status = cursor.putNoOverwrite(keyEntry, dataEntry);
if (status != OperationStatus.SUCCESS)
{
break;
}
}
}
/**
* Deletes the node for a given FQN and all its descendent nodes.
* Intended to be used in a non-transactional environment, but will use
* auto-commit in a transactional environment.
*/
public void remove(Fqn name)
throws Exception
{
checkOpen();
checkNonNull(name, "name");
if (transactional)
{
commitModification(
new Modification(Modification.ModificationType.REMOVE_NODE, name));
}
else
{
doRemove(null, name);
}
}
/**
* Internal version of remove(Fqn) that allows passing a transaction.
*/
private void doRemove(Transaction txn, Fqn name)
throws Exception
{
// if the Fqn is Fqn.ROOT, make sure we get all the children and delete them instead.
if (name.isRoot())
{
Set<String> children = getChildrenNames(name);
if (children != null)
{
for (String child : children)
{
doRemove(txn, Fqn.fromElements(child));
}
}
}
else
{
DatabaseEntry keyEntry = makeKeyEntry(name);
DatabaseEntry foundKey = new DatabaseEntry();
DatabaseEntry foundData = new DatabaseEntry();
foundData.setPartial(0, 0, true);
Cursor cursor = cacheDb.openCursor(txn, null);
try
{
OperationStatus status =
cursor.getSearchKey(keyEntry, foundData, LockMode.RMW);
while (status == OperationStatus.SUCCESS)
{
cursor.delete();
status = cursor.getNext(foundKey, foundData, LockMode.RMW);
if (status == OperationStatus.SUCCESS &&
!startsWith(foundKey, keyEntry))
{
status = OperationStatus.NOTFOUND;
}
}
}
finally
{
cursor.close();
}
}
}
/**
* Deletes a single FQN-key-value record.
* Intended to be used in a non-transactional environment, but will use
* auto-commit in a transactional environment.
*/
public Object remove(Fqn name, Object key)
throws Exception
{
checkOpen();
checkNonNull(name, "name");
Object oldVal;
if (transactional)
{
Modification mod =
new Modification(Modification.ModificationType.REMOVE_KEY_VALUE, name, key);
commitModification(mod);
oldVal = mod.getOldValue();
}
else
{
oldVal = doRemove(null, name, key);
}
return oldVal;
}
/**
* Internal version of remove(String,Object) that allows passing a
* transaction.
*/
private Object doRemove(Transaction txn, Fqn name, Object key)
throws Exception
{
Object oldVal = null;
DatabaseEntry keyEntry = makeKeyEntry(name);
DatabaseEntry foundData = new DatabaseEntry();
Cursor cursor = cacheDb.openCursor(txn, null);
try
{
OperationStatus status =
cursor.getSearchKey(keyEntry, foundData, LockMode.RMW);
if (status == OperationStatus.SUCCESS)
{
Map map = makeDataObject(foundData);
oldVal = map.remove(key);
cursor.putCurrent(makeDataEntry(map));
}
}
finally
{
cursor.close();
}
return oldVal;
}
/**
* Clears the map for the given node, but does not remove the node.
*/
public void removeData(Fqn name)
throws Exception
{
checkOpen();
checkNonNull(name, "name");
if (transactional)
{
commitModification(
new Modification(Modification.ModificationType.REMOVE_DATA, name));
}
else
{
doRemoveData(null, name);
}
}
/**
* Internal version of removeData(Fqn) that allows passing a transaction.
*/
private void doRemoveData(Transaction txn, Fqn name)
throws Exception
{
DatabaseEntry dataEntry = new DatabaseEntry();
dataEntry.setPartial(0, 0, true);
DatabaseEntry keyEntry = makeKeyEntry(name);
Cursor cursor = cacheDb.openCursor(txn, null);
try
{
OperationStatus status =
cursor.getSearchKey(keyEntry, dataEntry, LockMode.RMW);
if (status == OperationStatus.SUCCESS)
{
cursor.putCurrent(makeDataEntry(null));
}
}
finally
{
cursor.close();
}
}
/**
* Begins a transaction and applies the given modifications.
* <p/>
* <p>If onePhase is true, commits the transaction; otherwise, associates
* the txn value with the transaction and expects commit() or rollback() to
* be called later with the same tx value. Performs retries if necessary to
* resolve deadlocks.</p>
*/
@Override
public void prepare(Object tx, List<Modification> modifications, boolean onePhase) throws Exception
{
checkOpen();
checkNonNull(modifications, "modifications");
if (!onePhase)
{
checkNonNull(tx, "tx");
}
if (!transactional)
{
throw new UnsupportedOperationException(
"prepare() not allowed with a non-transactional cache loader");
}
Transaction txn = performTransaction(modifications);
if (onePhase)
{
txn.commit();
}
else
{
txnMap.put(tx, txn);
}
}
/**
* Performs and commits a single modification. The loader must be
* transactional. Commits the transaction if successful, or aborts the
* transaction and throws an exception if not successful.
*/
private void commitModification(Modification mod)
throws Exception
{
commitModifications(Collections.singletonList(mod));
}
/**
* Performs and commits a list of modifications. The loader must be
* transactional. Commits the transaction if successful, or aborts the
* transaction and throws an exception if not successful.
*/
private void commitModifications(List<Modification> mods)
throws Exception
{
if (!transactional) throw new IllegalStateException();
Transaction txn = performTransaction(mods);
txn.commit();
}
/**
* Performs the given operation, starting a transaction and performing
* retries. Returns the transaction if successful; aborts the transaction
* and throws an exception if not successful.
*/
private Transaction performTransaction(List<Modification> modifications)
throws Exception
{
/*
* Note that we can't use TransactionRunner here since if onePhase=false
* in the call to prepare(), we do not want to commit. TransactionRunner
* always commits or aborts.
*/
int retries = MAX_TXN_RETRIES;
while (true)
{
Transaction txn = env.beginTransaction(null, null);
try
{
doPut(txn, modifications);
return txn;
}
catch (Exception e)
{
txn.abort();
if (e instanceof DeadlockException && retries > 0)
{
retries -= 1;
}
else
{
throw e;
}
}
}
}
/**
* Commits the given transaction, or throws IllegalArgumentException if the
* given key is not associated with an uncommited transaction.
*/
@Override
public void commit(Object tx)
throws Exception
{
checkOpen();
checkNonNull(tx, "tx");
Transaction txn = txnMap.remove(tx);
if (txn != null)
{
txn.commit();
}
else if (transactional)
{
throw new IllegalArgumentException("Unknown txn key: " + tx);
}
}
/**
* Commits the given transaction, or throws IllegalArgumentException if the
* given key is not associated with an uncommited transaction.
*/
@Override
public void rollback(Object tx)
{
checkOpen();
checkNonNull(tx, "tx");
Transaction txn = txnMap.remove(tx);
if (txn != null)
{
try
{
txn.abort();
}
catch (Exception ignored)
{
}
}
else if (transactional)
{
throw new IllegalArgumentException("Unknown txn key: " + tx);
}
}
/**
* Returns whether the given entry starts with the given prefix bytes.
* Used to determine whether a database key starts with a given FQN.
*/
private boolean startsWith(DatabaseEntry entry,
DatabaseEntry prefix)
{
int size = prefix.getSize();
if (size > entry.getSize())
{
return false;
}
byte[] d1 = entry.getData();
byte[] d2 = prefix.getData();
int o1 = entry.getOffset();
int o2 = prefix.getOffset();
for (int i = 0; i < size; i += 1)
{
if (d1[o1 + i] != d2[o2 + i])
{
return false;
}
}
return true;
}
/**
* Converts a database entry to an Fqn.
*/
private Fqn makeKeyObject(DatabaseEntry entry)
{
Fqn name = Fqn.ROOT;
TupleInput tupleInput = TupleBinding.entryToInput(entry);
while (tupleInput.available() > 0)
{
String part = tupleInput.readString();
name = Fqn.fromRelativeElements(name, part);
}
return name;
}
/**
* Converts an Fqn to a database entry.
*/
private DatabaseEntry makeKeyEntry(Fqn name)
{
return makeKeyEntry(name, name.size());
}
/**
* Converts an Fqn to a database entry, outputing the given number of name
* parts.
*/
private DatabaseEntry makeKeyEntry(Fqn name, int nParts)
{
/* Write the sequence of name parts. */
TupleOutput tupleOutput = new TupleOutput();
for (int i = 0; i < nParts; i += 1)
{
tupleOutput.writeString(name.get(i).toString());
}
/* Return the tuple as an entry. */
DatabaseEntry entry = new DatabaseEntry();
TupleBinding.outputToEntry(tupleOutput, entry);
return entry;
}
/**
* Creates a key database entry from a parent database entry (prefix) and
* a child name part.
*/
private DatabaseEntry makeKeyEntry(DatabaseEntry prefix, String namePart)
{
/* Write the bytes of the prefix followed by the child name. */
TupleOutput tupleOutput = new TupleOutput();
tupleOutput.writeFast(prefix.getData(),
prefix.getOffset(),
prefix.getSize());
tupleOutput.writeString(namePart);
/* Return the tuple as an entry. */
DatabaseEntry entry = new DatabaseEntry();
TupleBinding.outputToEntry(tupleOutput, entry);
return entry;
}
/**
* Converts a database entry to a Map.
*/
@SuppressWarnings("unchecked")
private Map<Object, Object> makeDataObject(DatabaseEntry entry)
{
Map<Object, Object> map = (Map<Object, Object>) serialBinding.entryToObject(entry);
if (map == null)
{
map = new HashMap<Object, Object>();
}
return map;
}
/**
* Converts a Map to a database entry.
*/
private DatabaseEntry makeDataEntry(Map<Object, Object> map)
{
if (map != null)
{
if (map.size() == 0)
{
map = null;
}
else if (!(map instanceof Serializable))
{
map = new HashMap<Object, Object>(map);
}
}
DatabaseEntry entry = new DatabaseEntry();
serialBinding.objectToEntry(map, entry);
return entry;
}
/**
* Throws an exception if the environment is not open.
*/
private void checkOpen()
{
if (env == null)
{
throw new IllegalStateException(
"Operation not allowed before calling create()");
}
}
/**
* Throws an exception if the environment is not open.
*/
private void checkNotOpen()
{
if (env != null)
{
throw new IllegalStateException(
"Operation not allowed after calling create()");
}
}
/**
* Throws an exception if the parameter is null.
*/
private void checkNonNull(Object param, String paramName)
{
if (param == null)
{
throw new NullPointerException(
"Parameter must not be null: " + paramName);
}
}
}