Skip to content

Commit

Permalink
feat(bindings/java): implement Operator#delete (#2345)
Browse files Browse the repository at this point in the history
* chore: naming

Signed-off-by: tison <wander4096@gmail.com>

* docs(bindings/java): add docs for more classes

Signed-off-by: tison <wander4096@gmail.com>

* feat(bindings/java): implement Operator#delete

Signed-off-by: tison <wander4096@gmail.com>

* docs: more improvements

Signed-off-by: tison <wander4096@gmail.com>

* fix async no await

Signed-off-by: tison <wander4096@gmail.com>

---------

Signed-off-by: tison <wander4096@gmail.com>
  • Loading branch information
tisonkun committed May 28, 2023
1 parent 93cfe7f commit 6c23a45
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 11 deletions.
2 changes: 1 addition & 1 deletion bindings/java/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Error {
&self,
env: &mut JNIEnv<'local>,
) -> jni::errors::Result<JThrowable<'local>> {
let class = env.find_class("org/apache/opendal/exception/OpenDALException")?;
let class = env.find_class("org/apache/opendal/OpenDALException")?;
let code = env.new_string(match self.inner.kind() {
ErrorKind::Unexpected => "Unexpected",
ErrorKind::Unsupported => "Unsupported",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
import java.util.Map;

/**
* A blocking operator represents an underneath OpenDAL operator that
* BlockingOperator represents an underneath OpenDAL operator that
* accesses data synchronously.
*/
public class BlockingOperator extends NativeObject {
/**
* Construct a blocking operator:
* Construct an OpenDAL blocking operator:
*
* <p>
* You can find all possible schemes <a href="https://docs.rs/opendal/latest/opendal/enum.Scheme.html">here</a>
Expand Down Expand Up @@ -67,5 +67,5 @@ public Metadata stat(String path) {

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

private static native long stat(long nativeHandle, String file);
private static native long stat(long nativeHandle, String path);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
import java.io.UncheckedIOException;
import java.util.Properties;

/**
* Environment resolves environment-specific project metadata.
*/
public enum Environment {
INSTANCE;

Expand Down
3 changes: 3 additions & 0 deletions bindings/java/src/main/java/org/apache/opendal/Metadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

package org.apache.opendal;

/**
* Metadata carries all metadata associated with a path.
*/
public class Metadata extends NativeObject {
protected Metadata(long nativeHandle) {
super(nativeHandle);
Expand Down
39 changes: 39 additions & 0 deletions bindings/java/src/main/java/org/apache/opendal/NativeObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,36 @@
import io.questdb.jar.jni.JarJniLoader;
import java.util.concurrent.atomic.AtomicReference;

/**
* NativeObject is the base-class of all OpenDAL classes that have
* a pointer to a native object.
*
* <p>
* NativeObject has the {@link NativeObject#close()} method, which frees its associated
* native object.
*
* <p>
* This function should be called manually, or even better, called implicitly using a
* <a href="https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html">try-with-resources</a>
* statement, when you are finished with the object. It is no longer called automatically
* during the regular Java GC process via {@link NativeObject#finalize()}.
*
* <p>
* <b>Explanatory note</b>
*
* <p>
* When or if the Garbage Collector calls {@link Object#finalize()}
* depends on the JVM implementation and system conditions, which the programmer
* cannot control. In addition, the GC cannot see through the native reference
* long member variable (which is the pointer value to the native object),
* and cannot know what other resources depend on it.
*
* <p>
* Finalization is deprecated and subject to removal in a future release.
* The use of finalization can lead to problems with security, performance,
* and reliability. See <a href="https://openjdk.org/jeps/421">JEP 421</a>
* for discussion and alternatives.
*/
public abstract class NativeObject implements AutoCloseable {

private enum LibraryState {
Expand Down Expand Up @@ -55,6 +85,10 @@ public static void loadLibrary() {
}
}

/**
* An immutable reference to the value of the underneath pointer pointing
* to some underlying native OpenDAL object.
*/
protected final long nativeHandle;

protected NativeObject(long nativeHandle) {
Expand All @@ -66,5 +100,10 @@ public void close() {
disposeInternal(nativeHandle);
}

/**
* Deletes underlying native object pointer.
*
* @param handle to the native object pointer
*/
protected abstract void disposeInternal(long handle);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
* under the License.
*/

package org.apache.opendal.exception;
package org.apache.opendal;

/**
* A OpenDALException encapsulates the error of an operation. This exception
* An OpenDALException encapsulates the error of an operation. This exception
* type is used to describe an internal error from the native opendal library.
*/
public class OpenDALException extends RuntimeException {
private final Code code;

/**
* Construct an OpenDALException. This is called from JNI bindings code.
* Construct an OpenDALException. This constructor is called from native code.
*
* @param code string representation of the error code
* @param message error message
Expand Down
61 changes: 59 additions & 2 deletions bindings/java/src/main/java/org/apache/opendal/Operator.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,42 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

/**
* Operator represents an underneath OpenDAL operator that
* accesses data asynchronously.
*/
public class Operator extends NativeObject {
private static AsyncRegistry registry() {
return AsyncRegistry.INSTANCE;
}

/**
* Singleton to hold all outstanding futures.
*
* <p>
* This is a trick to avoid using global references to pass {@link CompletableFuture}
* among language boundary and between multiple native threads.
*
* @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#global_references">Global References</a>
* @see <a href="https://docs.rs/jni/latest/jni/objects/struct.GlobalRef.html">jni::objects::GlobalRef</a>
*/
private enum AsyncRegistry {
INSTANCE;

private final Map<Long, CompletableFuture<?>> registry = new ConcurrentHashMap<>();

@SuppressWarnings("unused") // called by jni-rs
/**
* Request a new {@link CompletableFuture} that is associated with a unique ID.
*
* <p>
* This method is called from native code. The return ID is used by:
*
* <li>Rust side: {@link #get(long)} the future when the native async op completed</li>
* <li>Java side: {@link #take(long)} the future to compose with more actions</li>
*
* @return the request ID associated to the obtained future
*/
@SuppressWarnings("unused")
private long requestId() {
final CompletableFuture<?> f = new CompletableFuture<>();
while (true) {
Expand All @@ -46,10 +71,25 @@ private long requestId() {
}
}

/**
* Get the future associated with the request ID.
*
* <p>
* This method is called from native code.
*
* @param requestId to identify the future
* @return the future associated with the request ID
*/
private CompletableFuture<?> get(long requestId) {
return registry.get(requestId);
}

/**
* Take the future associated with the request ID.
*
* @param requestId to identify the future
* @return the future associated with the request ID
*/
@SuppressWarnings("unchecked")
private <T> CompletableFuture<T> take(long requestId) {
final CompletableFuture<?> f = get(requestId);
Expand All @@ -60,6 +100,16 @@ private <T> CompletableFuture<T> take(long requestId) {
}
}

/**
* Construct an OpenDAL operator:
*
* <p>
* You can find all possible schemes <a href="https://docs.rs/opendal/latest/opendal/enum.Scheme.html">here</a>
* and see what config options each service supports.
*
* @param schema the name of the underneath service to access data from.
* @param map a map of properties to construct the underneath operator.
*/
public Operator(String schema, Map<String, String> map) {
super(constructor(schema, map));
}
Expand All @@ -80,6 +130,11 @@ public CompletableFuture<String> read(String path) {
return registry().take(requestId);
}

public CompletableFuture<Void> delete(String path) {
final long requestId = delete(nativeHandle, path);
return registry().take(requestId);
}

@Override
protected native void disposeInternal(long handle);

Expand All @@ -89,5 +144,7 @@ public CompletableFuture<String> read(String path) {

private static native long write(long nativeHandle, String path, String content);

private static native long stat(long nativeHandle, String file);
private static native long delete(long nativeHandle, String path);

private static native long stat(long nativeHandle, String path);
}
20 changes: 20 additions & 0 deletions bindings/java/src/main/java/org/apache/opendal/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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;
35 changes: 35 additions & 0 deletions bindings/java/src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,41 @@ async fn do_read<'local>(op: &mut Operator, path: String) -> Result<JObject<'loc
Ok(result.into())
}

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

fn intern_delete(env: &mut JNIEnv, op: *mut Operator, path: JString) -> Result<jlong> {
let op = unsafe { &mut *op };
let id = request_id(env)?;

let path = env.get_string(&path)?.to_str()?.to_string();

let runtime = unsafe { RUNTIME.get_unchecked() };
runtime.spawn(async move {
let result = do_delete(op, path).await;
complete_future(id, result.map(|_| JValueOwned::Void))
});

Ok(id)
}

async fn do_delete(op: &mut Operator, path: String) -> Result<()> {
Ok(op.delete(&path).await?)
}

fn request_id(env: &mut JNIEnv) -> Result<jlong> {
let registry = env
.call_static_method(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.HashMap;
import java.util.Map;
import org.apache.opendal.exception.OpenDALException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down
60 changes: 60 additions & 0 deletions bindings/java/src/test/java/org/apache/opendal/OperatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.assertj.core.api.Assertions.assertThat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class OperatorTest {
private Operator op;

@BeforeEach
public void init() {
Map<String, String> params = new HashMap<>();
params.put("root", "/tmp");
this.op = new Operator("Memory", params);
}

@AfterEach
public void clean() {
this.op.close();
}

@Test
public void testCreateAndDelete() {
op.write("testCreateAndDelete", "Odin").join();
assertThat(op.read("testCreateAndDelete").join()).isEqualTo("Odin");
op.delete("testCreateAndDelete").join();
op.stat("testCreateAndDelete")
.handle((r, e) -> {
assertThat(r).isNull();
assertThat(e).isInstanceOf(CompletionException.class).hasCauseInstanceOf(OpenDALException.class);
OpenDALException.Code code = ((OpenDALException) e.getCause()).getCode();
assertThat(code).isEqualTo(OpenDALException.Code.NotFound);
return null;
})
.join();
}
}
2 changes: 1 addition & 1 deletion core/src/types/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use flagset::FlagSet;
use crate::raw::*;
use crate::*;

/// Metadata carries all metadata associated with an path.
/// Metadata carries all metadata associated with a path.
///
/// # Notes
///
Expand Down

0 comments on commit 6c23a45

Please sign in to comment.