blob: 80296680829ed96f716006a494cad3638417274f [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use std::collections::HashMap;
use jni::JNIEnv;
use jni::objects::JObject;
use jni::objects::JValue;
use jni::sys::jboolean;
use jni::sys::jint;
use jni::sys::jlong;
use opendal::Entry;
use opendal::EntryMode;
use opendal::Metadata;
use opendal::OperatorInfo;
use opendal::raw::PresignedRequest;
use opendal::{Capability, Error, ErrorKind};
mod async_operator;
mod convert;
mod error;
mod executor;
mod layer;
mod operator;
mod operator_input_stream;
mod operator_output_stream;
mod utility;
pub(crate) type Result<T> = std::result::Result<T, error::Error>;
fn make_presigned_request<'a>(env: &mut JNIEnv<'a>, req: PresignedRequest) -> Result<JObject<'a>> {
let method = env.new_string(req.method().as_str())?;
let uri = env.new_string(req.uri().to_string())?;
let headers = {
let mut map = HashMap::new();
for (k, v) in req.header().iter() {
let key = k.to_string();
let value = v.to_str().map_err(|err| {
opendal::Error::new(opendal::ErrorKind::Unexpected, err.to_string())
})?;
map.insert(key, value.to_owned());
}
map
};
let headers = convert::hashmap_to_jmap(env, &headers)?;
let result = env.new_object(
"org/apache/opendal/PresignedRequest",
"(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V",
&[
JValue::Object(&method),
JValue::Object(&uri),
JValue::Object(&headers),
],
)?;
Ok(result)
}
fn make_operator_info<'a>(env: &mut JNIEnv<'a>, info: OperatorInfo) -> Result<JObject<'a>> {
let scheme = env.new_string(info.scheme().to_string())?;
let root = env.new_string(info.root().to_string())?;
let name = env.new_string(info.name().to_string())?;
let full_capability_obj = make_capability(env, info.full_capability())?;
let native_capability_obj = make_capability(env, info.native_capability())?;
let result = env
.new_object(
"org/apache/opendal/OperatorInfo",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/apache/opendal/Capability;Lorg/apache/opendal/Capability;)V",
&[
JValue::Object(&scheme),
JValue::Object(&root),
JValue::Object(&name),
JValue::Object(&full_capability_obj),
JValue::Object(&native_capability_obj),
],
)?;
Ok(result)
}
fn make_capability<'a>(env: &mut JNIEnv<'a>, cap: Capability) -> Result<JObject<'a>> {
let capability = env.new_object(
"org/apache/opendal/Capability",
"(ZZZZZZZZZZZZZZZZZZZZZZJJZZZZZZZZZZZZZZZ)V",
&[
JValue::Bool(cap.stat as jboolean),
JValue::Bool(cap.stat_with_if_match as jboolean),
JValue::Bool(cap.stat_with_if_none_match as jboolean),
JValue::Bool(cap.stat_with_if_modified_since as jboolean),
JValue::Bool(cap.stat_with_if_unmodified_since as jboolean),
JValue::Bool(cap.stat_with_version as jboolean),
JValue::Bool(cap.read as jboolean),
JValue::Bool(cap.read_with_if_match as jboolean),
JValue::Bool(cap.read_with_if_none_match as jboolean),
JValue::Bool(cap.read_with_override_cache_control as jboolean),
JValue::Bool(cap.read_with_override_content_disposition as jboolean),
JValue::Bool(cap.read_with_override_content_type as jboolean),
JValue::Bool(cap.write as jboolean),
JValue::Bool(cap.write_can_multi as jboolean),
JValue::Bool(cap.write_can_append as jboolean),
JValue::Bool(cap.write_with_content_type as jboolean),
JValue::Bool(cap.write_with_content_disposition as jboolean),
JValue::Bool(cap.write_with_cache_control as jboolean),
JValue::Bool(cap.write_with_if_match as jboolean),
JValue::Bool(cap.write_with_if_none_match as jboolean),
JValue::Bool(cap.write_with_if_not_exists as jboolean),
JValue::Bool(cap.write_with_user_metadata as jboolean),
JValue::Long(convert::usize_to_jlong(cap.write_multi_max_size)),
JValue::Long(convert::usize_to_jlong(cap.write_multi_min_size)),
JValue::Bool(cap.create_dir as jboolean),
JValue::Bool(cap.delete as jboolean),
JValue::Bool(cap.copy as jboolean),
JValue::Bool(cap.rename as jboolean),
JValue::Bool(cap.list as jboolean),
JValue::Bool(cap.list_with_limit as jboolean),
JValue::Bool(cap.list_with_start_after as jboolean),
JValue::Bool(cap.list_with_recursive as jboolean),
JValue::Bool(cap.list_with_versions as jboolean),
JValue::Bool(cap.list_with_deleted as jboolean),
JValue::Bool(cap.presign as jboolean),
JValue::Bool(cap.presign_read as jboolean),
JValue::Bool(cap.presign_stat as jboolean),
JValue::Bool(cap.presign_write as jboolean),
JValue::Bool(cap.shared as jboolean),
],
)?;
Ok(capability)
}
fn make_metadata<'a>(env: &mut JNIEnv<'a>, metadata: Metadata) -> 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::Error>(JObject::null()),
|v| {
Ok(env
.call_static_method(
"java/time/Instant",
"ofEpochSecond",
"(JJ)Ljava/time/Instant;",
&[
JValue::Long(v.into_inner().as_second()),
JValue::Long(v.into_inner().subsec_nanosecond() as jlong),
],
)?
.l()?)
},
)?;
let cache_control = convert::string_to_jstring(env, metadata.cache_control())?;
let content_disposition = convert::string_to_jstring(env, metadata.content_disposition())?;
let content_md5 = convert::string_to_jstring(env, metadata.content_md5())?;
let content_type = convert::string_to_jstring(env, metadata.content_type())?;
let etag = convert::string_to_jstring(env, metadata.etag())?;
let version = convert::string_to_jstring(env, metadata.version())?;
let content_length = metadata.content_length() as jlong;
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/time/Instant;Ljava/lang/String;)V",
&[
JValue::Int(mode as jint),
JValue::Long(content_length),
JValue::Object(&content_disposition),
JValue::Object(&content_md5),
JValue::Object(&content_type),
JValue::Object(&cache_control),
JValue::Object(&etag),
JValue::Object(&last_modified),
JValue::Object(&version),
],
)?;
Ok(result)
}
fn make_entry<'a>(env: &mut JNIEnv<'a>, entry: Entry) -> Result<JObject<'a>> {
let path = env.new_string(entry.path())?;
let metadata = make_metadata(env, entry.metadata().to_owned())?;
Ok(env.new_object(
"org/apache/opendal/Entry",
"(Ljava/lang/String;Lorg/apache/opendal/Metadata;)V",
&[JValue::Object(&path), JValue::Object(&metadata)],
)?)
}
fn make_write_options<'a>(
env: &mut JNIEnv<'a>,
options: &JObject,
) -> Result<opendal::options::WriteOptions> {
let concurrent = match convert::read_int_field(env, options, "concurrent")? {
v if v > 0 => v as usize,
v => {
return Err(Error::new(
ErrorKind::Unexpected,
format!("Concurrent must be positive, instead got: {v}"),
)
.into());
}
};
Ok(opendal::options::WriteOptions {
append: convert::read_bool_field(env, options, "append").unwrap_or_default(),
content_type: convert::read_string_field(env, options, "contentType")?,
content_disposition: convert::read_string_field(env, options, "contentDisposition")?,
content_encoding: convert::read_string_field(env, options, "contentEncoding")?,
cache_control: convert::read_string_field(env, options, "cacheControl")?,
if_match: convert::read_string_field(env, options, "ifMatch")?,
if_none_match: convert::read_string_field(env, options, "ifNoneMatch")?,
if_not_exists: convert::read_bool_field(env, options, "ifNotExists").unwrap_or_default(),
user_metadata: convert::read_map_field(env, options, "userMetadata")?,
concurrent,
chunk: convert::read_jlong_field_to_usize(env, options, "chunk")?,
})
}
fn make_list_options<'a>(
env: &mut JNIEnv<'a>,
options: &JObject,
) -> Result<opendal::options::ListOptions> {
Ok(opendal::options::ListOptions {
limit: convert::read_jlong_field_to_usize(env, options, "limit")?,
start_after: convert::read_string_field(env, options, "startAfter")?,
recursive: convert::read_bool_field(env, options, "recursive").unwrap_or_default(),
versions: convert::read_bool_field(env, options, "versions").unwrap_or_default(),
deleted: convert::read_bool_field(env, options, "deleted").unwrap_or_default(),
})
}
fn make_stat_options(env: &mut JNIEnv, options: &JObject) -> Result<opendal::options::StatOptions> {
Ok(opendal::options::StatOptions {
if_match: convert::read_string_field(env, options, "ifMatch")?,
if_none_match: convert::read_string_field(env, options, "ifNoneMatch")?,
if_modified_since: convert::read_instant_field_to_timestamp(
env,
options,
"ifModifiedSince",
)?,
if_unmodified_since: convert::read_instant_field_to_timestamp(
env,
options,
"ifUnmodifiedSince",
)?,
version: convert::read_string_field(env, options, "version")?,
override_content_type: convert::read_string_field(env, options, "overrideContentType")?,
override_cache_control: convert::read_string_field(env, options, "overrideCacheControl")?,
override_content_disposition: convert::read_string_field(
env,
options,
"overrideContentDisposition",
)?,
})
}
fn make_read_options<'a>(
env: &mut JNIEnv<'a>,
options: &JObject,
) -> Result<opendal::options::ReadOptions> {
let offset = convert::read_int64_field(env, options, "offset")?;
let length = convert::read_int64_field(env, options, "length")?;
Ok(opendal::options::ReadOptions {
range: convert::offset_length_to_range(offset, length)?.into(),
..Default::default()
})
}
fn make_reader_options<'a>(
_: &mut JNIEnv<'a>,
_: &JObject,
) -> Result<opendal::options::ReaderOptions> {
Ok(opendal::options::ReaderOptions::default())
}