diff --git a/bindings/java/src/blocking_operator.rs b/bindings/java/src/blocking_operator.rs index d9e5b5ad2cba..980b899cdd41 100644 --- a/bindings/java/src/blocking_operator.rs +++ b/bindings/java/src/blocking_operator.rs @@ -31,6 +31,7 @@ use opendal::Scheme; use crate::get_global_runtime; use crate::jmap_to_hashmap; +use crate::make_operator_info; use crate::Result; #[no_mangle] @@ -160,3 +161,27 @@ fn intern_delete(env: &mut JNIEnv, op: &mut BlockingOperator, path: JString) -> let path = env.get_string(&path)?; Ok(op.delete(path.to_str()?)?) } + +/// # Safety +/// +/// This function should not be called before the Operator are ready. +#[no_mangle] +pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_info<'local>( + mut env: JNIEnv<'local>, + _: JClass, + op: *mut BlockingOperator, +) -> JObject<'local> { + intern_info(&mut env, &mut *op).unwrap_or_else(|e| { + e.throw(&mut env); + JObject::null() + }) +} + +fn intern_info<'local>( + env: &mut JNIEnv<'local>, + op: &mut BlockingOperator, +) -> Result> { + let info = op.info(); + + make_operator_info(env, info) +} diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index 353855d4c5d4..cd039ef09190 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -19,21 +19,23 @@ use std::cell::RefCell; use std::collections::HashMap; use std::ffi::c_void; -use jni::objects::JMap; +use crate::error::Error; use jni::objects::JObject; use jni::objects::JString; -use jni::objects::JValue; +use jni::objects::{JMap, JValue}; +use jni::sys::jboolean; use jni::sys::jint; +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::OperatorInfo; use tokio::runtime::Builder; use tokio::runtime::Runtime; -use crate::error::Error; - mod blocking_operator; mod error; mod metadata; @@ -144,3 +146,79 @@ fn make_presigned_request<'a>(env: &mut JNIEnv<'a>, req: PresignedRequest) -> Re )?; Ok(result) } + +fn make_operator_info<'a>(env: &mut JNIEnv<'a>, info: OperatorInfo) -> Result> { + let operator_info_class = env.find_class("org/apache/opendal/OperatorInfo")?; + + let schema = 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 operator_info_obj = env + .new_object( + operator_info_class, + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/apache/opendal/Capability;Lorg/apache/opendal/Capability;)V", + &[ + JValue::Object(&schema), + JValue::Object(&root), + JValue::Object(&name), + JValue::Object(&full_capability_obj), + JValue::Object(&native_capability_obj), + ], + )?; + + Ok(operator_info_obj) +} + +fn make_capability<'a>(env: &mut JNIEnv<'a>, cap: Capability) -> Result> { + let capability_class = env.find_class("org/apache/opendal/Capability")?; + + let capability = env.new_object( + capability_class, + "(ZZZZZZZZZZZZZZZZZZJJJZZZZZZZZZZZZZZZJZ)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.read as jboolean), + JValue::Bool(cap.read_can_seek as jboolean), + JValue::Bool(cap.read_can_next as jboolean), + JValue::Bool(cap.read_with_range 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::Long(cap.write_multi_max_size.map_or(-1, |v| v as jlong)), + JValue::Long(cap.write_multi_min_size.map_or(-1, |v| v as jlong)), + JValue::Long(cap.write_multi_align_size.map_or(-1, |v| v as jlong)), + 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_delimiter_slash as jboolean), + JValue::Bool(cap.list_without_delimiter 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.batch as jboolean), + JValue::Bool(cap.batch_delete as jboolean), + JValue::Long(cap.batch_max_operations.map_or(-1, |v| v as jlong)), + JValue::Bool(cap.blocking as jboolean), + ], + )?; + + Ok(capability) +} diff --git a/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java b/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java index 1d8e2bf25a66..e82bdfea8be5 100644 --- a/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java +++ b/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java @@ -61,6 +61,10 @@ public Metadata stat(String path) { return new Metadata(stat(nativeHandle, path)); } + public OperatorInfo info() { + return info(nativeHandle); + } + @Override protected native void disposeInternal(long handle); @@ -73,4 +77,6 @@ public Metadata stat(String path) { private static native void delete(long nativeHandle, String path); private static native long stat(long nativeHandle, String path); + + private static native OperatorInfo info(long nativeHandle); } diff --git a/bindings/java/src/main/java/org/apache/opendal/Capability.java b/bindings/java/src/main/java/org/apache/opendal/Capability.java new file mode 100644 index 000000000000..2291738bb38f --- /dev/null +++ b/bindings/java/src/main/java/org/apache/opendal/Capability.java @@ -0,0 +1,270 @@ +/* + * 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. + */ + +package org.apache.opendal; + +import lombok.ToString; + +@ToString +public class Capability { + /** + * If operator supports stat. + */ + public final boolean stat; + /** + * If operator supports stat with if match. + */ + public final boolean statWithIfMatch; + /** + * If operator supports stat with if none match. + */ + public final boolean statWithIfNoneMatch; + + /** + * If operator supports read. + */ + public final boolean read; + /** + * If operator supports seek on returning reader. + */ + public final boolean readCanSeek; + /** + * If operator supports next on returning reader. + */ + public final boolean readCanNext; + /** + * If operator supports read with range. + */ + public final boolean readWithRange; + /** + * If operator supports read with if match. + */ + public final boolean readWithIfMatch; + /** + * If operator supports read with if none match. + */ + public final boolean readWithIfNoneMatch; + /** + * If operator supports read with override cache control. + */ + public final boolean readWithOverrideCacheControl; + /** + * if operator supports read with override content disposition. + */ + public final boolean readWithOverrideContentDisposition; + /** + * if operator supports read with override content type. + */ + public final boolean readWithOverrideContentType; + + /** + * If operator supports write. + */ + public final boolean write; + /** + * If operator supports write can be called in multi times. + */ + public final boolean writeCanMulti; + /** + * If operator supports write by append. + */ + public final boolean writeCanAppend; + /** + * If operator supports write with content type. + */ + public final boolean writeWithContentType; + /** + * If operator supports write with content disposition. + */ + public final boolean writeWithContentDisposition; + /** + * If operator supports write with cache control. + */ + public final boolean writeWithCacheControl; + /** + * write_multi_max_size is the max size that services support in write_multi. + * For example, AWS S3 supports 5GiB as max in write_multi. + */ + public final long writeMultiMaxSize; + /** + * write_multi_min_size is the min size that services support in write_multi. + * For example, AWS S3 requires at least 5MiB in write_multi expect the last one. + */ + public final long writeMultiMinSize; + /** + * write_multi_align_size is the align size that services required in write_multi. + * For example, Google GCS requires align size to 256KiB in write_multi. + */ + public final long writeMultiAlignSize; + + /** + * If operator supports create dir. + */ + public final boolean createDir; + + /** + * If operator supports delete. + */ + public final boolean delete; + + /** + * If operator supports copy. + */ + public final boolean copy; + + /** + * If operator supports rename. + */ + public final boolean rename; + + /** + * If operator supports list. + */ + public final boolean list; + /** + * If backend supports list with limit. + */ + public final boolean listWithLimit; + /** + * If backend supports list with start after. + */ + public final boolean listWithStartAfter; + /** + * If backend support list with using slash as delimiter. + */ + public final boolean listWithDelimiterSlash; + /** + * If backend supports list without delimiter. + */ + public final boolean listWithoutDelimiter; + + /** + * If operator supports presign. + */ + public final boolean presign; + /** + * If operator supports presign read. + */ + public final boolean presignRead; + /** + * If operator supports presign stat. + */ + public final boolean presignStat; + /** + * If operator supports presign write. + */ + public final boolean presignWrite; + + /** + * If operator supports batch. + */ + public final boolean batch; + /** + * If operator supports batch delete. + */ + public final boolean batchDelete; + /** + * The max operations that operator supports in batch. + */ + public final long batchMaxOperations; + + /** + * If operator supports blocking. + */ + public final boolean blocking; + + public Capability( + boolean stat, + boolean statWithIfMatch, + boolean statWithIfNoneMatch, + boolean read, + boolean readCanSeek, + boolean readCanNext, + boolean readWithRange, + boolean readWithIfMatch, + boolean readWithIfNoneMatch, + boolean readWithOverrideCacheControl, + boolean readWithOverrideContentDisposition, + boolean readWithOverrideContentType, + boolean write, + boolean writeCanMulti, + boolean writeCanAppend, + boolean writeWithContentType, + boolean writeWithContentDisposition, + boolean writeWithCacheControl, + long writeMultiMaxSize, + long writeMultiMinSize, + long writeMultiAlignSize, + boolean createDir, + boolean delete, + boolean copy, + boolean rename, + boolean list, + boolean listWithLimit, + boolean listWithStartAfter, + boolean listWithDelimiterSlash, + boolean listWithoutDelimiter, + boolean presign, + boolean presignRead, + boolean presignStat, + boolean presignWrite, + boolean batch, + boolean batchDelete, + long batchMaxOperations, + boolean blocking) { + this.stat = stat; + this.statWithIfMatch = statWithIfMatch; + this.statWithIfNoneMatch = statWithIfNoneMatch; + this.read = read; + this.readCanSeek = readCanSeek; + this.readCanNext = readCanNext; + this.readWithRange = readWithRange; + this.readWithIfMatch = readWithIfMatch; + this.readWithIfNoneMatch = readWithIfNoneMatch; + this.readWithOverrideCacheControl = readWithOverrideCacheControl; + this.readWithOverrideContentDisposition = readWithOverrideContentDisposition; + this.readWithOverrideContentType = readWithOverrideContentType; + this.write = write; + this.writeCanMulti = writeCanMulti; + this.writeCanAppend = writeCanAppend; + this.writeWithContentType = writeWithContentType; + this.writeWithContentDisposition = writeWithContentDisposition; + this.writeWithCacheControl = writeWithCacheControl; + this.writeMultiMaxSize = writeMultiMaxSize; + this.writeMultiMinSize = writeMultiMinSize; + this.writeMultiAlignSize = writeMultiAlignSize; + this.createDir = createDir; + this.delete = delete; + this.copy = copy; + this.rename = rename; + this.list = list; + this.listWithLimit = listWithLimit; + this.listWithStartAfter = listWithStartAfter; + this.listWithDelimiterSlash = listWithDelimiterSlash; + this.listWithoutDelimiter = listWithoutDelimiter; + this.presign = presign; + this.presignRead = presignRead; + this.presignStat = presignStat; + this.presignWrite = presignWrite; + this.batch = batch; + this.batchDelete = batchDelete; + this.batchMaxOperations = batchMaxOperations; + this.blocking = blocking; + } +} diff --git a/bindings/java/src/main/java/org/apache/opendal/Operator.java b/bindings/java/src/main/java/org/apache/opendal/Operator.java index c6206ef3c325..cdc09e34b193 100644 --- a/bindings/java/src/main/java/org/apache/opendal/Operator.java +++ b/bindings/java/src/main/java/org/apache/opendal/Operator.java @@ -142,6 +142,10 @@ public CompletableFuture read(String path) { return AsyncRegistry.take(requestId); } + public OperatorInfo info() { + return info(nativeHandle); + } + public CompletableFuture presignRead(String path, Duration duration) { final long requestId = presignRead(nativeHandle, path, duration.toNanos()); return AsyncRegistry.take(requestId); @@ -182,4 +186,6 @@ public CompletableFuture delete(String path) { private static native long presignWrite(long nativeHandle, String path, long duration); private static native long presignStat(long nativeHandle, String path, long duration); + + private static native OperatorInfo info(long nativeHandle); } diff --git a/bindings/java/src/main/java/org/apache/opendal/OperatorInfo.java b/bindings/java/src/main/java/org/apache/opendal/OperatorInfo.java new file mode 100644 index 000000000000..c170f33559cb --- /dev/null +++ b/bindings/java/src/main/java/org/apache/opendal/OperatorInfo.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package org.apache.opendal; + +import lombok.NonNull; +import lombok.ToString; + +@ToString +public class OperatorInfo { + public final String scheme; + public final String root; + public final String name; + public final Capability fullCapability; + public final Capability nativeCapability; + + public OperatorInfo( + @NonNull String scheme, + @NonNull String root, + @NonNull String name, + @NonNull Capability fullCapability, + @NonNull Capability nativeCapability) { + this.scheme = scheme; + this.root = root; + this.name = name; + this.fullCapability = fullCapability; + this.nativeCapability = nativeCapability; + } +} diff --git a/bindings/java/src/operator.rs b/bindings/java/src/operator.rs index d0b3d938d198..58823e78111a 100644 --- a/bindings/java/src/operator.rs +++ b/bindings/java/src/operator.rs @@ -34,6 +34,7 @@ use opendal::Scheme; use crate::get_current_env; use crate::get_global_runtime; use crate::jmap_to_hashmap; +use crate::make_operator_info; use crate::make_presigned_request; use crate::Result; @@ -262,6 +263,28 @@ async fn do_delete(op: &mut Operator, path: String) -> Result<()> { Ok(op.delete(&path).await?) } +// # Safety +/// +/// This function should not be called before the Operator are ready. +#[no_mangle] +pub unsafe extern "system" fn Java_org_apache_opendal_Operator_info<'local>( + mut env: JNIEnv<'local>, + _: JClass, + op: *mut Operator, +) -> JObject<'local> { + intern_info(&mut env, op).unwrap_or_else(|e| { + e.throw(&mut env); + JObject::null() + }) +} + +fn intern_info<'local>(env: &mut JNIEnv<'local>, op: *mut Operator) -> Result> { + let op = unsafe { &mut *op }; + + let info = op.info(); + make_operator_info(env, info) +} + /// # Safety /// /// This function should not be called before the Operator are ready. diff --git a/bindings/java/src/test/java/org/apache/opendal/OperatorInfoTest.java b/bindings/java/src/test/java/org/apache/opendal/OperatorInfoTest.java new file mode 100644 index 000000000000..e4ea7786a217 --- /dev/null +++ b/bindings/java/src/test/java/org/apache/opendal/OperatorInfoTest.java @@ -0,0 +1,93 @@ +/* + * 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. + */ + +package org.apache.opendal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class OperatorInfoTest { + + @TempDir + private static Path tempDir; + + @Test + public void testBlockingOperatorInfo() { + Map conf = new HashMap<>(); + conf.put("root", tempDir.toString()); + try (BlockingOperator op = new BlockingOperator("fs", conf)) { + + OperatorInfo info = op.info(); + assertNotNull(info); + assertEquals("fs", info.scheme); + + Capability fullCapability = info.fullCapability; + assertNotNull(fullCapability); + + assertTrue(fullCapability.read); + assertTrue(fullCapability.write); + assertTrue(fullCapability.delete); + assertTrue(fullCapability.writeCanAppend); + + assertEquals(fullCapability.writeMultiAlignSize, -1); + assertEquals(fullCapability.writeMultiMaxSize, -1); + assertEquals(fullCapability.writeMultiMinSize, -1); + assertEquals(fullCapability.batchMaxOperations, -1); + + Capability nativeCapability = info.nativeCapability; + assertNotNull(nativeCapability); + } + } + + @Test + public void testOperatorInfo() { + Map conf = new HashMap<>(); + String root = "/opendal/"; + conf.put("root", root); + try (Operator op = new Operator("memory", conf)) { + + OperatorInfo info = op.info(); + assertNotNull(info); + assertEquals("memory", info.scheme); + assertEquals(root, info.root); + + Capability fullCapability = info.fullCapability; + assertNotNull(fullCapability); + + assertTrue(fullCapability.read); + assertTrue(fullCapability.write); + assertTrue(fullCapability.delete); + assertTrue(!fullCapability.writeCanAppend); + + assertEquals(fullCapability.writeMultiAlignSize, -1); + assertEquals(fullCapability.writeMultiMaxSize, -1); + assertEquals(fullCapability.writeMultiMinSize, -1); + assertEquals(fullCapability.batchMaxOperations, -1); + + Capability nativeCapability = info.nativeCapability; + assertNotNull(nativeCapability); + } + } +}