diff --git a/Makefile b/Makefile index 2e05e6513e..63a3646e4e 100644 --- a/Makefile +++ b/Makefile @@ -394,6 +394,31 @@ sst_dump: tools/sst_dump.o $(LIBOBJECTS) ldb: tools/ldb.o $(LIBOBJECTS) $(CXX) tools/ldb.o $(LIBOBJECTS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS) +# --------------------------------------------------------------------------- +# Jni stuff +# --------------------------------------------------------------------------- +JNI_NATIVE_SOURCES = ./java/rocksjni/rocksjni.cc + +JAVA_INCLUDE = -I/usr/lib/jvm/java-openjdk/include/ -I/usr/lib/jvm/java-openjdk/include/linux +ROCKSDBJNILIB = ./java/librocksdbjni.so + +ifeq ($(PLATFORM), OS_MACOSX) +ROCKSDBJNILIB = ./java/librocksdbjni.jnilib +JAVA_INCLUDE = -I/System/Library/Frameworks/JavaVM.framework/Headers/ +endif + +jni: clean + OPT="-fPIC -DNDEBUG -O2" $(MAKE) $(LIBRARY) -j32 + cd java;$(MAKE) java; + $(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o $(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(LIBOBJECTS) $(LDFLAGS) $(COVERAGEFLAGS) + +jclean: + cd java;$(MAKE) clean; + rm -f $(ROCKSDBJNILIB) + +jtest: + cd java;$(MAKE) sample; + # --------------------------------------------------------------------------- # Platform-specific compilation # --------------------------------------------------------------------------- @@ -457,6 +482,10 @@ depend: $(DEPFILES) # working solution. ifneq ($(MAKECMDGOALS),clean) ifneq ($(MAKECMDGOALS),format) +ifneq ($(MAKECMDGOALS),jclean) +ifneq ($(MAKECMDGOALS),jtest) -include $(DEPFILES) endif endif +endif +endif diff --git a/java/Makefile b/java/Makefile new file mode 100644 index 0000000000..794ec14397 --- /dev/null +++ b/java/Makefile @@ -0,0 +1,17 @@ +NATIVE_JAVA_CLASSES = org.rocksdb.RocksDB +NATIVE_INCLUDE = ./include +ROCKSDB_JAR = rocksdbjni.jar + +clean: + -find . -name "*.class" -exec rm {} \; + -find . -name "hs*.log" -exec rm {} \; + rm -f $(ROCKSDB_JAR) + +java: + javac org/rocksdb/*.java + jar -cf $(ROCKSDB_JAR) org/rocksdb/*.class + javah -d $(NATIVE_INCLUDE) -jni $(NATIVE_JAVA_CLASSES) + +sample: + javac -cp $(ROCKSDB_JAR) RocksDBSample.java + java -ea -Djava.library.path=.:../ -cp ".:./*" RocksDBSample /tmp/rocksdbjni/ diff --git a/java/RocksDBSample.java b/java/RocksDBSample.java new file mode 100644 index 0000000000..dc2ae6cd97 --- /dev/null +++ b/java/RocksDBSample.java @@ -0,0 +1,79 @@ +// Copyright (c) 2014, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. + +import java.util.*; +import java.lang.*; +import org.rocksdb.*; +import java.io.IOException; + +public class RocksDBSample { + static { + System.loadLibrary("rocksdbjni"); + } + + public static void main(String[] args) { + if (args.length < 1) { + System.out.println("usage: RocksDBSample db_path"); + return; + } + String db_path = args[0]; + + System.out.println("RocksDBSample"); + + try { + RocksDB db = RocksDB.open(db_path); + db.put("hello".getBytes(), "world".getBytes()); + byte[] value = db.get("hello".getBytes()); + System.out.format("Get('hello') = %s\n", + new String(value)); + + for (int i = 1; i <= 9; ++i) { + for (int j = 1; j <= 9; ++j) { + db.put(String.format("%dx%d", i, j).getBytes(), + String.format("%d", i * j).getBytes()); + } + } + + for (int i = 1; i <= 9; ++i) { + for (int j = 1; j <= 9; ++j) { + System.out.format("%s ", new String(db.get( + String.format("%dx%d", i, j).getBytes()))); + } + System.out.println(""); + } + + value = db.get("1x1".getBytes()); + assert(value != null); + value = db.get("world".getBytes()); + assert(value == null); + + byte[] testKey = "asdf".getBytes(); + byte[] testValue = + "asdfghjkl;'?> testKey.length); + len = db.get("asdfjkl;".getBytes(), enoughArray); + assert(len == RocksDB.NOT_FOUND); + len = db.get(testKey, enoughArray); + assert(len == testValue.length); + try { + db.close(); + } catch (IOException e) { + System.err.println(e); + } + } catch (RocksDBException e) { + System.err.println(e); + } + } +} diff --git a/java/org/rocksdb/RocksDB.java b/java/org/rocksdb/RocksDB.java new file mode 100644 index 0000000000..cd43cf4e5e --- /dev/null +++ b/java/org/rocksdb/RocksDB.java @@ -0,0 +1,101 @@ +// Copyright (c) 2013, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. + +package org.rocksdb; + +import java.lang.*; +import java.util.*; +import java.io.Closeable; +import java.io.IOException; + +/** + * A RocksDB is a persistent ordered map from keys to values. It is safe for + * concurrent access from multiple threads without any external synchronization. + * All methods of this class could potentially throw RocksDBException, which + * indicates sth wrong at the rocksdb library side and the call failed. + */ +public class RocksDB implements Closeable { + public static final int NOT_FOUND = -1; + /** + * The factory constructor of RocksDB that opens a RocksDB instance given + * the path to the database. + * + * @param path the path to the rocksdb. + * @param status an out value indicating the status of the Open(). + * @return a rocksdb instance on success, null if the specified rocksdb can + * not be opened. + */ + public static RocksDB open(String path) throws RocksDBException { + RocksDB db = new RocksDB(); + db.open0(path); + return db; + } + + @Override public void close() throws IOException { + close0(); + } + + /** + * Set the database entry for "key" to "value". + * + * @param key the specified key to be inserted. + * @param value the value associated with the specified key. + */ + public void put(byte[] key, byte[] value) throws RocksDBException { + put(key, key.length, value, value.length); + } + + /** + * Get the value associated with the specified key. + * + * @param key the key to retrieve the value. + * @param value the out-value to receive the retrieved value. + * @return The size of the actual value that matches the specified + * {@code key} in byte. If the return value is greater than the + * length of {@code value}, then it indicates that the size of the + * input buffer {@code value} is insufficient and partial result will + * be returned. RocksDB.NOT_FOUND will be returned if the value not + * found. + */ + public int get(byte[] key, byte[] value) throws RocksDBException { + return get(key, key.length, value, value.length); + } + + /** + * The simplified version of get which returns a new byte array storing + * the value associated with the specified input key if any. null will be + * returned if the specified key is not found. + * + * @param key the key retrieve the value. + * @return a byte array storing the value associated with the input key if + * any. null if it does not find the specified key. + * + * @see RocksDBException + */ + public byte[] get(byte[] key) throws RocksDBException { + return get(key, key.length); + } + + /** + * Private constructor. + */ + private RocksDB() { + nativeHandle = -1; + } + + // native methods + private native void open0(String path) throws RocksDBException; + private native void put( + byte[] key, int keyLen, + byte[] value, int valueLen) throws RocksDBException; + private native int get( + byte[] key, int keyLen, + byte[] value, int valueLen) throws RocksDBException; + private native byte[] get( + byte[] key, int keyLen) throws RocksDBException; + private native void close0(); + + private long nativeHandle; +} diff --git a/java/org/rocksdb/RocksDBException.java b/java/org/rocksdb/RocksDBException.java new file mode 100644 index 0000000000..e426e03ee0 --- /dev/null +++ b/java/org/rocksdb/RocksDBException.java @@ -0,0 +1,24 @@ +// Copyright (c) 2013, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. + +package org.rocksdb; + +import java.lang.*; +import java.util.*; + +/** + * A RocksDBException encapsulates the error of an operation. This exception + * type is used to describe an internal error from the c++ rocksdb library. + */ +public class RocksDBException extends Exception { + /** + * The private construct used by a set of public static factory method. + * + * @param msg the specified error message. + */ + public RocksDBException(String msg) { + super(msg); + } +} diff --git a/java/rocksjni/portal.h b/java/rocksjni/portal.h new file mode 100644 index 0000000000..d51ea2059c --- /dev/null +++ b/java/rocksjni/portal.h @@ -0,0 +1,81 @@ +// Copyright (c) 2014, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. + +// This file is designed for caching those frequently used IDs and provide +// efficient portal (i.e, a set of static functions) to access java code +// from c++. + +#ifndef JAVA_ROCKSJNI_PORTAL_H_ +#define JAVA_ROCKSJNI_PORTAL_H_ + +#include +#include "rocksdb/db.h" + +namespace rocksdb { + +// The portal class for org.rocksdb.RocksDB +class RocksDBJni { + public: + // Get the java class id of org.rocksdb.RocksDB. + static jclass getJClass(JNIEnv* env) { + static jclass jclazz = env->FindClass("org/rocksdb/RocksDB"); + assert(jclazz != nullptr); + return jclazz; + } + + // Get the field id of the member variable of org.rocksdb.RocksDB + // that stores the pointer to rocksdb::DB. + static jfieldID getHandleFieldID(JNIEnv* env) { + static jfieldID fid = env->GetFieldID( + getJClass(env), "nativeHandle", "J"); + assert(fid != nullptr); + return fid; + } + + // Get the pointer to rocksdb::DB of the specified org.rocksdb.RocksDB. + static rocksdb::DB* getHandle(JNIEnv* env, jobject jdb) { + return reinterpret_cast( + env->GetLongField(jdb, getHandleFieldID(env))); + } + + // Pass the rocksdb::DB pointer to the java side. + static void setHandle(JNIEnv* env, jobject jdb, rocksdb::DB* db) { + env->SetLongField( + jdb, getHandleFieldID(env), + reinterpret_cast(db)); + } +}; + +// The portal class for org.rocksdb.RocksDBException +class RocksDBExceptionJni { + public: + // Get the jclass of org.rocksdb.RocksDBException + static jclass getJClass(JNIEnv* env) { + static jclass jclazz = env->FindClass("org/rocksdb/RocksDBException"); + assert(jclazz != nullptr); + return jclazz; + } + + // Create and throw a java exception by converting the input + // Status to an RocksDBException. + // + // In case s.ok() is true, then this function will not throw any + // exception. + static void ThrowNew(JNIEnv* env, Status s) { + if (s.ok()) { + return; + } + jstring msg = env->NewStringUTF(s.ToString().c_str()); + // get the constructor id of org.rocksdb.RocksDBException + static jmethodID mid = env->GetMethodID( + getJClass(env), "", "(Ljava/lang/String;)V"); + assert(mid != nullptr); + + env->Throw((jthrowable)env->NewObject(getJClass(env), mid, msg)); + } +}; + +} // namespace rocksdb +#endif // JAVA_ROCKSJNI_PORTAL_H_ diff --git a/java/rocksjni/rocksjni.cc b/java/rocksjni/rocksjni.cc new file mode 100644 index 0000000000..6c992bc000 --- /dev/null +++ b/java/rocksjni/rocksjni.cc @@ -0,0 +1,177 @@ +// Copyright (c) 2014, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// This file implements the "bridge" between Java and C++ and enables +// calling c++ rocksdb::DB methods from Java side. + +#include +#include +#include +#include + +#include "include/org_rocksdb_RocksDB.h" +#include "rocksjni/portal.h" +#include "rocksdb/db.h" + +/* + * Class: org_rocksdb_RocksDB + * Method: open0 + * Signature: (Ljava/lang/String;)V + */ +void Java_org_rocksdb_RocksDB_open0( + JNIEnv* env, jobject java_db, jstring jdb_path) { + rocksdb::DB* db; + rocksdb::Options options; + options.create_if_missing = true; + + jboolean isCopy = false; + const char* db_path = env->GetStringUTFChars(jdb_path, &isCopy); + rocksdb::Status s = rocksdb::DB::Open(options, db_path, &db); + env->ReleaseStringUTFChars(jdb_path, db_path); + + if (s.ok()) { + rocksdb::RocksDBJni::setHandle(env, java_db, db); + return; + } + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: put + * Signature: ([BI[BI)V + */ +void Java_org_rocksdb_RocksDB_put( + JNIEnv* env, jobject jdb, + jbyteArray jkey, jint jkey_len, + jbyteArray jvalue, jint jvalue_len) { + rocksdb::DB* db = rocksdb::RocksDBJni::getHandle(env, jdb); + + jboolean isCopy; + jbyte* key = env->GetByteArrayElements(jkey, &isCopy); + jbyte* value = env->GetByteArrayElements(jvalue, &isCopy); + rocksdb::Slice key_slice( + reinterpret_cast(key), jkey_len); + rocksdb::Slice value_slice( + reinterpret_cast(value), jvalue_len); + + rocksdb::Status s = db->Put( + rocksdb::WriteOptions(), key_slice, value_slice); + + // trigger java unref on key and value. + // by passing JNI_ABORT, it will simply release the reference without + // copying the result back to the java byte array. + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + env->ReleaseByteArrayElements(jvalue, value, JNI_ABORT); + + if (s.ok()) { + return; + } + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); +} + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: ([BI)[B + */ +jbyteArray Java_org_rocksdb_RocksDB_get___3BI( + JNIEnv* env, jobject jdb, jbyteArray jkey, jint jkey_len) { + rocksdb::DB* db = rocksdb::RocksDBJni::getHandle(env, jdb); + + jboolean isCopy; + jbyte* key = env->GetByteArrayElements(jkey, &isCopy); + rocksdb::Slice key_slice( + reinterpret_cast(key), jkey_len); + + std::string value; + rocksdb::Status s = db->Get( + rocksdb::ReadOptions(), + key_slice, &value); + + // trigger java unref on key. + // by passing JNI_ABORT, it will simply release the reference without + // copying the result back to the java byte array. + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + + if (s.IsNotFound()) { + return nullptr; + } + + if (s.ok()) { + jbyteArray jvalue = env->NewByteArray(value.size()); + env->SetByteArrayRegion( + jvalue, 0, value.size(), + reinterpret_cast(value.c_str())); + return jvalue; + } + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + + return nullptr; +} + +/* + * Class: org_rocksdb_RocksDB + * Method: get + * Signature: ([BI[BI)I + */ +jint Java_org_rocksdb_RocksDB_get___3BI_3BI( + JNIEnv* env, jobject jdb, + jbyteArray jkey, jint jkey_len, + jbyteArray jvalue, jint jvalue_len) { + rocksdb::DB* db = rocksdb::RocksDBJni::getHandle(env, jdb); + + jboolean isCopy; + jbyte* key = env->GetByteArrayElements(jkey, &isCopy); + jbyte* value = env->GetByteArrayElements(jvalue, &isCopy); + rocksdb::Slice key_slice( + reinterpret_cast(key), jkey_len); + + // TODO(yhchiang): we might save one memory allocation here by adding + // a DB::Get() function which takes preallocated jbyte* as input. + std::string cvalue; + rocksdb::Status s = db->Get( + rocksdb::ReadOptions(), key_slice, &cvalue); + + // trigger java unref on key. + // by passing JNI_ABORT, it will simply release the reference without + // copying the result back to the java byte array. + env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); + + if (s.IsNotFound()) { + env->ReleaseByteArrayElements(jvalue, value, JNI_ABORT); + return -1; + } else if (s.ok()) { + int cvalue_len = static_cast(cvalue.size()); + int length = cvalue_len; + // currently we prevent overflowing. + if (length > jvalue_len) { + length = jvalue_len; + } + memcpy(value, cvalue.c_str(), length); + env->ReleaseByteArrayElements(jvalue, value, JNI_COMMIT); + if (cvalue_len > length) { + return static_cast(cvalue.size()); + } + return length; + } + rocksdb::RocksDBExceptionJni::ThrowNew(env, s); + + return -1; +} + +/* + * Class: org_rocksdb_RocksDB + * Method: close0 + * Signature: ()V + */ +void Java_org_rocksdb_RocksDB_close0( + JNIEnv* env, jobject java_db) { + rocksdb::DB* db = rocksdb::RocksDBJni::getHandle(env, java_db); + delete db; + db = nullptr; + + rocksdb::RocksDBJni::setHandle(env, java_db, db); +}