You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2023/03/24 14:54:31 UTC

[incubator-opendal] branch main updated: feat(bindings/java): add java binding (#1736)

This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 836ab994 feat(bindings/java): add java binding (#1736)
836ab994 is described below

commit 836ab9948b08ea2beb0f8325dcfedeaf6f148ebb
Author: An Li <ki...@gmail.com>
AuthorDate: Fri Mar 24 22:54:24 2023 +0800

    feat(bindings/java): add java binding (#1736)
---
 .gitignore                                         |   2 +
 Cargo.lock                                         |  36 +++++
 Cargo.toml                                         |   1 +
 Cargo.toml => bindings/java/Cargo.toml             |  23 ++-
 bindings/java/pom.xml                              |  94 +++++++++++
 bindings/java/readme.md                            |   8 +
 bindings/java/src/lib.rs                           | 176 +++++++++++++++++++++
 .../src/main/java/org/apache/opendal/Operator.java |  91 +++++++++++
 .../org/apache/opendal/operator/OperatorTest.java  |  62 ++++++++
 9 files changed, 481 insertions(+), 12 deletions(-)

diff --git a/.gitignore b/.gitignore
index c83c9045..03f8a162 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@ incubator-opendal-release-verify
 # profiling
 flamegraph.svg
 perf.*
+
+**/.DS_Store
diff --git a/Cargo.lock b/Cargo.lock
index 0cdbdbc1..6490b858 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -451,6 +451,12 @@ dependencies = [
  "jobserver",
 ]
 
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
 [[package]]
 name = "cexpr"
 version = "0.6.0"
@@ -1593,6 +1599,28 @@ version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
 
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
 [[package]]
 name = "jobserver"
 version = "0.1.26"
@@ -2227,6 +2255,14 @@ dependencies = [
  "opendal",
 ]
 
+[[package]]
+name = "opendal-java"
+version = "0.1.0"
+dependencies = [
+ "jni",
+ "opendal",
+]
+
 [[package]]
 name = "opendal-nodejs"
 version = "0.30.3"
diff --git a/Cargo.toml b/Cargo.toml
index 9522c0ae..652ff8bd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,6 +27,7 @@ members = [
   "bindings/object_store",
   "bindings/python",
   "bindings/ruby",
+  "bindings/java",
 
   "bin/oli",
 ]
diff --git a/Cargo.toml b/bindings/java/Cargo.toml
similarity index 80%
copy from Cargo.toml
copy to bindings/java/Cargo.toml
index 9522c0ae..84487340 100644
--- a/Cargo.toml
+++ b/bindings/java/Cargo.toml
@@ -15,18 +15,17 @@
 # specific language governing permissions and limitations
 # under the License.
 
-[profile.bench]
-debug = true
+[package]
+name = "opendal-java"
+version = "0.1.0"
+edition = "2021"
 
-[workspace]
-members = [
-  "core",
 
-  "bindings/c",
-  "bindings/nodejs",
-  "bindings/object_store",
-  "bindings/python",
-  "bindings/ruby",
+[lib]
+crate-type = ["cdylib"]
+doc = false
 
-  "bin/oli",
-]
+
+[dependencies]
+jni = "0.21.1"
+opendal = { version = "0.30", path = "../../core" }
diff --git a/bindings/java/pom.xml b/bindings/java/pom.xml
new file mode 100644
index 00000000..54e141ef
--- /dev/null
+++ b/bindings/java/pom.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.opendal</groupId>
+    <artifactId>java-binding</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <rust-maven-plugin.version>1.0.0</rust-maven-plugin.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.questdb</groupId>
+            <artifactId>jar-jni</artifactId>
+            <version>${rust-maven-plugin.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!--
+                The Rust Maven Plugin
+                Here it'll build the `str-reverse` crate and place the cdylib in the target's "classes" directory.
+                Placing it there will have Maven automatically bundle the compiled code in the Jar.
+            -->
+            <plugin>
+                <groupId>org.questdb</groupId>
+                <artifactId>rust-maven-plugin</artifactId>
+                <version>${rust-maven-plugin.version}</version>
+                <executions>
+                    <execution>
+                        <id>opendal-java</id>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                        <configuration>
+
+                            <path>../java</path>
+
+
+                            <release>true</release>
+
+                            <copyTo>${project.build.directory}/classes/org/apache/opendal/rust/libs</copyTo>
+
+                            <copyWithPlatformDir>true</copyWithPlatformDir>
+
+
+                            <extraArgs>
+                                <extraArg>--color=always</extraArg>
+                            </extraArgs>
+                            <environmentVariables>
+                                <REVERSED_STR_PREFIX>Great Scott, A reversed string!</REVERSED_STR_PREFIX>
+                            </environmentVariables>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/bindings/java/readme.md b/bindings/java/readme.md
new file mode 100644
index 00000000..638ca3ba
--- /dev/null
+++ b/bindings/java/readme.md
@@ -0,0 +1,8 @@
+## Todos
+
+- [ ] Readme for usage
+- [ ] Development/Contribution guide.
+- [ ] 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
diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs
new file mode 100644
index 00000000..32c537d2
--- /dev/null
+++ b/bindings/java/src/lib.rs
@@ -0,0 +1,176 @@
+// 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 std::str::FromStr;
+
+use jni::objects::{JClass, JMap, JObject, JString};
+use jni::JNIEnv;
+
+use opendal::Scheme;
+use opendal::{BlockingOperator, Operator};
+
+#[no_mangle]
+pub extern "system" fn Java_org_apache_opendal_Operator_getOperator(
+    mut env: JNIEnv,
+    _class: JClass,
+    input: JString,
+    params: JObject,
+) -> *const i32 {
+    let input: String = env
+        .get_string(&input)
+        .expect("Couldn't get java string!")
+        .into();
+    let schema = Scheme::from_str(&input).unwrap();
+    let map = convert_map(&mut env, &params);
+    let operator = build_operator(schema, map).expect("Couldn't found operator");
+    Box::into_raw(Box::new(operator)) as *const i32
+}
+
+fn convert_map(env: &mut JNIEnv, params: &JObject) -> HashMap<String, String> {
+    let mut result: HashMap<String, String> = 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(())
+        });
+    result
+}
+
+/// # 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(
+    mut _env: JNIEnv,
+    _class: JClass,
+    ptr: *mut Operator,
+) {
+    unsafe { Box::from_raw(ptr) };
+}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_Operator_write(
+    mut env: JNIEnv,
+    _class: JClass,
+    ptr: *mut BlockingOperator,
+    file: JString,
+    content: JString,
+) {
+    let op = unsafe { &mut *ptr };
+    let file: String = env
+        .get_string(&file)
+        .expect("Couldn't get java string!")
+        .into();
+    let content: String = env
+        .get_string(&content)
+        .expect("Couldn't get java string!")
+        .into();
+    op.write(&file, content).unwrap();
+}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_Operator_read<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    ptr: *mut BlockingOperator,
+    file: JString<'local>,
+) -> JString<'local> {
+    let op = unsafe { &mut *ptr };
+    let file: String = env
+        .get_string(&file)
+        .expect("Couldn't 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
+}
+
+/// # 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<'local>(
+    mut env: JNIEnv<'local>,
+    _class: JClass<'local>,
+    ptr: *mut BlockingOperator,
+    file: JString<'local>,
+) {
+    let op = unsafe { &mut *ptr };
+    let file: String = env
+        .get_string(&file)
+        .expect("Couldn't get java string!")
+        .into();
+    op.delete(&file).unwrap();
+}
+
+fn build_operator(
+    scheme: opendal::Scheme,
+    map: HashMap<String, String>,
+) -> Result<opendal::Operator, opendal::Error> {
+    use opendal::services::*;
+
+    let op = match scheme {
+        opendal::Scheme::Azblob => opendal::Operator::from_map::<Azblob>(map).unwrap().finish(),
+        opendal::Scheme::Azdfs => opendal::Operator::from_map::<Azdfs>(map).unwrap().finish(),
+        opendal::Scheme::Fs => opendal::Operator::from_map::<Fs>(map).unwrap().finish(),
+        opendal::Scheme::Gcs => opendal::Operator::from_map::<Gcs>(map).unwrap().finish(),
+        opendal::Scheme::Ghac => opendal::Operator::from_map::<Ghac>(map).unwrap().finish(),
+        opendal::Scheme::Http => opendal::Operator::from_map::<Http>(map).unwrap().finish(),
+        opendal::Scheme::Ipmfs => opendal::Operator::from_map::<Ipmfs>(map).unwrap().finish(),
+        opendal::Scheme::Memory => opendal::Operator::from_map::<Memory>(map).unwrap().finish(),
+        opendal::Scheme::Obs => opendal::Operator::from_map::<Obs>(map).unwrap().finish(),
+        opendal::Scheme::Oss => opendal::Operator::from_map::<Oss>(map).unwrap().finish(),
+        opendal::Scheme::S3 => opendal::Operator::from_map::<S3>(map).unwrap().finish(),
+        opendal::Scheme::Webdav => opendal::Operator::from_map::<Webdav>(map).unwrap().finish(),
+        opendal::Scheme::Webhdfs => opendal::Operator::from_map::<Webhdfs>(map)
+            .unwrap()
+            .finish(),
+
+        _ => {
+            return Err(opendal::Error::new(
+                opendal::ErrorKind::Unexpected,
+                "Scheme not supported",
+            ));
+        }
+    };
+
+    Ok(op)
+}
diff --git a/bindings/java/src/main/java/org/apache/opendal/Operator.java b/bindings/java/src/main/java/org/apache/opendal/Operator.java
new file mode 100644
index 00000000..96df85a2
--- /dev/null
+++ b/bindings/java/src/main/java/org/apache/opendal/Operator.java
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+// 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.Map;
+
+
+public class Operator {
+
+    long ptr;
+
+    public Operator(String schema, Map<String, String> 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<String, String> params);
+
+    private native void freeOperator(long ptr);
+
+    private native void write(long ptr, String fileName, String content);
+
+    private native String read(long ptr, String fileName);
+
+    private native void delete(long ptr, String fileName);
+
+
+    public void write(String fileName, String content) {
+        write(this.ptr, fileName, content);
+    }
+
+    public String read(String s) {
+        return read(this.ptr, s);
+    }
+
+    public void delete(String s) {
+        delete(this.ptr, s);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        this.freeOperator(ptr);
+    }
+}
diff --git a/bindings/java/src/test/java/org/apache/opendal/operator/OperatorTest.java b/bindings/java/src/test/java/org/apache/opendal/operator/OperatorTest.java
new file mode 100644
index 00000000..b72a4953
--- /dev/null
+++ b/bindings/java/src/test/java/org/apache/opendal/operator/OperatorTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+// 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.operator;
+
+import org.apache.opendal.Operator;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class OperatorTest {
+
+    @Test
+    public void testBuilder() {
+        Map<String, String> params = new HashMap<>();
+        params.put("root", "/tmp");
+        Operator op = new Operator("Memory", params);
+
+        op.write("hello1.txt", "hello world");
+        String rs = op.read("hello1.txt");
+        op.delete("hello1.txt");
+
+        Assert.assertEquals(rs, "hello world");
+
+    }
+
+}
\ No newline at end of file