Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(binding/java): add list & remove_all support #3333

Merged
merged 23 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b141712
feat(binding/java): add Entry for list
G-XD Oct 16, 2023
a9bb79e
feat(binding/java): support list for blocking
G-XD Oct 16, 2023
d3036ae
feat(binding/java): support list & remove_all
G-XD Oct 16, 2023
fb960f5
test(binding/java): add list test
G-XD Oct 16, 2023
7662b9c
feat(binding/java): add OpList args
G-XD Oct 17, 2023
deae37d
feat(binding/java): support metakey args
G-XD Oct 17, 2023
b72ccc1
chore(binding/java): use Set for Metakey args
G-XD Oct 18, 2023
9893be8
chore(binding/java): fix code
G-XD Oct 18, 2023
b7e0d85
Merge branch 'main' into java_binding_list
G-XD Oct 18, 2023
9df26e9
chore(binding/java): fix code
G-XD Oct 18, 2023
dc6f0d3
chore(binding/java): use Data for OpList
G-XD Oct 18, 2023
6b45bb1
Merge branch 'main' into java_binding_list
G-XD Oct 18, 2023
e788c68
test(binding/java): add test for list
G-XD Oct 18, 2023
4507714
refactor(binding/java): update list api
G-XD Oct 19, 2023
e6fb207
ci(binding/java): update root path of fs test
G-XD Oct 19, 2023
145ff8d
refactor(binding/java): refactor Metakey name
G-XD Oct 20, 2023
00dd58c
refactor(binding/java): remove list with args support
G-XD Oct 23, 2023
30941bc
refactor(binding/java): align the pattern of convert list
G-XD Oct 23, 2023
8e0374c
refactor(binding/java): align the pattern of convert list
G-XD Oct 23, 2023
38301ce
Merge branch 'main' into java_binding_list
G-XD Oct 25, 2023
8c43cc1
refactor(binding/java): use Metadata::metakey() api for make entry
G-XD Oct 25, 2023
5872f4d
ci(binding/java): remove behavior tests
G-XD Oct 25, 2023
6718148
refactor(binding/java): update code style
G-XD Oct 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bindings/java/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ anyhow = "1.0.71"
jni = "0.21.1"
num_cpus = "1.15.0"
once_cell = "1.17.1"
flagset = "0.4"
tokio = { version = "1.28.1", features = ["full"] }
opendal = { workspace = true }

Expand Down
96 changes: 94 additions & 2 deletions bindings/java/src/blocking_operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ use jni::objects::JClass;
use jni::objects::JObject;
use jni::objects::JString;
use jni::sys::jobject;
use jni::sys::{jbyteArray, jlong};
use jni::sys::{jbyteArray, jintArray, jlong};
use jni::JNIEnv;

use opendal::BlockingOperator;
use opendal::Metakey;

use crate::jstring_to_option_string;
use crate::jstring_to_string;
use crate::make_entry;
use crate::make_metadata;
use crate::metakey_to_flagset;
use crate::Result;

