blob: c35d75edae9596d71a4b8ac674a10aa4efd091f0 [file] [log] [blame]
// +build android
package ipc
import (
"encoding/json"
"fmt"
"runtime"
"veyron/jni/runtimes/google/util"
"veyron2/ipc"
"veyron2/security"
"veyron2/verror"
)
// #cgo LDFLAGS: -llog -ljniwrapper
// #include "jni_wrapper.h"
import "C"
func newInvoker(env *C.JNIEnv, jVM *C.JavaVM, jObj C.jobject) (*invoker, error) {
// Create a new Java VDLInvoker object.
tempJInvoker, err := util.NewObject(env, jVDLInvokerClass, []util.Sign{util.ObjectSign}, jObj)
jInvoker := C.jobject(tempJInvoker)
if err != nil {
return nil, fmt.Errorf("error creating Java VDLInvoker object: %v", err)
}
// Fetch the argGetter for the object.
jPathArray := C.jobjectArray(util.CallObjectMethodOrCatch(env, jInvoker, "getImplementedServices", nil, util.ArraySign(util.StringSign)))
paths := util.GoStringArray(env, jPathArray)
getter, err := newArgGetter(paths)
if err != nil {
return nil, err
}
// Reference Java invoker; it will be de-referenced when the go invoker
// created below is garbage-collected (through the finalizer callback we
// setup just below).
jInvoker = C.NewGlobalRef(env, jInvoker)
i := &invoker{
jVM: jVM,
jInvoker: jInvoker,
argGetter: getter,
}
runtime.SetFinalizer(i, func(i *invoker) {
envPtr, freeFunc := util.GetEnv(i.jVM)
env := (*C.JNIEnv)(envPtr)
defer freeFunc()
C.DeleteGlobalRef(env, i.jInvoker)
})
return i, nil
}
type invoker struct {
jVM *C.JavaVM
jInvoker C.jobject
argGetter *argGetter
}
func (i *invoker) Prepare(method string, numArgs int) (argptrs []interface{}, label security.Label, err error) {
// NOTE(spetrovic): In the long-term, this method will return an array of
// []vom.Value. This will in turn result in VOM decoding all input
// arguments into vom.Value objects, which we shall then de-serialize into
// Java objects (see Invoke comments below). This approach is blocked on
// pending VOM encoder/decoder changes as well as Java (de)serializer.
envPtr, freeFunc := util.GetEnv(i.jVM)
env := (*C.JNIEnv)(envPtr)
defer freeFunc()
mArgs := i.argGetter.FindMethod(method, numArgs)
if mArgs == nil {
err = fmt.Errorf("couldn't find VDL method %q with %d args", method, numArgs)
return
}
argptrs = mArgs.InPtrs()
// Get the security label.
labelSign := util.ClassSign("com.veyron2.security.Label")
jLabel, err := util.CallObjectMethod(env, i.jInvoker, "getSecurityLabel", []util.Sign{util.StringSign}, labelSign, util.CamelCase(method))
if err != nil {
return nil, security.Label(0), err
}
label = security.Label(util.JIntField(env, jLabel, "value"))
return
}
func (i *invoker) Invoke(method string, call ipc.ServerCall, argptrs []interface{}) (results []interface{}, err error) {
// NOTE(spetrovic): In the long-term, all input arguments will be of
// vom.Value type (see comments for Prepare() method above). Through JNI,
// we will call Java functions that transform a serialized vom.Value into
// Java objects. We will then pass those Java objects to Java's Invoke
// method. The returned Java objects will be converted into serialized
// vom.Values, which will then be returned. This approach is blocked on VOM
// encoder/decoder changes as well as Java's (de)serializer.
envPtr, freeFunc := util.GetEnv(i.jVM)
env := (*C.JNIEnv)(envPtr)
defer freeFunc()
// Create a new Java server call instance.
mArgs := i.argGetter.FindMethod(method, len(argptrs))
if mArgs == nil {
err = fmt.Errorf("couldn't find VDL method %q with %d args", method, len(argptrs))
}
sCall := newServerCall(call, mArgs)
jServerCall := C.jobject(util.NewObjectOrCatch(env, jServerCallClass, []util.Sign{util.LongSign}, sCall))
util.GoRef(sCall) // unref-ed when jServerCall is garbage-collected
// Translate input args to JSON.
jArgs, err := i.encodeArgs(env, argptrs)
if err != nil {
return
}
// Invoke the method.
callSign := util.ClassSign("com.veyron2.ipc.ServerCall")
replySign := util.ClassSign("com.veyron.runtimes.google.VDLInvoker$InvokeReply")
jReply, err := util.CallObjectMethod(env, i.jInvoker, "invoke", []util.Sign{util.StringSign, callSign, util.ArraySign(util.StringSign)}, replySign, util.CamelCase(method), jServerCall, jArgs)
if err != nil {
return nil, fmt.Errorf("error invoking Java method %q: %v", method, err)
}
// Decode and return results.
return i.decodeResults(env, method, len(argptrs), C.jobject(jReply))
}
// encodeArgs JSON-encodes the provided argument pointers, converts them into
// Java strings, and returns a Java string array response.
func (*invoker) encodeArgs(env *C.JNIEnv, argptrs []interface{}) (C.jobjectArray, error) {
// JSON encode.
jsonArgs := make([][]byte, len(argptrs))
for i, argptr := range argptrs {
// Remove the pointer from the argument. Simply *argptr doesn't work
// as argptr is of type interface{}.
arg := util.DerefOrDie(argptr)
var err error
jsonArgs[i], err = json.Marshal(arg)
if err != nil {
return nil, fmt.Errorf("error marshalling %q into JSON", arg)
}
}
// Convert to Java array of C.jstring.
ret := C.NewObjectArray(env, C.jsize(len(argptrs)), jStringClass, nil)
for i, arg := range jsonArgs {
C.SetObjectArrayElement(env, ret, C.jsize(i), C.jobject(util.JStringPtr(env, string(arg))))
}
return ret, nil
}
// decodeResults JSON-decodes replies stored in the Java reply object and
// returns an array of Go reply objects.
func (i *invoker) decodeResults(env *C.JNIEnv, method string, numArgs int, jReply C.jobject) ([]interface{}, error) {
// Unpack the replies.
results := util.JStringArrayField(env, jReply, "results")
hasAppErr := util.JBoolField(env, jReply, "hasApplicationError")
errorID := util.JStringField(env, jReply, "errorID")
errorMsg := util.JStringField(env, jReply, "errorMsg")
// Get result instances.
mArgs := i.argGetter.FindMethod(method, numArgs)
if mArgs == nil {
return nil, fmt.Errorf("couldn't find method %q with %d input args: %v", method, numArgs)
}
argptrs := mArgs.OutPtrs()
// Check for app error.
if hasAppErr {
return resultsWithError(argptrs, verror.Make(verror.ID(errorID), errorMsg)), nil
}
// JSON-decode.
if len(results) != len(argptrs) {
return nil, fmt.Errorf("mismatch in number of output arguments, have: %d want: %d", len(results), len(argptrs))
}
for i, result := range results {
if err := json.Unmarshal([]byte(result), argptrs[i]); err != nil {
return nil, err
}
}
return resultsWithError(argptrs, nil), nil
}
// resultsWithError dereferences the provided result pointers and appends the
// given error to the returned array.
func resultsWithError(resultptrs []interface{}, err error) []interface{} {
ret := make([]interface{}, len(resultptrs)+1)
for i, resultptr := range resultptrs {
ret[i] = util.DerefOrDie(resultptr)
}
ret[len(resultptrs)] = err
return ret
}