New JNI interface to better support cursor result sets

This commit is contained in:
Jeremy Lakeman 2013-01-22 15:26:40 +10:30
parent 03536301f8
commit 3a93fee8a5
5 changed files with 292 additions and 118 deletions

2
cli.c
View File

@ -99,6 +99,7 @@ int cli_arg(int argc, const char *const *argv, const struct command_line_option
int arglen = strlen(argname);
int i;
const char *word;
*dst = defaultvalue;
for(i = 0; (word = o->words[i]); ++i) {
int wordlen = strlen(word);
/* No need to check that the "<...>" and "[<...>]" are all intact in the command_line_option,
@ -118,7 +119,6 @@ int cli_arg(int argc, const char *const *argv, const struct command_line_option
never happen, but it can because more than one version of a command line option may exist, one
with a given argument and another without, and allowing a default value means we can have a
single function handle both in a fairly simple manner. */
*dst = defaultvalue;
return 1;
}

View File

@ -61,8 +61,9 @@ int commandline_usage(int argc, const char *const *argv, const struct command_li
JNIEnv *jni_env = NULL;
int jni_exception = 0;
jobject outv_list = NULL;
jmethodID listAddMethodId = NULL;
jobject jniResults = NULL;
jclass IJniResults = NULL;
jmethodID startResultSet, setColumnName, putString, putBlob, putLong, putDouble, totalRowCount;
char *outv_buffer = NULL;
char *outv_current = NULL;
@ -84,27 +85,43 @@ static int outv_growbuf(size_t needed)
return 0;
}
static int put_blob(jbyte *value, jsize length){
jbyteArray arr = NULL;
if (value && length>0){
arr = (*jni_env)->NewByteArray(jni_env, length);
if (arr == NULL || (*jni_env)->ExceptionOccurred(jni_env)) {
jni_exception = 1;
return WHY("Exception thrown from NewByteArray()");
}
(*jni_env)->SetByteArrayRegion(jni_env, arr, 0, length, value);
if ((*jni_env)->ExceptionOccurred(jni_env)) {
jni_exception = 1;
return WHYF("Exception thrown from SetByteArrayRegion()");
}
}
(*jni_env)->CallVoidMethod(jni_env, jniResults, putBlob, arr);
if ((*jni_env)->ExceptionOccurred(jni_env)) {
jni_exception = 1;
return WHY("Exception thrown from CallVoidMethod()");
}
if (arr)
(*jni_env)->DeleteLocalRef(jni_env, arr);
return 0;
}
static int outv_end_field()
{
size_t length = outv_current - outv_buffer;
jsize length = outv_current - outv_buffer;
outv_current = outv_buffer;
jbyteArray arr = (*jni_env)->NewByteArray(jni_env, length);
if (arr == NULL) {
jni_exception = 1;
return WHY("Exception thrown from NewByteArray()");
}
(*jni_env)->SetByteArrayRegion(jni_env, arr, 0, length, (jbyte*)outv_buffer);
if ((*jni_env)->ExceptionOccurred(jni_env)) {
jni_exception = 1;
return WHY("Exception thrown from SetByteArrayRegion()");
}
(*jni_env)->CallBooleanMethod(jni_env, outv_list, listAddMethodId, arr);
if ((*jni_env)->ExceptionOccurred(jni_env)) {
jni_exception = 1;
return WHY("Exception thrown from CallBooleanMethod()");
}
(*jni_env)->DeleteLocalRef(jni_env, arr);
return 0;
return put_blob((jbyte *)outv_buffer, length);
}
int Throw(JNIEnv *env, const char *class, const char *msg){
jclass exceptionClass = NULL;
if ((exceptionClass = (*env)->FindClass(env, class)) == NULL)
return -1; // exception
(*env)->ThrowNew(env, exceptionClass, msg);
return -1;
}
/* JNI entry point to command line. See org.servalproject.servald.ServalD class for the Java side.
@ -112,70 +129,84 @@ static int outv_end_field()
*/
JNIEXPORT jint JNICALL Java_org_servalproject_servald_ServalD_rawCommand(JNIEnv *env, jobject this, jobject outv, jobjectArray args)
{
jclass stringClass = NULL;
jclass listClass = NULL;
unsigned char status = 0; // to match what the shell gets: 0..255
// Enforce non re-entrancy.
if (jni_env) {
jclass exceptionClass = NULL;
if ((exceptionClass = (*env)->FindClass(env, "java/lang/IllegalStateException")) == NULL)
return -1; // exception
(*env)->ThrowNew(env, exceptionClass, "re-entrancy not supported");
return -1;
if (!IJniResults){
IJniResults = (*env)->FindClass(env, "org/servalproject/servald/IJniResults");
if (IJniResults==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate class org.servalproject.servald.IJniResults");
startResultSet = (*env)->GetMethodID(env, IJniResults, "startResultSet", "(I)V");
if (startResultSet==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method startResultSet");
setColumnName = (*env)->GetMethodID(env, IJniResults, "setColumnName", "(ILjava/lang/String;)V");
if (setColumnName==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method setColumnName");
putString = (*env)->GetMethodID(env, IJniResults, "putString", "(Ljava/lang/String;)V");
if (putString==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method putString");
putBlob = (*env)->GetMethodID(env, IJniResults, "putBlob", "([B)V");
if (putBlob==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method putBlob");
putLong = (*env)->GetMethodID(env, IJniResults, "putLong", "(J)V");
if (putLong==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method putLong");
putDouble = (*env)->GetMethodID(env, IJniResults, "putDouble", "(D)V");
if (putDouble==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method putDouble");
totalRowCount = (*env)->GetMethodID(env, IJniResults, "totalRowCount", "(I)V");
if (totalRowCount==NULL)
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method totalRowCount");
}
// Get some handles to some classes and methods that we use later on.
if ((stringClass = (*env)->FindClass(env, "java/lang/String")) == NULL)
return -1; // exception
if ((listClass = (*env)->FindClass(env, "java/util/List")) == NULL)
return -1; // exception
if ((listAddMethodId = (*env)->GetMethodID(env, listClass, "add", "(Ljava/lang/Object;)Z")) == NULL)
return -1; // exception
unsigned char status = 0; // to match what the shell gets: 0..255
if (jni_env)
return Throw(env, "java/lang/IllegalStateException", "re-entrancy not supported");
// Construct argv, argc from this method's arguments.
jsize len = (*env)->GetArrayLength(env, args);
const char **argv = malloc(sizeof(char*) * (len + 1));
if (argv == NULL) {
jclass exceptionClass = NULL;
if ((exceptionClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError")) == NULL)
return -1; // exception
(*env)->ThrowNew(env, exceptionClass, "malloc returned NULL");
return -1;
}
const char **argv = alloca(sizeof(char*) * (len + 1));
if (argv == NULL)
return Throw(env, "java/lang/OutOfMemoryError", "alloca returned NULL");
argv[len]=NULL;
jsize i;
for (i = 0; i <= len; ++i)
argv[i] = NULL;
int argc = len;
// From now on, in case of an exception we have to free some resources before
// returning.
jni_exception = 0;
for (i = 0; !jni_exception && i != len; ++i) {
const char *empty="";
for (i = 0; i < len; ++i) {
const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i);
if (arg == NULL)
if ((*env)->ExceptionOccurred(env))
jni_exception = 1;
else {
argv[i] = empty;
if (arg != NULL && !jni_exception){
const char *str = (*env)->GetStringUTFChars(env, arg, NULL);
if (str == NULL)
if (str == NULL){
jni_exception = 1;
else
}else
argv[i] = str;
}
}
if (!jni_exception) {
// Set up the output buffer.
outv_list = outv;
jniResults = outv;
outv_current = outv_buffer;
// Execute the command.
jni_env = env;
status = parseCommandLine(NULL, argc, argv);
jni_env = NULL;
}
// Release argv Java string buffers.
for (i = 0; i != len; ++i) {
if (argv[i]) {
for (i = 0; i < len; ++i) {
if (argv[i] && argv[i]!=empty) {
const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i);
(*env)->ReleaseStringUTFChars(env, arg, argv[i]);
}
}
free(argv);
// Deal with Java exceptions: NewStringUTF out of memory in outv_end_field().
if (jni_exception || (outv_current != outv_buffer && outv_end_field() == -1))
return -1;
@ -225,15 +256,14 @@ int parseCommandLine(const char *argv0, int argc, const char *const *args)
int cli_putchar(char c)
{
#ifdef HAVE_JNI_H
if (jni_env) {
if (outv_current == outv_limit && outv_growbuf(1) == -1)
return EOF;
*outv_current++ = c;
return (unsigned char) c;
}
else
if (jni_env) {
if (outv_current == outv_limit && outv_growbuf(1) == -1)
return EOF;
*outv_current++ = c;
return (unsigned char) c;
}
#endif
return putchar(c);
return putchar(c);
}
/* Write a buffer of data to output. If in a JNI call, then this appends the data to the
@ -243,23 +273,22 @@ int cli_putchar(char c)
int cli_write(const unsigned char *buf, size_t len)
{
#ifdef HAVE_JNI_H
if (jni_env) {
size_t avail = outv_limit - outv_current;
if (avail < len) {
memcpy(outv_current, buf, avail);
outv_current = outv_limit;
if (outv_growbuf(len) == -1)
return EOF;
len -= avail;
buf += avail;
}
memcpy(outv_current, buf, len);
outv_current += len;
return 0;
if (jni_env) {
size_t avail = outv_limit - outv_current;
if (avail < len) {
memcpy(outv_current, buf, avail);
outv_current = outv_limit;
if (outv_growbuf(len) == -1)
return EOF;
len -= avail;
buf += avail;
}
else
memcpy(outv_current, buf, len);
outv_current += len;
return 0;
}
#endif
return fwrite(buf, len, 1, stdout);
return fwrite(buf, len, 1, stdout);
}
/* Write a null-terminated string to output. If in a JNI call, then this appends the string to the
@ -309,6 +338,117 @@ int cli_printf(const char *fmt, ...)
return ret;
}
void cli_columns(int columns, const char *names[]){
#ifdef HAVE_JNI_H
if (jni_env) {
(*jni_env)->CallVoidMethod(jni_env, jniResults, startResultSet, columns);
if ((*jni_env)->ExceptionOccurred(jni_env)) {
jni_exception = 1;
WHY("Exception thrown from CallVoidMethod()");
return;
}
int i;
for (i=0;i<columns;i++){
jstring str = (jstring)(*jni_env)->NewStringUTF(jni_env, names[i]);
if (str == NULL) {
jni_exception = 1;
WHY("Exception thrown from NewStringUTF()");
return;
}
(*jni_env)->CallVoidMethod(jni_env, jniResults, setColumnName, i, str);
(*jni_env)->DeleteLocalRef(jni_env, str);
}
return;
}
#endif
cli_printf("%d",columns);
cli_delim("\n");
int i;
for (i=0;i<columns;i++){
cli_puts(names[i]);
if (i+1==columns)
cli_delim("\n");
else
cli_delim(":");
}
}
void cli_field_name(const char *name, const char *delim){
#ifdef HAVE_JNI_H
if (jni_env) {
jstring str = (jstring)(*jni_env)->NewStringUTF(jni_env, name);
if (str == NULL) {
jni_exception = 1;
WHY("Exception thrown from NewStringUTF()");
return;
}
(*jni_env)->CallVoidMethod(jni_env, jniResults, setColumnName, -1, str);
(*jni_env)->DeleteLocalRef(jni_env, str);
return;
}
#endif
cli_puts(name);
cli_delim(delim);
}
void cli_put_long(int64_t value, const char *delim){
#ifdef HAVE_JNI_H
if (jni_env) {
(*jni_env)->CallVoidMethod(jni_env, jniResults, putLong, value);
return;
}
#endif
cli_printf("%lld",value);
cli_delim(delim);
}
void cli_put_string(const char *value, const char *delim){
#ifdef HAVE_JNI_H
if (jni_env) {
jstring str = NULL;
if (value){
str = (jstring)(*jni_env)->NewStringUTF(jni_env, value);
if (str == NULL) {
jni_exception = 1;
WHY("Exception thrown from NewStringUTF()");
return;
}
}
(*jni_env)->CallVoidMethod(jni_env, jniResults, putString, str);
(*jni_env)->DeleteLocalRef(jni_env, str);
return;
}
#endif
if (value)
cli_puts(value);
cli_delim(delim);
}
void cli_put_hexvalue(const unsigned char *value, int length, const char *delim){
#ifdef HAVE_JNI_H
if (jni_env) {
put_blob((jbyte*)value, length);
return;
}
#endif
if (value)
cli_puts(alloca_tohex(value, length));
cli_delim(delim);
}
void cli_row_count(int rows){
#ifdef HAVE_JNI_H
if (jni_env) {
(*jni_env)->CallVoidMethod(jni_env, jniResults, totalRowCount, rows);
if ((*jni_env)->ExceptionOccurred(jni_env)) {
jni_exception = 1;
WHY("Exception thrown from CallVoidMethod()");
return;
}
}
#endif
}
/* Delimit the current output field. This closes the current field, so that the next cli_ output
function will start appending to a new field. Returns 0 on success, -1 on error. If not in a
JNI call, then this simply writes a newline to standard output (or the value of the
@ -1397,7 +1537,7 @@ int app_rhizome_list(int argc, const char *const *argv, const struct command_lin
return -1;
if (rhizome_opendb() == -1)
return -1;
return rhizome_list_manifests(service, name, sender_sid, recipient_sid, atoi(offset), atoi(limit));
return rhizome_list_manifests(service, name, sender_sid, recipient_sid, atoi(offset), atoi(limit), 0);
}
int app_keyring_create(int argc, const char *const *argv, const struct command_line_option *o, void *context)

View File

@ -310,7 +310,7 @@ long long rhizome_bar_version(unsigned char *bar);
unsigned long long rhizome_bar_bidprefix_ll(unsigned char *bar);
int rhizome_list_manifests(const char *service, const char *name,
const char *sender_sid, const char *recipient_sid,
int limit, int offset);
int limit, int offset, char count_rows);
int rhizome_retrieve_manifest(const char *manifestid, rhizome_manifest *m);
#define RHIZOME_DONTVERIFY 0

View File

@ -930,11 +930,11 @@ rollback:
int rhizome_list_manifests(const char *service, const char *name,
const char *sender_sid, const char *recipient_sid,
int limit, int offset)
int limit, int offset, char count_rows)
{
IN();
strbuf b = strbuf_alloca(1024);
strbuf_sprintf(b, "SELECT id, manifest, version, inserttime, author FROM manifests WHERE 1=1");
strbuf_sprintf(b, "SELECT id, manifest, version, inserttime, author, rowid FROM manifests WHERE 1=1");
if (service && *service)
strbuf_sprintf(b, " AND service = ?1");
@ -947,8 +947,6 @@ int rhizome_list_manifests(const char *service, const char *name,
strbuf_sprintf(b, " ORDER BY inserttime DESC");
if (limit)
strbuf_sprintf(b, " LIMIT %u", limit);
if (offset)
strbuf_sprintf(b, " OFFSET %u", offset);
@ -977,24 +975,30 @@ int rhizome_list_manifests(const char *service, const char *name,
}
ret=0;
size_t rows = 0;
cli_puts("12"); cli_delim("\n"); // number of columns
cli_puts("service"); cli_delim(":");
cli_puts("id"); cli_delim(":");
cli_puts("version"); cli_delim(":");
cli_puts("date"); cli_delim(":");
cli_puts(".inserttime"); cli_delim(":");
cli_puts(".author"); cli_delim(":");
cli_puts(".fromhere"); cli_delim(":");
cli_puts("filesize"); cli_delim(":");
cli_puts("filehash"); cli_delim(":");
cli_puts("sender"); cli_delim(":");
cli_puts("recipient"); cli_delim(":");
cli_puts("name"); cli_delim("\n"); // should be last, because name may contain ':'
const char *names[]={
"_id",
"service",
"id",
"version",
"date",
".inserttime",
".author",
".fromhere",
"filesize",
"filehash",
"sender",
"recipient",
"name"
};
cli_columns(13,names);
while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) {
++rows;
if (!( sqlite3_column_count(statement) == 5
if (limit>0 && rows>limit)
break;
if (!( sqlite3_column_count(statement) == 6
&& sqlite3_column_type(statement, 0) == SQLITE_TEXT
&& sqlite3_column_type(statement, 1) == SQLITE_BLOB
&& sqlite3_column_type(statement, 2) == SQLITE_INTEGER
@ -1017,6 +1021,8 @@ int rhizome_list_manifests(const char *service, const char *name,
long long q_version = sqlite3_column_int64(statement, 2);
long long q_inserttime = sqlite3_column_int64(statement, 3);
const char *q_author = (const char *) sqlite3_column_text(statement, 4);
long long rowid = sqlite3_column_int64(statement, 5);
if (rhizome_read_manifest_file(m, manifestblob, manifestblobsize) == -1) {
WARNF("MANIFESTS row id=%s has invalid manifest blob -- skipped", q_manifestid);
} else {
@ -1024,6 +1030,7 @@ int rhizome_list_manifests(const char *service, const char *name,
if (blob_version != q_version)
WARNF("MANIFESTS row id=%s version=%lld does not match manifest blob.version=%lld", q_manifestid, q_version, blob_version);
int match = 1;
const char *blob_service = rhizome_manifest_get(m, "service", NULL, 0);
if (service[0] && !(blob_service && strcasecmp(service, blob_service) == 0))
match = 0;
@ -1037,43 +1044,64 @@ int rhizome_list_manifests(const char *service, const char *name,
if (!(blob_recipient && strcasecmp(recipient_sid, blob_recipient) == 0))
match = 0;
}
if (match) {
const char *blob_name = rhizome_manifest_get(m, "name", NULL, 0);
long long blob_date = rhizome_manifest_get_ll(m, "date");
const char *blob_filehash = rhizome_manifest_get(m, "filehash", NULL, 0);
long long blob_filesize = rhizome_manifest_get_ll(m, "filesize");
int from_here = 0;
unsigned char senderSid[SID_SIZE];
unsigned char recipientSid[SID_SIZE];
if (blob_sender)
stowSid(senderSid, 0, blob_sender);
if (blob_recipient)
stowSid(recipientSid, 0, blob_recipient);
if (q_author) {
if (config.debug.rhizome) DEBUGF("q_author=%s", alloca_str_toprint(q_author));
unsigned char authorSid[SID_SIZE];
stowSid(authorSid, 0, q_author);
stowSid(m->author, 0, q_author);
int cn = 0, in = 0, kp = 0;
from_here = keyring_find_sid(keyring, &cn, &in, &kp, authorSid);
from_here = keyring_find_sid(keyring, &cn, &in, &kp, m->author);
}
if (!from_here && blob_sender) {
if (config.debug.rhizome) DEBUGF("blob_sender=%s", alloca_str_toprint(blob_sender));
unsigned char senderSid[SID_SIZE];
stowSid(senderSid, 0, blob_sender);
int cn = 0, in = 0, kp = 0;
from_here = keyring_find_sid(keyring, &cn, &in, &kp, senderSid);
}
if (config.debug.rhizome) DEBUGF("manifest payload size = %lld", blob_filesize);
cli_puts(blob_service ? blob_service : ""); cli_delim(":");
cli_puts(q_manifestid); cli_delim(":");
cli_printf("%lld", blob_version); cli_delim(":");
cli_printf("%lld", blob_date); cli_delim(":");
cli_printf("%lld", q_inserttime); cli_delim(":");
cli_puts(q_author ? q_author : ""); cli_delim(":");
cli_printf("%d", from_here); cli_delim(":");
cli_printf("%lld", blob_filesize); cli_delim(":");
cli_puts(blob_filehash ? blob_filehash : ""); cli_delim(":");
cli_puts(blob_sender ? blob_sender : ""); cli_delim(":");
cli_puts(blob_recipient ? blob_recipient : ""); cli_delim(":");
cli_puts(blob_name ? blob_name : ""); cli_delim("\n");
cli_put_long(rowid, ":");
cli_put_string(blob_service, ":");
cli_put_hexvalue(m->cryptoSignPublic, RHIZOME_MANIFEST_ID_BYTES, ":");
cli_put_long(blob_version, ":");
cli_put_long(blob_date, ":");
cli_put_long(q_inserttime, ":");
cli_put_hexvalue(q_author?m->author:NULL, SID_SIZE, ":");
cli_put_long(from_here, ":");
cli_put_long(m->fileLength, ":");
unsigned char filehash[SHA512_DIGEST_LENGTH];
if (m->fileLength)
fromhex(filehash, blob_filehash, SHA512_DIGEST_LENGTH);
cli_put_hexvalue(m->fileLength?filehash:NULL, SHA512_DIGEST_LENGTH, ":");
cli_put_hexvalue(blob_sender?senderSid:NULL, SID_SIZE, ":");
cli_put_hexvalue(blob_recipient?recipientSid:NULL, SID_SIZE, ":");
cli_put_string(blob_name, "\n");
}
}
if (m) rhizome_manifest_free(m);
}
if (ret==0 && count_rows){
while (sqlite_step_retry(&retry, statement) == SQLITE_ROW)
++rows;
}
cli_row_count(rows);
cleanup:
sqlite3_finalize(statement);
RETURN(ret);

View File

@ -630,6 +630,12 @@ int cli_putchar(char c);
int cli_puts(const char *str);
int cli_printf(const char *fmt, ...);
int cli_delim(const char *opt);
void cli_columns(int columns, const char *names[]);
void cli_row_count(int rows);
void cli_field_name(const char *name, const char *delim);
void cli_put_long(int64_t value, const char *delim);
void cli_put_string(const char *value, const char *delim);
void cli_put_hexvalue(const unsigned char *value, int length, const char *delim);
int overlay_mdp_getmyaddr(int index,unsigned char *sid);
int overlay_mdp_bind(unsigned char *localaddr,int port);