diff --git a/bindings/java/pom.xml b/bindings/java/pom.xml
index 5b542b5ce737..abe86ad936b0 100644
--- a/bindings/java/pom.xml
+++ b/bindings/java/pom.xml
@@ -132,25 +132,6 @@
maven-surefire-plugin
3.0.0
-
- com.coderplus.maven.plugins
- copy-rename-maven-plugin
- 1.0
-
-
- copy-file
- generate-sources
-
- copy
-
-
- ../tests/features/binding.feature
- target/test-classes/features/binding.feature
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs
index 1edd5b261ad5..fc8167ba969e 100644
--- a/bindings/java/src/lib.rs
+++ b/bindings/java/src/lib.rs
@@ -21,10 +21,10 @@ use std::ffi::c_void;
use std::str::FromStr;
use std::sync::Arc;
-use jni::objects::JClass;
use jni::objects::JMap;
use jni::objects::JObject;
use jni::objects::JString;
+use jni::objects::{JClass, JThrowable, JValue};
use jni::sys::{jboolean, jint};
use jni::sys::{jlong, JNI_VERSION_1_8};
use jni::{JNIEnv, JavaVM};
@@ -120,7 +120,7 @@ pub extern "system" fn Java_org_apache_opendal_Operator_getOperator(
///
/// This function should not be called before the Operator are ready.
#[no_mangle]
-pub unsafe extern "system" fn Java_org_apache_opendal_Operator_asyncWrite(
+pub unsafe extern "system" fn Java_org_apache_opendal_Operator_writeAsync(
mut env: JNIEnv,
_class: JClass,
ptr: *mut Operator,
@@ -221,6 +221,31 @@ pub unsafe extern "system" fn Java_org_apache_opendal_Operator_read<'local>(
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))
+}
+
/// # Safety
///
/// This function should not be called before the Operator are ready.
@@ -236,8 +261,13 @@ pub unsafe extern "system" fn Java_org_apache_opendal_Operator_stat(
.get_string(&file)
.expect("Couldn't get java string!")
.into();
- let metadata = op.stat(&file).unwrap();
- Box::into_raw(Box::new(metadata)) as jlong
+ let result = op.stat(&file);
+ if let Err(error) = result {
+ let exception = convert_error_into_java_exception(&mut env, error).unwrap();
+ env.throw(exception).unwrap();
+ return 0 as jlong;
+ }
+ Box::into_raw(Box::new(result.unwrap())) as jlong
}
/// # Safety
@@ -270,7 +300,7 @@ 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_freeStat(
+pub unsafe extern "system" fn Java_org_apache_opendal_Metadata_freeMetadata(
mut _env: JNIEnv,
_class: JClass,
ptr: *mut opendal::Metadata,
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 281215036295..809ba7e73948 100644
--- a/bindings/java/src/main/java/org/apache/opendal/Metadata.java
+++ b/bindings/java/src/main/java/org/apache/opendal/Metadata.java
@@ -19,17 +19,7 @@
package org.apache.opendal;
-public class Metadata {
-
- long ptr;
-
- private native void freeStat(long statPtr);
-
- private native boolean isFile(long statPtr);
-
- private native long getContentLength(long statPtr);
-
-
+public class Metadata extends OpenDALObject {
public Metadata(long ptr) {
this.ptr = ptr;
}
@@ -38,12 +28,18 @@ public boolean isFile() {
return isFile(this.ptr);
}
- @Override
- protected void finalize() {
- freeStat(this.ptr);
- }
-
public long getContentLength() {
return getContentLength(this.ptr);
}
+
+ @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);
}
diff --git a/bindings/java/src/main/java/org/apache/opendal/OpenDALObject.java b/bindings/java/src/main/java/org/apache/opendal/OpenDALObject.java
new file mode 100644
index 000000000000..8a60cf9a9f0a
--- /dev/null
+++ b/bindings/java/src/main/java/org/apache/opendal/OpenDALObject.java
@@ -0,0 +1,37 @@
+/*
+ * 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 cbd7f4a2e34e..e436ed753871 100644
--- a/bindings/java/src/main/java/org/apache/opendal/Operator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/Operator.java
@@ -20,50 +20,21 @@
package org.apache.opendal;
-import io.questdb.jar.jni.JarJniLoader;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
-public class Operator {
-
- long ptr;
-
+public class Operator extends OpenDALObject {
public Operator(String schema, Map params) {
this.ptr = getOperator(schema, params);
}
- public static final String ORG_APACHE_OPENDAL_RUST_LIBS = "/org/apache/opendal/rust/libs";
-
- public static final String OPENDAL_JAVA = "opendal_java";
-
- static {
- JarJniLoader.loadLib(
- Operator.class,
- ORG_APACHE_OPENDAL_RUST_LIBS,
- OPENDAL_JAVA);
- }
-
- private native long getOperator(String type, Map params);
-
- protected native void freeOperator(long ptr);
-
- private native void write(long ptr, String fileName, String content);
-
- private native void asyncWrite(long ptr, String fileName, String content, CompletableFuture future);
-
- private native String read(long ptr, String fileName);
-
- private native void delete(long ptr, String fileName);
-
- private native long stat(long ptr, String file);
-
public void write(String fileName, String content) {
write(this.ptr, fileName, content);
}
- public CompletableFuture asyncWrite(String fileName, String content) {
+ public CompletableFuture writeAsync(String fileName, String content) {
CompletableFuture future = new CompletableFuture<>();
- asyncWrite(this.ptr, fileName, content, future);
+ writeAsync(this.ptr, fileName, content, future);
return future;
}
@@ -81,8 +52,21 @@ public Metadata stat(String fileName) {
}
@Override
- protected void finalize() throws Throwable {
- super.finalize();
+ public void close() {
this.freeOperator(ptr);
}
+
+ private native long getOperator(String type, Map params);
+
+ protected native void freeOperator(long ptr);
+
+ private native void write(long ptr, String fileName, String content);
+
+ private native void writeAsync(long ptr, String fileName, String content, CompletableFuture future);
+
+ private native String read(long ptr, String fileName);
+
+ private native void delete(long ptr, String fileName);
+
+ private native long stat(long ptr, String file);
}
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
new file mode 100644
index 000000000000..1806b908e4ee
--- /dev/null
+++ b/bindings/java/src/main/java/org/apache/opendal/exception/OpenDALErrorCode.java
@@ -0,0 +1,67 @@
+/*
+ * 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/main/java/org/apache/opendal/exception/OpenDALException.java b/bindings/java/src/main/java/org/apache/opendal/exception/OpenDALException.java
new file mode 100644
index 000000000000..104423ac90b8
--- /dev/null
+++ b/bindings/java/src/main/java/org/apache/opendal/exception/OpenDALException.java
@@ -0,0 +1,33 @@
+/*
+ * 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 class OpenDALException extends RuntimeException {
+ private final OpenDALErrorCode errorCode;
+
+ public OpenDALException(OpenDALErrorCode errorCode, String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public OpenDALErrorCode getErrorCode() {
+ return errorCode;
+ }
+}
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 76633ae51988..d34fead8e06e 100644
--- a/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java
+++ b/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java
@@ -26,10 +26,9 @@
import java.util.Map;
import java.util.concurrent.CompletableFuture;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertTrue;
public class AsyncStepsTest {
-
Operator operator;
@Given("A new OpenDAL Async Operator")
@@ -41,7 +40,7 @@ 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.asyncWrite(fileName, content);
+ CompletableFuture future = operator.writeAsync(fileName, content);
Boolean result = future.join();
assertTrue(result);
}
diff --git a/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java b/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java
new file mode 100644
index 000000000000..ce2cf3a1a7eb
--- /dev/null
+++ b/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.Map;
+import org.apache.opendal.exception.OpenDALErrorCode;
+import org.apache.opendal.exception.OpenDALException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class ExceptionTest {
+ Operator operator;
+
+ @BeforeEach
+ public void init() {
+ Map params = new HashMap<>();
+ params.put("root", "/tmp");
+ this.operator = new Operator("Memory", params);
+ }
+
+ @AfterEach
+ public void clean() {
+ this.operator.close();
+ }
+
+ @Test
+ public void testStatNotExistFile() {
+ OpenDALException exception = assertThrows(OpenDALException.class, () -> this.operator.stat("not_exist_file"));
+ assertEquals(exception.getErrorCode(), OpenDALErrorCode.NOT_FOUND);
+ }
+}
diff --git a/bindings/java/src/test/java/org/apache/opendal/StepsTest.java b/bindings/java/src/test/java/org/apache/opendal/StepsTest.java
index a4f411f0ee39..71a4a82c6af9 100644
--- a/bindings/java/src/test/java/org/apache/opendal/StepsTest.java
+++ b/bindings/java/src/test/java/org/apache/opendal/StepsTest.java
@@ -22,14 +22,14 @@
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
-
import java.util.HashMap;
import java.util.Map;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
public class StepsTest {
-
Operator operator;
@Given("A new OpenDAL Blocking Operator")
@@ -44,7 +44,6 @@ public void blocking_write_path_test_with_content_hello_world(String fileName, S
this.operator.write(fileName, content);
}
-
@Then("The blocking file {string} should exist")
public void the_blocking_file_test_should_exist(String fileName) {
Metadata metadata = this.operator.stat(fileName);
@@ -56,22 +55,17 @@ public void the_blocking_file_test_should_exist(String fileName) {
public void the_blocking_file_test_entry_mode_must_be_file(String fileName) {
Metadata metadata = this.operator.stat(fileName);
assertTrue(metadata.isFile());
-
}
@Then("The blocking file {string} content length must be {int}")
public void the_blocking_file_test_content_length_must_be_13(String fileName, int length) {
Metadata metadata = this.operator.stat(fileName);
-
assertEquals(metadata.getContentLength(), length);
}
@Then("The blocking file {string} must have content {string}")
public void the_blocking_file_test_must_have_content_hello_world(String fileName, String content) {
String readContent = this.operator.read(fileName);
-
assertEquals(content, readContent);
}
-
-
}
diff --git a/bindings/java/src/test/resources/features/binding.feature b/bindings/java/src/test/resources/features/binding.feature
new file mode 120000
index 000000000000..ad3840b3e359
--- /dev/null
+++ b/bindings/java/src/test/resources/features/binding.feature
@@ -0,0 +1 @@
+../../../../../tests/features/binding.feature
\ No newline at end of file