/// # Safety
Expand Down Expand Up @@ -123,7 +127,7 @@ pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_stat(
fn intern_stat(env: &mut JNIEnv, op: &mut BlockingOperator, path: JString) -> Result<jobject> {
let path = jstring_to_string(env, &path)?;
let metadata = op.stat(&path)?;
Ok(make_metadata(env, metadata)?.into_raw())
Ok(make_metadata(env, metadata, Metakey::Complete.into())?.into_raw())
}

/// # Safety
Expand Down Expand Up @@ -221,3 +225,91 @@ fn intern_rename(

Ok(op.rename(&source_path, &target_path)?)
}

/// # Safety
///
/// This function should not be called before the Operator are ready.
#[no_mangle]
pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_removeAll(
mut env: JNIEnv,
_: JClass,
op: *mut BlockingOperator,
path: JString,
) {
intern_remove_all(&mut env, &mut *op, path).unwrap_or_else(|e| {
e.throw(&mut env);
})
}

fn intern_remove_all(env: &mut JNIEnv, op: &mut BlockingOperator, path: JString) -> Result<()> {
let path = jstring_to_string(env, &path)?;

Ok(op.remove_all(&path)?)
}

/// # Safety
///
/// This function should not be called before the Operator are ready.
#[no_mangle]
pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_listWith(
mut env: JNIEnv,
_: JClass,
op: *mut BlockingOperator,
path: JString,
limit: jlong,
start_after: JString,
delimiter: JString,
metakeys: jintArray,
) -> jobject {
intern_list_with(
&mut env,
&mut *op,
path,
limit,
start_after,
delimiter,
metakeys,
)
.unwrap_or_else(|e| {
e.throw(&mut env);
JObject::default().into_raw()
})
}

fn intern_list_with(
env: &mut JNIEnv,
op: &mut BlockingOperator,
path: JString,
limit: jlong,
start_after: JString,
delimiter: JString,
metakeys: jintArray,
) -> Result<jobject> {
let path = jstring_to_string(env, &path)?;
let mut op = op.list_with(&path);
if limit >= 0 {
op = op.limit(limit as usize);
}
if jstring_to_option_string(env, &start_after)?.is_some() {
op = op.start_after(jstring_to_string(env, &start_after)?.as_str());
}
if jstring_to_option_string(env, &delimiter)?.is_some() {
op = op.delimiter(jstring_to_string(env, &delimiter)?.as_str());
}

let metakey = metakey_to_flagset(env, metakeys)?;
if let Some(metakey) = metakey {
op = op.metakey(metakey);
}

let list = env.new_object("java/util/ArrayList", "()V", &[])?;
let jlist = env.get_list(&list)?;

let obs = op.call()?;
for entry in obs {
let entry = make_entry(env, entry, metakey.unwrap_or(Metakey::Mode.into()))?;
jlist.add(env, &entry)?;
}

Ok(list.into_raw())
}
143 changes: 125 additions & 18 deletions bindings/java/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,25 @@ use std::collections::HashMap;
use std::ffi::c_void;

use crate::error::Error;
use flagset::FlagSet;
use jni::objects::JObject;
use jni::objects::JPrimitiveArray;
use jni::objects::JString;
use jni::objects::{JMap, JValue};
use jni::sys::jboolean;
use jni::sys::jint;
use jni::sys::jintArray;
use jni::sys::jlong;
use jni::sys::JNI_VERSION_1_8;
use jni::JNIEnv;
use jni::JavaVM;
use once_cell::sync::OnceCell;
use opendal::raw::PresignedRequest;
use opendal::Capability;
use opendal::Entry;
use opendal::EntryMode;
use opendal::Metadata;
use opendal::Metakey;
use opendal::OperatorInfo;
use tokio::runtime::Builder;
use tokio::runtime::Runtime;
Expand Down Expand Up @@ -226,38 +231,81 @@ fn make_capability<'a>(env: &mut JNIEnv<'a>, cap: Capability) -> Result<JObject<
Ok(capability)
}

fn make_metadata<'a>(env: &mut JNIEnv<'a>, metadata: Metadata) -> Result<JObject<'a>> {
fn make_metadata<'a>(
env: &mut JNIEnv<'a>,
metadata: Metadata,
metakey: FlagSet<Metakey>,
) -> Result<JObject<'a>> {
let mode = match metadata.mode() {
EntryMode::FILE => 0,
EntryMode::DIR => 1,
EntryMode::Unknown => 2,
};

let last_modified = metadata.last_modified().map_or_else(
|| Ok::<JObject<'_>, Error>(JObject::null()),
|v| {
Ok(env.new_object(
"java/util/Date",
"(J)V",
&[JValue::Long(v.timestamp_millis())],
)?)
},
)?;
let last_modified =
if metakey.contains(Metakey::LastModified) || metakey.contains(Metakey::Complete) {
metadata.last_modified().map_or_else(
|| Ok::<JObject<'_>, Error>(JObject::null()),
|v| {
Ok(env.new_object(
"java/util/Date",
"(J)V",
&[JValue::Long(v.timestamp_millis())],
)?)
},
)?
} else {
JObject::null()
};

let cache_control = string_to_jstring(env, metadata.cache_control())?;
let content_disposition = string_to_jstring(env, metadata.content_disposition())?;
let content_md5 = string_to_jstring(env, metadata.content_md5())?;
let content_type = string_to_jstring(env, metadata.content_type())?;
let etag = string_to_jstring(env, metadata.etag())?;
let version = string_to_jstring(env, metadata.version())?;
let cache_control =
if metakey.contains(Metakey::CacheControl) || metakey.contains(Metakey::Complete) {
string_to_jstring(env, metadata.cache_control())?
} else {
JObject::null()
};
let content_disposition =
if metakey.contains(Metakey::ContentDisposition) || metakey.contains(Metakey::Complete) {
string_to_jstring(env, metadata.content_disposition())?
} else {
JObject::null()
};
let content_md5 =
if metakey.contains(Metakey::ContentMd5) || metakey.contains(Metakey::Complete) {
string_to_jstring(env, metadata.content_md5())?
} else {
JObject::null()
};
let content_type =
if metakey.contains(Metakey::ContentType) || metakey.contains(Metakey::Complete) {
string_to_jstring(env, metadata.content_type())?
} else {
JObject::null()
};
let etag = if metakey.contains(Metakey::Etag) || metakey.contains(Metakey::Complete) {
string_to_jstring(env, metadata.etag())?
} else {
JObject::null()
};
let version = if metakey.contains(Metakey::Version) || metakey.contains(Metakey::Complete) {
string_to_jstring(env, metadata.version())?
} else {
JObject::null()
};
let content_length =
if metakey.contains(Metakey::ContentLength) || metakey.contains(Metakey::Complete) {
metadata.content_length() as jlong
} else {
-1
};

