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, ¶ms);
+ 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