blob: de36f16b90c7b82c5bb9148481f56db00edd01a0 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "base/android_jni_proxy.h"
#include <jni.h>
#include "base/logging.h"
#include "base/mutex.h"
#include "base/scoped_ptr.h"
#include "net/http_client_common.h"
namespace {
// To invoke Java's method, we need to ensure that the current thread is
// attached to Java VM. This class manages it, and provides a way to access
// JNIEnv instance, which is an interface to call Java's methods.
class ScopedJavaThreadAttacher {
public:
explicit ScopedJavaThreadAttacher(JavaVM *jvm) : jvm_(jvm) {
jni_env_ = GetJNIEnv(jvm_, &attached_);
}
~ScopedJavaThreadAttacher() {
if (attached_ && jvm_ != NULL) {
jvm_->DetachCurrentThread();
}
}
// Returned JNIEnv may be invalidated when this instance is destructed.
JNIEnv *mutable_jni_env() {
return jni_env_;
}
private:
JavaVM *jvm_;
JNIEnv *jni_env_;
bool attached_;
// Returns JNIEnv instance which attached to the current thread, and
// stores true into attached, when this thread is newly attached to the
// Java VM (i.e., the caller has responsibility to detach from the Java VM.
static JNIEnv* GetJNIEnv(JavaVM *jvm, bool *attached) {
*attached = false;
if (jvm == NULL) {
// In case jvm is accidentally NULL, we do nothing, instead of
// terminating the thread.
LOG(ERROR) << "Given jvm is null.";
return NULL;
}
// First, try to get JNIEnv instance attached to the current thread.
JNIEnv *env;
jint result = jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
if (result == JNI_EDETACHED) {
// This thread is not attached to Java VM. So try to attach.
JavaVMAttachArgs attach_args = { JNI_VERSION_1_6, NULL, NULL };
result = jvm->AttachCurrentThread(&env, &attach_args);
if (result != JNI_OK) {
LOG(ERROR) << "Failed to attach Java thread.";
return NULL;
}
*attached = true;
}
if (result != JNI_OK) {
LOG(ERROR) << "Failed to get JNIEnv.";
return NULL;
}
return env;
}
DISALLOW_COPY_AND_ASSIGN(ScopedJavaThreadAttacher);
};
// Creates jbyteArray instance containing the data.
jbyteArray BufferToJByteArray(
JNIEnv *env, const void *data, const size_t size) {
jbyteArray result = env->NewByteArray(size);
CHECK(result);
env->SetByteArrayRegion(
result, 0, size, reinterpret_cast<const jbyte*>(data));
return result;
}
// Copies the contents of the given source to buf, and store the size into
// buf_size.
// Retruns true, if finished successfully. Otherwise, false.
bool CopyJByteArrayToBuf(JNIEnv *env, const jbyteArray &source,
void *buf, size_t *buf_size) {
const jsize size = env->GetArrayLength(source);
if (size > *buf_size) {
return false;
}
env->GetByteArrayRegion(source, 0, size, reinterpret_cast<jbyte*>(buf));
*buf_size = size;
return true;
}
void AssignJByteArrayToString(JNIEnv *env, const jbyteArray &source,
string *dest) {
size_t size = env->GetArrayLength(source);
scoped_ptr<char[]> buf(new char[size]);
CHECK(CopyJByteArrayToBuf(env, source, buf.get(), &size));
dest->assign(buf.get(), size);
}
const char *HTTPMethodTypeToChars(mozc::HTTPMethodType type) {
switch (type) {
case mozc::HTTP_GET:
return "GET";
case mozc::HTTP_HEAD:
return "HEAD";
case mozc::HTTP_POST:
return "POST";
default:
LOG(ERROR) << "Invalid method; " << type;
return NULL;
}
}
// Utility to enlarge the java's local frame, as RAII idiom, like scoped_ptr.
class ScopedJavaLocalFrame {
public:
ScopedJavaLocalFrame(JNIEnv *env, jint capacity) : env_(env) {
env_->PushLocalFrame(capacity);
}
~ScopedJavaLocalFrame() {
env_->PopLocalFrame(NULL);
}
private:
JNIEnv *env_;
DISALLOW_COPY_AND_ASSIGN(ScopedJavaLocalFrame);
};
const jint kDefaultLocalFrameSize = 16;
mozc::Mutex jvm_mutex;
class JavaHttpClientDescriptor {
public:
explicit JavaHttpClientDescriptor(JavaVM *jvm) : jvm_(jvm) {
CHECK(jvm != NULL) << "Given JVM is null.";
ScopedJavaThreadAttacher thread_attacher(jvm);
JNIEnv *env = thread_attacher.mutable_jni_env();
ScopedJavaLocalFrame local_frame(env, kDefaultLocalFrameSize);
const char kHttpClientClassPath[] =
"org/mozc/android/inputmethod/japanese/nativecallback/HttpClient";
jclass http_Client_class = env->FindClass(kHttpClientClassPath);
CHECK(http_Client_class != NULL)
<< kHttpClientClassPath << " is not found.";
// We need to keep "global" reference for jclass instance, in order to
// avoid the garbage collection of the encryptor class.
http_client_class_ =
static_cast<jclass>(env->NewGlobalRef(http_Client_class));
request_id_ = env->GetStaticMethodID(
http_client_class_, "request", "([B[B[B)[B");
}
~JavaHttpClientDescriptor() {
ScopedJavaThreadAttacher thread_attacher(jvm_);
JNIEnv *env = thread_attacher.mutable_jni_env();
env->DeleteGlobalRef(http_client_class_);
}
JavaVM *mutable_jvm() {
return jvm_;
}
jbyteArray Request(JNIEnv *env,
jbyteArray method, jbyteArray url, jbyteArray post_data) {
jbyteArray result = static_cast<jbyteArray>(env->CallStaticObjectMethod(
http_client_class_, request_id_, method, url, post_data));
if (env->ExceptionOccurred() != NULL) {
env->ExceptionDescribe();
env->ExceptionClear();
return NULL;
}
return result;
}
static JavaHttpClientDescriptor *Create(JavaVM *jvm) {
if (jvm == NULL) {
return NULL;
}
return new JavaHttpClientDescriptor(jvm);
}
private:
JavaVM *jvm_;
jclass http_client_class_;
jmethodID request_id_;
DISALLOW_COPY_AND_ASSIGN(JavaHttpClientDescriptor);
};
scoped_ptr<JavaHttpClientDescriptor> http_client_descriptor;
} // namespace
namespace mozc {
namespace jni {
// static
bool JavaHttpClientProxy::Request(HTTPMethodType type,
const string &url,
const char *post_data,
size_t post_size,
const HTTPClient::Option &option,
string *output_string) {
JavaHttpClientDescriptor *descriptor = http_client_descriptor.get();
if (descriptor == NULL) {
LOG(ERROR) << "JavaVM is not initialized.";
return false;
}
ScopedJavaThreadAttacher thread_attacher(descriptor->mutable_jvm());
JNIEnv *env = thread_attacher.mutable_jni_env();
if (env == NULL) {
LOG(ERROR) << "Java env is not available.";
return false;
}
ScopedJavaLocalFrame local_frame(env, kDefaultLocalFrameSize);
const char *method = HTTPMethodTypeToChars(type);
if (!method) {
LOG(ERROR) << "HTTP method type is not accepatable.";
return false;
}
jbyteArray java_result = descriptor->Request(
env,
BufferToJByteArray(env, method, strlen(method)),
BufferToJByteArray(env, url.c_str(), url.length()),
BufferToJByteArray(env, post_data, post_size));
if (java_result == NULL) {
LOG(ERROR) << "Method invocation failed.";
return false;
}
if (output_string) {
AssignJByteArrayToString(env, java_result, output_string);
}
return true;
}
// static
void JavaHttpClientProxy::SetJavaVM(JavaVM *jvm) {
mozc::scoped_lock lock(&jvm_mutex);
http_client_descriptor.reset(JavaHttpClientDescriptor::Create(jvm));
}
} // namespace jni
} // namespace mozc