let result = env
.new_object(
"org/apache/opendal/Metadata",
"(IJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Date;Ljava/lang/String;)V",
&[
JValue::Int(mode as jint),
JValue::Long(metadata.content_length() as jlong),
JValue::Long(content_length),
JValue::Object(&content_disposition),
JValue::Object(&content_md5),
JValue::Object(&content_type),
Expand All @@ -270,13 +318,36 @@ fn make_metadata<'a>(env: &mut JNIEnv<'a>, metadata: Metadata) -> Result<JObject
Ok(result)
}

fn make_entry<'a>(
env: &mut JNIEnv<'a>,
entry: Entry,
metakey: FlagSet<Metakey>,
) -> Result<JObject<'a>> {
let path = env.new_string(entry.path())?;
let metadata = make_metadata(env, entry.metadata().to_owned(), metakey)?;

Ok(env.new_object(
"org/apache/opendal/Entry",
"(Ljava/lang/String;Lorg/apache/opendal/Metadata;)V",
&[JValue::Object(&path), JValue::Object(&metadata)],
)?)
}

fn string_to_jstring<'a>(env: &mut JNIEnv<'a>, s: Option<&str>) -> Result<JObject<'a>> {
s.map_or_else(
|| Ok(JObject::null()),
|v| Ok(env.new_string(v.to_string())?.into()),
)
}

fn jstring_to_option_string(env: &mut JNIEnv, s: &JString) -> Result<Option<String>> {
if s.is_null() {
Ok(None)
} else {
Ok(Some(jstring_to_string(env, s)?))
}
}

/// # Safety
///
/// The caller must guarantee that the Object passed in is an instance
Expand All @@ -285,3 +356,39 @@ fn jstring_to_string(env: &mut JNIEnv, s: &JString) -> Result<String> {
let res = unsafe { env.get_string_unchecked(s)? };
Ok(res.into())
}

fn metakey_to_flagset(env: &mut JNIEnv, metakeys: jintArray) -> Result<Option<FlagSet<Metakey>>> {
G-XD marked this conversation as resolved.
Show resolved Hide resolved
if metakeys.is_null() {
return Ok(None);
}
let metakeys = unsafe { JPrimitiveArray::from_raw(metakeys) };
let len = env.get_array_length(&metakeys)?;
let mut buf: Vec<jint> = vec![0; len as usize];
env.get_int_array_region(metakeys, 0, &mut buf)?;

let mut metakey: Option<FlagSet<Metakey>> = None;
for key in buf {
let m = match key {
0 => Metakey::Complete,
1 => Metakey::Mode,
2 => Metakey::CacheControl,
3 => Metakey::ContentDisposition,
4 => Metakey::ContentLength,
5 => Metakey::ContentMd5,
6 => Metakey::ContentRange,
7 => Metakey::ContentType,
8 => Metakey::Etag,
9 => Metakey::LastModified,
10 => Metakey::Version,
_ => Err(opendal::Error::new(
opendal::ErrorKind::Unexpected,
"Invalid metakey",
))?,
};
metakey = match metakey {
None => Some(m.into()),
Some(metakey) => Some(metakey | m),
}
}
Ok(metakey)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@
package org.apache.opendal;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import org.apache.opendal.args.OpList;
import org.apache.opendal.args.OpList.Metakey;
import org.apache.opendal.args.OpList.OpListBuilder;

/**
* BlockingOperator represents an underneath OpenDAL operator that
Expand Down Expand Up @@ -92,6 +96,28 @@ public void rename(String sourcePath, String targetPath) {
rename(nativeHandle, sourcePath, targetPath);
}

public void removeAll(String path) {
removeAll(nativeHandle, path);
}

public List<Entry> list(String path) {
return listWith(path).build().call();
}

public OpListBuilder<List<Entry>> listWith(String path) {
return OpList.builder(path, this::internListWith);
}

private List<Entry> internListWith(OpList<List<Entry>> opList) {
G-XD marked this conversation as resolved.
Show resolved Hide resolved
return listWith(
nativeHandle,
opList.getPath(),
opList.getLimit(),
opList.getStartAfter().orElse(null),
opList.getDelimiter().orElse(null),
opList.getMetakeys().stream().mapToInt(Metakey::getId).toArray());
}

@Override
protected native void disposeInternal(long handle);

Expand All @@ -110,4 +136,9 @@ public void rename(String sourcePath, String targetPath) {
private static native long copy(long nativeHandle, String sourcePath, String targetPath);

private static native long rename(long nativeHandle, String sourcePath, String targetPath);

private static native void removeAll(long nativeHandle, String path);

private static native List<Entry> listWith(
long nativeHandle, String path, long limit, String startAfter, String delimiter, int... metakeys);
}
Loading
Loading