rocksdb/java/rocksjni/kv_helper.h

282 lines
8.0 KiB
C++

// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
//
// This file defines helper methods for Java API write methods
//
#pragma once
#include <jni.h>
#include <cstring>
#include <exception>
#include <functional>
#include <string>
#include "rocksdb/rocksdb_namespace.h"
#include "rocksdb/slice.h"
#include "rocksdb/status.h"
#include "rocksjni/portal.h"
namespace ROCKSDB_NAMESPACE {
/**
* @brief Exception class used to make the flow of key/value (Put(), Get(),
* Merge(), ...) calls clearer.
*
* This class is used by Java API JNI methods in try { save/fetch } catch { ...
* } style.
*
*/
class KVException : public std::exception {
public:
// These values are expected on Java API calls to represent the result of a
// Get() which has failed; a negative length is returned to indicate an error.
static const int kNotFound = -1; // the key was not found in RocksDB
static const int kStatusError =
-2; // there was some other error fetching the value for the key
/**
* @brief Throw a KVException (and potentially a Java exception) if the
* RocksDB status is "bad"
*
* @param env JNI environment needed to create a Java exception
* @param status RocksDB status to examine
*/
static void ThrowOnError(JNIEnv* env, const Status& status) {
if (status.ok()) {
return;
}
if (status.IsNotFound()) {
// IsNotFound does not generate a Java Exception, any other bad status
// does..
throw KVException(kNotFound);
}
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, status);
throw KVException(kStatusError);
}
/**
* @brief Throw a KVException and a Java exception
*
* @param env JNI environment needed to create a Java exception
* @param message content of the exception we will throw
*/
static void ThrowNew(JNIEnv* env, const std::string& message) {
ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, message);
throw KVException(kStatusError);
}
/**
* @brief Throw a KVException if there is already a Java exception in the JNI
* enviroment
*
* @param env
*/
static void ThrowOnError(JNIEnv* env) {
if (env->ExceptionCheck()) {
throw KVException(kStatusError);
}
}
KVException(jint code) : kCode_(code){};
virtual const char* what() const noexcept {
return "Exception raised by JNI. There may be a Java exception in the "
"JNIEnv. Please check!";
}
jint Code() const { return kCode_; }
private:
jint kCode_;
};
/**
* @brief Construct a slice with the contents of a Java byte array
*
* The slice refers to an array into which the Java byte array's whole region is
* copied
*/
class JByteArraySlice {
public:
JByteArraySlice(JNIEnv* env, const jbyteArray& jarr, const jint jarr_off,
const jint jarr_len)
: arr_(new jbyte[jarr_len]),
slice_(reinterpret_cast<char*>(arr_), jarr_len) {
env->GetByteArrayRegion(jarr, jarr_off, jarr_len, arr_);
KVException::ThrowOnError(env);
};
~JByteArraySlice() {
slice_.clear();
delete[] arr_;
};
Slice& slice() { return slice_; }
private:
jbyte* arr_;
Slice slice_;
};
/**
* @brief Construct a slice with the contents of a direct Java ByterBuffer
*
* The slice refers directly to the contents of the buffer, no copy is made.
*
*/
class JDirectBufferSlice {
public:
JDirectBufferSlice(JNIEnv* env, const jobject& jbuffer,
const jint jbuffer_off, const jint jbuffer_len)
: slice_(static_cast<char*>(env->GetDirectBufferAddress(jbuffer)) +
jbuffer_off,
jbuffer_len) {
KVException::ThrowOnError(env);
jlong capacity = env->GetDirectBufferCapacity(jbuffer);
if (capacity < jbuffer_off + jbuffer_len) {
auto message = "Direct buffer offset " + std::to_string(jbuffer_off) +
" + length " + std::to_string(jbuffer_len) +
" exceeds capacity " + std::to_string(capacity);
KVException::ThrowNew(env, message);
slice_.clear();
}
}
~JDirectBufferSlice() { slice_.clear(); };
Slice& slice() { return slice_; }
private:
Slice slice_;
};
/**
* @brief Wrap a pinnable slice with a method to retrieve the contents back into
* Java
*
* The Java Byte Array version sets the byte array's region from the slice
*/
class JByteArrayPinnableSlice {
public:
/**
* @brief Construct a new JByteArrayPinnableSlice object referring to an
* existing java byte buffer
*
* @param env
* @param jbuffer
* @param jbuffer_off
* @param jbuffer_len
*/
JByteArrayPinnableSlice(JNIEnv* env, const jbyteArray& jbuffer,
const jint jbuffer_off, const jint jbuffer_len)
: env_(env),
jbuffer_(jbuffer),
jbuffer_off_(jbuffer_off),
jbuffer_len_(jbuffer_len){};
/**
* @brief Construct an empty new JByteArrayPinnableSlice object
*
*/
JByteArrayPinnableSlice(JNIEnv* env) : env_(env){};
PinnableSlice& pinnable_slice() { return pinnable_slice_; }
~JByteArrayPinnableSlice() { pinnable_slice_.Reset(); };
/**
* @brief copy back contents of the pinnable slice into the Java ByteBuffer
*
* @return jint min of size of buffer and number of bytes in value for
* requested key
*/
jint Fetch() {
const jint pinnable_len = static_cast<jint>(pinnable_slice_.size());
const jint result_len = std::min(jbuffer_len_, pinnable_len);
env_->SetByteArrayRegion(
jbuffer_, jbuffer_off_, result_len,
reinterpret_cast<const jbyte*>(pinnable_slice_.data()));
KVException::ThrowOnError(
env_); // exception thrown: ArrayIndexOutOfBoundsException
return pinnable_len;
};
/**
* @brief create a new Java buffer and copy the result into it
*
* @return jbyteArray the java buffer holding the result
*/
jbyteArray NewByteArray() {
const jint pinnable_len = static_cast<jint>(pinnable_slice_.size());
jbyteArray jbuffer =
ROCKSDB_NAMESPACE::JniUtil::createJavaByteArrayWithSizeCheck(
env_, pinnable_slice_.data(), pinnable_len);
KVException::ThrowOnError(env_); // OutOfMemoryError
return jbuffer;
}
private:
JNIEnv* env_;
jbyteArray jbuffer_;
jint jbuffer_off_;
jint jbuffer_len_;
PinnableSlice pinnable_slice_;
};
/**
* @brief Wrap a pinnable slice with a method to retrieve the contents back into
* Java
*
* The Java Direct Buffer version copies the memory of the buffer from the slice
*/
class JDirectBufferPinnableSlice {
public:
JDirectBufferPinnableSlice(JNIEnv* env, const jobject& jbuffer,
const jint jbuffer_off, const jint jbuffer_len)
: buffer_(static_cast<char*>(env->GetDirectBufferAddress(jbuffer)) +
jbuffer_off),
jbuffer_len_(jbuffer_len) {
jlong capacity = env->GetDirectBufferCapacity(jbuffer);
if (capacity < jbuffer_off + jbuffer_len) {
auto message =
"Invalid value argument. Capacity is less than requested region. "
"offset " +
std::to_string(jbuffer_off) + " + length " +
std::to_string(jbuffer_len) + " exceeds capacity " +
std::to_string(capacity);
KVException::ThrowNew(env, message);
}
}
PinnableSlice& pinnable_slice() { return pinnable_slice_; }
~JDirectBufferPinnableSlice() { pinnable_slice_.Reset(); };
/**
* @brief copy back contents of the pinnable slice into the Java DirectBuffer
*
* @return jint min of size of buffer and number of bytes in value for
* requested key
*/
jint Fetch() {
const jint pinnable_len = static_cast<jint>(pinnable_slice_.size());
const jint result_len = std::min(jbuffer_len_, pinnable_len);
memcpy(buffer_, pinnable_slice_.data(), result_len);
return pinnable_len;
};
private:
char* buffer_;
jint jbuffer_len_;
PinnableSlice pinnable_slice_;
};
} // namespace ROCKSDB_NAMESPACE