Codebase list mozc / upstream/latest base / android_jni_proxy.cc
upstream/latest

Tree @upstream/latest (Download .tar.gz)

android_jni_proxy.cc @upstream/latestraw · history · blame

// Copyright 2010-2014, 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