diff --git a/.github/workflows/bindings_java.yml b/.github/workflows/bindings_java.yml index 55d48b3f9c22..5af665e2a5e5 100644 --- a/.github/workflows/bindings_java.yml +++ b/.github/workflows/bindings_java.yml @@ -49,6 +49,6 @@ jobs: distribution: 'temurin' java-version: '11' cache: 'maven' - - name: Build with Maven - run: mvn test --file ${{ github.workspace }}/bindings/java/pom.xml - + - name: Build and test + working-directory: bindings/java + run: mvn clean verify diff --git a/Cargo.lock b/Cargo.lock index b2df511659db..734e88d31ac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "arc-swap" @@ -262,7 +262,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] @@ -292,7 +292,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] @@ -1008,7 +1008,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] @@ -1025,7 +1025,7 @@ checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] @@ -1426,7 +1426,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] @@ -3302,9 +3302,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.52" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" dependencies = [ "unicode-ident", ] @@ -4056,7 +4056,7 @@ checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] @@ -4372,9 +4372,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ "proc-macro2", "quote", @@ -4456,7 +4456,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] @@ -4565,9 +4565,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg", "bytes", @@ -4579,7 +4579,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -4594,13 +4594,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.12", + "syn 2.0.16", ] [[package]] diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index 92612ca592fb..60728fd78115 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -32,10 +32,9 @@ crate-type = ["cdylib"] doc = false [dependencies] -jni = "0.21.1" opendal.workspace = true +jni = "0.21.1" once_cell = "1.17.1" -tokio = { version = "1", features = ["full"] } - +tokio = { version = "1.28.1", features = ["full"] } num_cpus = "1.15.0" diff --git a/bindings/java/readme.md b/bindings/java/README.md similarity index 58% rename from bindings/java/readme.md rename to bindings/java/README.md index 638ca3babf6f..1ec380decb0d 100644 --- a/bindings/java/readme.md +++ b/bindings/java/README.md @@ -1,3 +1,13 @@ +# OpenDAL Java Bindings + +## Build and test + +Following the command below to build `opendal-java` and run the tests: + +```shell +mvn clean verify +``` + ## Todos - [ ] Readme for usage @@ -5,4 +15,4 @@ - [ ] Exceptions need polish to conform Java files related interface. - [ ] Cucumber test cases - [ ] Experiment: Java doesn't support async/await hence using Kotlin to implement async related API. -- [ ] Cross platform build for release build. \ No newline at end of file +- [ ] Cross-platform build for release build. diff --git a/bindings/java/pom.xml b/bindings/java/pom.xml index abe86ad936b0..70b4c44be821 100644 --- a/bindings/java/pom.xml +++ b/bindings/java/pom.xml @@ -25,14 +25,18 @@ 4.0.0 org.apache.opendal - java-binding + opendal-java 1.0-SNAPSHOT - 8 - 8 + 1.8 + 1.8 UTF-8 - 1.0.0 + + 1.0.0 + + 3.0.0 + 3.3.1 @@ -58,46 +62,43 @@ org.questdb jar-jni - ${rust-maven-plugin.version} + ${questdb.version} + io.cucumber cucumber-java test - io.cucumber cucumber-junit-platform-engine test - org.junit.platform junit-platform-suite test - org.junit.jupiter junit-jupiter test - - + + + src/test/resources + true + + - org.questdb rust-maven-plugin - ${rust-maven-plugin.version} + ${questdb.version} opendal-java @@ -105,9 +106,9 @@ build - ../java + ${project.basedir} true - ${project.build.directory}/classes/org/apache/opendal/rust/libs + ${project.build.directory}/classes/native true --color=always @@ -119,18 +120,14 @@ org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - UTF-8 - 1.8 - 1.8 - + maven-resources-plugin + ${maven-resources-plugin.version} + org.apache.maven.plugins maven-surefire-plugin - 3.0.0 + ${maven-surefire-plugin.version} diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index 1d41d7c8af99..2da8a6af3c4b 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -19,7 +19,6 @@ use std::cell::RefCell; use std::collections::HashMap; use std::ffi::c_void; use std::str::FromStr; -use std::sync::Arc; use jni::objects::JClass; use jni::objects::JMap; @@ -27,24 +26,20 @@ use jni::objects::JObject; use jni::objects::JString; use jni::objects::JThrowable; use jni::objects::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 jni::sys::{jboolean, jobject, JNI_VERSION_1_8}; +use jni::sys::{jint, jlong}; +use jni::{JNIEnv, JavaVM}; use once_cell::sync::OnceCell; -use opendal::BlockingOperator; -use opendal::Operator; -use opendal::Scheme; use tokio::runtime::Builder; use tokio::runtime::Runtime; -static mut RUNTIME: OnceCell = OnceCell::new(); +use opendal::Operator; +use opendal::Scheme; +use opendal::{BlockingOperator, ErrorKind}; +static mut RUNTIME: OnceCell = OnceCell::new(); thread_local! { - static JAVA_VM: RefCell>> = RefCell::new(None); - static JENV: RefCell> = RefCell::new(None); + static ENV: RefCell> = RefCell::new(None); } /// # Safety @@ -52,34 +47,21 @@ thread_local! { /// This function could be only called by java vm when load this lib. #[no_mangle] pub unsafe extern "system" fn JNI_OnLoad(vm: JavaVM, _: *mut c_void) -> jint { - // TODO: make this configurable in the future - let thread_count = num_cpus::get(); - - let java_vm = Arc::new(vm); - let runtime = Builder::new_multi_thread() - .worker_threads(thread_count) - .on_thread_start(move || { - JENV.with(|cell| { - let env = java_vm.attach_current_thread_as_daemon().unwrap(); - *cell.borrow_mut() = Some(env.get_raw()); - }); - JAVA_VM.with(|cell| { - *cell.borrow_mut() = Some(java_vm.clone()); - }); - }) - .on_thread_stop(move || { - JENV.with(|cell| { - *cell.borrow_mut() = None; - }); - JAVA_VM.with(|cell| unsafe { - if let Some(vm) = cell.borrow_mut().take() { - vm.detach_current_thread(); - } - }); - }) - .build() + RUNTIME + .set( + Builder::new_multi_thread() + .worker_threads(num_cpus::get()) + .on_thread_start(move || { + ENV.with(|cell| { + let env = vm.attach_current_thread_as_daemon().unwrap(); + *cell.borrow_mut() = Some(env.get_raw()); + }) + }) + .build() + .unwrap(), + ) .unwrap(); - RUNTIME.set(runtime).unwrap(); + JNI_VERSION_1_8 } @@ -88,13 +70,13 @@ pub unsafe extern "system" fn JNI_OnLoad(vm: JavaVM, _: *mut c_void) -> jint { /// This function could be only called by java vm when unload this lib. #[no_mangle] pub unsafe extern "system" fn JNI_OnUnload(_: JavaVM, _: *mut c_void) { - if let Some(runtime) = RUNTIME.take() { - runtime.shutdown_background(); + if let Some(r) = RUNTIME.take() { + r.shutdown_background() } } #[no_mangle] -pub extern "system" fn Java_org_apache_opendal_Operator_getOperator( +pub extern "system" fn Java_org_apache_opendal_Operator_newOperator( mut env: JNIEnv, _class: JClass, input: JString, @@ -102,21 +84,21 @@ pub extern "system" fn Java_org_apache_opendal_Operator_getOperator( ) -> jlong { let input: String = env .get_string(&input) - .expect("Couldn't get java string!") + .expect("cannot get java string") .into(); let scheme = Scheme::from_str(&input).unwrap(); - let map = convert_map(&mut env, ¶ms); - if let Ok(operator) = build_operator(scheme, map) { + let map = convert_jmap_to_hashmap(&mut env, ¶ms); + if let Ok(operator) = Operator::via_map(scheme, map) { Box::into_raw(Box::new(operator)) as jlong } else { - env.exception_clear().expect("Couldn't clear exception"); + env.exception_clear().expect("cannot clear exception"); env.throw_new( "java/lang/IllegalArgumentException", "Unsupported operator.", ) - .expect("Couldn't throw exception"); + .expect("cannot throw exception"); 0 as jlong } } @@ -131,53 +113,60 @@ pub unsafe extern "system" fn Java_org_apache_opendal_Operator_writeAsync( ptr: *mut Operator, file: JString, content: JString, - future: JObject, -) { +) -> jobject { let op = &mut *ptr; let file: String = env.get_string(&file).unwrap().into(); let content: String = env.get_string(&content).unwrap().into(); + + let class = "java/util/concurrent/CompletableFuture"; + let f = env.new_object(class, "()V", &[]).unwrap(); + // keep the future alive, so that we can complete it later - // but this approach will be limited by global ref table size - let future = env.new_global_ref(future).unwrap(); + // but this approach will be limited by global ref table size (65535) + let future = env.new_global_ref(&f).unwrap(); - let x = async move { - op.write(&file, content).await.unwrap(); - JENV.with(|cell| { - let env_ptr = cell.borrow().unwrap(); - let mut env = JNIEnv::from_raw(env_ptr).unwrap(); + RUNTIME.get_unchecked().spawn(async move { + let result = op.write(&file, content).await; - // build result - let boolean_class = env.find_class("java/lang/Boolean").unwrap(); - let boolean = env - .get_static_field(boolean_class, "TRUE", "Ljava/lang/Boolean;") - .unwrap(); + let env = ENV.with(|cell| *cell.borrow_mut()).unwrap(); + let mut env = JNIEnv::from_raw(env).unwrap(); - // complete the java future - let _ = env + match result { + Ok(()) => env .call_method( future, "complete", "(Ljava/lang/Object;)Z", - &[boolean.borrow()], + &[JValue::Object(&JObject::null())], ) - .unwrap(); - }); - }; - RUNTIME.get().unwrap().spawn(x); + .unwrap(), + Err(err) => { + let exception = convert_error_to_exception(&mut env, err).unwrap(); + env.call_method( + future, + "completeExceptionally", + "(Ljava/lang/Throwable;)Z", + &[JValue::Object(&exception)], + ) + .unwrap() + } + } + }); + + f.as_raw() } /// # Safety /// /// This function should not be called before the Operator are ready. #[no_mangle] -pub unsafe extern "system" fn Java_org_apache_opendal_Operator_freeOperator( +pub unsafe extern "system" fn Java_org_apache_opendal_Operator_disposeInternal( mut _env: JNIEnv, _class: JClass, ptr: *mut Operator, ) { - // Take ownership of the pointer by wrapping it with a Box - let _ = Box::from_raw(ptr); + drop(Box::from_raw(ptr)); } /// # Safety @@ -194,11 +183,11 @@ pub unsafe extern "system" fn Java_org_apache_opendal_Operator_write( let op = &mut *ptr; let file: String = env .get_string(&file) - .expect("Couldn't get java string!") + .expect("cannot get java string!") .into(); let content: String = env .get_string(&content) - .expect("Couldn't get java string!") + .expect("cannot get java string!") .into(); op.write(&file, content).unwrap(); } @@ -216,39 +205,10 @@ pub unsafe extern "system" fn Java_org_apache_opendal_Operator_read<'local>( let op = &mut *ptr; let file: String = env .get_string(&file) - .expect("Couldn't get java string!") + .expect("cannot get java string!") .into(); - let content = String::from_utf8(op.read(&file).unwrap()).expect("Couldn't convert to string"); - - let output = env - .new_string(content) - .expect("Couldn't create java string!"); - output -} - -fn convert_error_into_java_exception<'local>( - env: &mut JNIEnv<'local>, - error: opendal::Error, -) -> Result, jni::errors::Error> { - let error_code_class = env.find_class("org/apache/opendal/exception/OpenDALErrorCode")?; - let error_code_string = env.new_string(error.kind().into_static())?; - let error_code = env.call_static_method( - error_code_class, - "parse", - "(Ljava/lang/String;)Lorg/apache/opendal/exception/OpenDALErrorCode;", - &[JValue::Object(error_code_string.as_ref())], - )?; - - let exception_class = env.find_class("org/apache/opendal/exception/OpenDALException")?; - let exception = env.new_object( - exception_class, - "(Lorg/apache/opendal/exception/OpenDALErrorCode;Ljava/lang/String;)V", - &[ - JValue::Object(error_code.l()?.as_ref()), - JValue::Object(env.new_string(error.to_string())?.as_ref()), - ], - )?; - Ok(JThrowable::from(exception)) + let content = String::from_utf8(op.read(&file).unwrap()).expect("cannot convert to string"); + env.new_string(content).expect("cannot create java string") } /// # Safety @@ -264,11 +224,11 @@ pub unsafe extern "system" fn Java_org_apache_opendal_Operator_stat( let op = &mut *ptr; let file: String = env .get_string(&file) - .expect("Couldn't get java string!") + .expect("cannot get java string!") .into(); let result = op.stat(&file); if let Err(error) = result { - let exception = convert_error_into_java_exception(&mut env, error).unwrap(); + let exception = convert_error_to_exception(&mut env, error).unwrap(); env.throw(exception).unwrap(); return 0 as jlong; } @@ -305,13 +265,12 @@ pub unsafe extern "system" fn Java_org_apache_opendal_Metadata_getContentLength( /// /// This function should not be called before the Stat are ready. #[no_mangle] -pub unsafe extern "system" fn Java_org_apache_opendal_Metadata_freeMetadata( +pub unsafe extern "system" fn Java_org_apache_opendal_Metadata_disposeInternal( mut _env: JNIEnv, _class: JClass, ptr: *mut opendal::Metadata, ) { - // Take ownership of the pointer by wrapping it with a Box - let _ = Box::from_raw(ptr); + drop(Box::from_raw(ptr)); } /// # Safety @@ -327,65 +286,52 @@ pub unsafe extern "system" fn Java_org_apache_opendal_Operator_delete<'local>( let op = &mut *ptr; let file: String = env .get_string(&file) - .expect("Couldn't get java string!") + .expect("cannot get java string!") .into(); op.delete(&file).unwrap(); } -fn build_operator( - scheme: opendal::Scheme, - map: HashMap, -) -> Result { - use opendal::services::*; - - let op = match scheme { - opendal::Scheme::Azblob => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Azdfs => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Fs => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Gcs => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Ghac => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Http => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Ipmfs => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Memory => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Obs => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Oss => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::S3 => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Webdav => opendal::Operator::from_map::(map).unwrap().finish(), - opendal::Scheme::Webhdfs => opendal::Operator::from_map::(map) - .unwrap() - .finish(), +fn convert_error_to_exception<'local>( + env: &mut JNIEnv<'local>, + error: opendal::Error, +) -> Result, jni::errors::Error> { + let class = env.find_class("org/apache/opendal/exception/ODException")?; - _ => { - return Err(opendal::Error::new( - opendal::ErrorKind::Unexpected, - "Scheme not supported", - )); - } - }; + let code = env.new_string(match error.kind() { + ErrorKind::Unexpected => "Unexpected", + ErrorKind::Unsupported => "Unsupported", + ErrorKind::ConfigInvalid => "ConfigInvalid", + ErrorKind::NotFound => "NotFound", + ErrorKind::PermissionDenied => "PermissionDenied", + ErrorKind::IsADirectory => "IsADirectory", + ErrorKind::NotADirectory => "NotADirectory", + ErrorKind::AlreadyExists => "AlreadyExists", + ErrorKind::RateLimited => "RateLimited", + ErrorKind::IsSameFile => "IsSameFile", + ErrorKind::ConditionNotMatch => "ConditionNotMatch", + ErrorKind::ContentTruncated => "ContentTruncated", + ErrorKind::ContentIncomplete => "ContentIncomplete", + _ => "Unexpected", + })?; + let message = env.new_string(error.to_string())?; - Ok(op) + let sig = "(Ljava/lang/String;Ljava/lang/String;)V"; + let params = &[JValue::Object(&code), JValue::Object(&message)]; + env.new_object(class, sig, params).map(JThrowable::from) } -fn convert_map(env: &mut JNIEnv, params: &JObject) -> HashMap { +fn convert_jmap_to_hashmap(env: &mut JNIEnv, params: &JObject) -> HashMap { + let map = JMap::from_env(env, params).unwrap(); + let mut iter = map.iter(env).unwrap(); + let mut result: HashMap = HashMap::new(); - let _ = JMap::from_env(env, params) - .unwrap() - .iter(env) - .and_then(|mut iter| { - while let Some(e) = iter.next(env)? { - let key = JString::from(e.0); - let value = JString::from(e.1); - let key: String = env - .get_string(&key) - .expect("Couldn't get java string!") - .into(); - let value: String = env - .get_string(&value) - .expect("Couldn't get java string!") - .into(); - result.insert(key, value); - } - Ok(()) - }); + while let Some(e) = iter.next(env).unwrap() { + let k = JString::from(e.0); + let v = JString::from(e.1); + result.insert( + env.get_string(&k).unwrap().into(), + env.get_string(&v).unwrap().into(), + ); + } result } diff --git a/bindings/java/src/main/java/org/apache/opendal/Metadata.java b/bindings/java/src/main/java/org/apache/opendal/Metadata.java index 809ba7e73948..42465664068e 100644 --- a/bindings/java/src/main/java/org/apache/opendal/Metadata.java +++ b/bindings/java/src/main/java/org/apache/opendal/Metadata.java @@ -19,27 +19,21 @@ package org.apache.opendal; -public class Metadata extends OpenDALObject { - public Metadata(long ptr) { - this.ptr = ptr; +public class Metadata extends NativeObject { + public Metadata(long nativeHandle) { + super(nativeHandle); } public boolean isFile() { - return isFile(this.ptr); + return isFile(nativeHandle); } public long getContentLength() { - return getContentLength(this.ptr); + return getContentLength(nativeHandle); } @Override - public void close() { - freeMetadata(this.ptr); - } - - private native void freeMetadata(long statPtr); - - private native boolean isFile(long statPtr); - - private native long getContentLength(long statPtr); + protected native void disposeInternal(long handle); + private static native boolean isFile(long nativeHandle); + private static native long getContentLength(long nativeHandle); } diff --git a/bindings/java/src/main/java/org/apache/opendal/NativeObject.java b/bindings/java/src/main/java/org/apache/opendal/NativeObject.java new file mode 100644 index 000000000000..fadf722588ba --- /dev/null +++ b/bindings/java/src/main/java/org/apache/opendal/NativeObject.java @@ -0,0 +1,71 @@ +/* + * 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 io.questdb.jar.jni.JarJniLoader; +import java.util.concurrent.atomic.AtomicReference; + +public abstract class NativeObject implements AutoCloseable { + + private enum LibraryState { + NOT_LOADED, + LOADING, + LOADED + } + + private static final AtomicReference libraryLoaded = + new AtomicReference<>(LibraryState.NOT_LOADED); + + static { + NativeObject.loadLibrary(); + } + + public static void loadLibrary() { + if (libraryLoaded.get() == LibraryState.LOADED) { + return; + } + + if (libraryLoaded.compareAndSet(LibraryState.NOT_LOADED, LibraryState.LOADING)) { + JarJniLoader.loadLib(NativeObject.class, "/native", "opendal_java"); + libraryLoaded.set(LibraryState.LOADED); + return; + } + + while (libraryLoaded.get() == LibraryState.LOADING) { + try { + Thread.sleep(10); + } catch (InterruptedException ignore) { + } + } + } + + protected final long nativeHandle; + + protected NativeObject(long nativeHandle) { + this.nativeHandle = nativeHandle; + } + + @Override + public void close() { + disposeInternal(nativeHandle); + } + + protected abstract void disposeInternal(long handle); +} diff --git a/bindings/java/src/main/java/org/apache/opendal/OpenDALObject.java b/bindings/java/src/main/java/org/apache/opendal/OpenDALObject.java deleted file mode 100644 index 8a60cf9a9f0a..000000000000 --- a/bindings/java/src/main/java/org/apache/opendal/OpenDALObject.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 io.questdb.jar.jni.JarJniLoader; - -public abstract class OpenDALObject implements AutoCloseable { - private static final String ORG_APACHE_OPENDAL_RUST_LIBS = "/org/apache/opendal/rust/libs"; - - private static final String OPENDAL_JAVA = "opendal_java"; - - static { - JarJniLoader.loadLib( - Operator.class, - ORG_APACHE_OPENDAL_RUST_LIBS, - OPENDAL_JAVA); - } - - long ptr; -} 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 e436ed753871..b3e0bb018c2c 100644 --- a/bindings/java/src/main/java/org/apache/opendal/Operator.java +++ b/bindings/java/src/main/java/org/apache/opendal/Operator.java @@ -23,50 +23,43 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; -public class Operator extends OpenDALObject { +public class Operator extends NativeObject { public Operator(String schema, Map params) { - this.ptr = getOperator(schema, params); + super(newOperator(schema, params)); } public void write(String fileName, String content) { - write(this.ptr, fileName, content); + write(nativeHandle, fileName, content); } - public CompletableFuture writeAsync(String fileName, String content) { - CompletableFuture future = new CompletableFuture<>(); - writeAsync(this.ptr, fileName, content, future); - return future; + public CompletableFuture writeAsync(String fileName, String content) { + return writeAsync(nativeHandle, fileName, content); } public String read(String s) { - return read(this.ptr, s); + return read(nativeHandle, s); } public void delete(String s) { - delete(this.ptr, s); + delete(nativeHandle, s); } public Metadata stat(String fileName) { - long statPtr = stat(this.ptr, fileName); - return new Metadata(statPtr); + return new Metadata(stat(nativeHandle, fileName)); } @Override - public void close() { - this.freeOperator(ptr); - } - - private native long getOperator(String type, Map params); + protected native void disposeInternal(long handle); - protected native void freeOperator(long ptr); + private static native long newOperator(String type, Map params); - private native void write(long ptr, String fileName, String content); + private static native void write(long nativeHandle, String fileName, String content); - private native void writeAsync(long ptr, String fileName, String content, CompletableFuture future); + private static native CompletableFuture writeAsync(long nativeHandle, String fileName, String content); - private native String read(long ptr, String fileName); + private static native String read(long nativeHandle, String fileName); - private native void delete(long ptr, String fileName); + private static native void delete(long nativeHandle, String fileName); - private native long stat(long ptr, String file); + private static native long stat(long nativeHandle, String file); } diff --git a/bindings/java/src/main/java/org/apache/opendal/exception/OpenDALException.java b/bindings/java/src/main/java/org/apache/opendal/exception/ODException.java similarity index 58% rename from bindings/java/src/main/java/org/apache/opendal/exception/OpenDALException.java rename to bindings/java/src/main/java/org/apache/opendal/exception/ODException.java index 104423ac90b8..5a428673e65b 100644 --- a/bindings/java/src/main/java/org/apache/opendal/exception/OpenDALException.java +++ b/bindings/java/src/main/java/org/apache/opendal/exception/ODException.java @@ -19,15 +19,35 @@ package org.apache.opendal.exception; -public class OpenDALException extends RuntimeException { - private final OpenDALErrorCode errorCode; +public class ODException extends RuntimeException { + private final Code code; - public OpenDALException(OpenDALErrorCode errorCode, String message) { + public ODException(String code, String message) { + this(Code.valueOf(code), message); + } + + public ODException(Code code, String message) { super(message); - this.errorCode = errorCode; + this.code = code; + } + + public Code getCode() { + return code; } - public OpenDALErrorCode getErrorCode() { - return errorCode; + public enum Code { + Unexpected, + Unsupported, + ConfigInvalid, + NotFound, + PermissionDenied, + IsADirectory, + NotADirectory, + AlreadyExists, + RateLimited, + IsSameFile, + ConditionNotMatch, + ContentTruncated, + ContentIncomplete, } } diff --git a/bindings/java/src/main/java/org/apache/opendal/exception/OpenDALErrorCode.java b/bindings/java/src/main/java/org/apache/opendal/exception/OpenDALErrorCode.java deleted file mode 100644 index 1806b908e4ee..000000000000 --- a/bindings/java/src/main/java/org/apache/opendal/exception/OpenDALErrorCode.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.exception; - -public enum OpenDALErrorCode { - UNEXPECTED, - UNSUPPORTED, - CONFIG_INVALID, - NOT_FOUND, - PERMISSION_DENIED, - IS_A_DIRECTORY, - NOT_A_DIRECTORY, - ALREADY_EXISTS, - RATE_LIMITED, - IS_SAME_FILE, - CONDITION_NOT_MATCH, - CONTENT_TRUNCATED, - CONTENT_INCOMPLETE; - - public static OpenDALErrorCode parse(String errorCode) { - switch (errorCode) { - case "Unsupported": - return UNSUPPORTED; - case "ConfigInvalid": - return CONFIG_INVALID; - case "NotFound": - return NOT_FOUND; - case "PermissionDenied": - return PERMISSION_DENIED; - case "IsADirectory": - return IS_A_DIRECTORY; - case "NotADirectory": - return NOT_A_DIRECTORY; - case "AlreadyExists": - return ALREADY_EXISTS; - case "RateLimited": - return RATE_LIMITED; - case "IsSameFile": - return IS_SAME_FILE; - case "ConditionNotMatch": - return CONDITION_NOT_MATCH; - case "ContentTruncated": - return CONTENT_TRUNCATED; - case "ContentIncomplete": - return CONTENT_INCOMPLETE; - default: - return UNEXPECTED; - } - } -} diff --git a/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java b/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java index d34fead8e06e..81e8538da601 100644 --- a/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java +++ b/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; public class AsyncStepsTest { @@ -40,9 +41,12 @@ public void a_new_open_dal_async_operator() { @When("Async write path {string} with content {string}") public void async_write_path_test_with_content_hello_world(String fileName, String content) { - CompletableFuture future = operator.writeAsync(fileName, content); - Boolean result = future.join(); - assertTrue(result); + CompletableFuture f = operator.writeAsync(fileName, content); + + f.join(); + + assertTrue(f.isDone()); + assertFalse(f.isCompletedExceptionally()); } @Then("The async file {string} should exist") diff --git a/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java b/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java index ce2cf3a1a7eb..2d38418922f4 100644 --- a/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java +++ b/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java @@ -21,8 +21,7 @@ import java.util.HashMap; import java.util.Map; -import org.apache.opendal.exception.OpenDALErrorCode; -import org.apache.opendal.exception.OpenDALException; +import org.apache.opendal.exception.ODException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,7 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class ExceptionTest { - Operator operator; + private Operator operator; @BeforeEach public void init() { @@ -47,7 +46,7 @@ public void clean() { @Test public void testStatNotExistFile() { - OpenDALException exception = assertThrows(OpenDALException.class, () -> this.operator.stat("not_exist_file")); - assertEquals(exception.getErrorCode(), OpenDALErrorCode.NOT_FOUND); + final ODException exception = assertThrows(ODException.class, () -> operator.stat("not_exist_file")); + assertEquals(ODException.Code.NotFound, exception.getCode()); } } diff --git a/core/src/types/operator/builder.rs b/core/src/types/operator/builder.rs index 80c1ce8a46e6..4f91366123cc 100644 --- a/core/src/types/operator/builder.rs +++ b/core/src/types/operator/builder.rs @@ -119,7 +119,7 @@ impl Operator { Ok(OperatorBuilder::new(acc)) } - /// Create a new operator from given shceme and map. + /// Create a new operator from given scheme and map. /// /// # Notes ///