diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index 97c6d0c5338..4a309701316 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -41,6 +41,7 @@ use tokio::runtime::Runtime; mod blocking_operator; mod error; mod operator; +mod utility; pub(crate) type Result = std::result::Result; diff --git a/bindings/java/src/main/java/org/apache/opendal/NativeLibrary.java b/bindings/java/src/main/java/org/apache/opendal/NativeLibrary.java new file mode 100644 index 00000000000..6825c7d8a92 --- /dev/null +++ b/bindings/java/src/main/java/org/apache/opendal/NativeLibrary.java @@ -0,0 +1,103 @@ +/* + * 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.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.concurrent.atomic.AtomicReference; +import lombok.experimental.UtilityClass; + +/** + * Utility for loading the native library. + */ +@UtilityClass +public class NativeLibrary { + private enum LibraryState { + NOT_LOADED, + LOADING, + LOADED + } + + private static final AtomicReference libraryLoaded = new AtomicReference<>(LibraryState.NOT_LOADED); + + static { + NativeLibrary.loadLibrary(); + } + + public static void loadLibrary() { + if (libraryLoaded.get() == LibraryState.LOADED) { + return; + } + + if (libraryLoaded.compareAndSet(LibraryState.NOT_LOADED, LibraryState.LOADING)) { + try { + doLoadLibrary(); + } catch (IOException e) { + libraryLoaded.set(LibraryState.NOT_LOADED); + throw new UncheckedIOException("Unable to load the OpenDAL shared library", e); + } + libraryLoaded.set(LibraryState.LOADED); + return; + } + + while (libraryLoaded.get() == LibraryState.LOADING) { + try { + Thread.sleep(10); + } catch (InterruptedException ignore) { + } + } + } + + private static void doLoadLibrary() throws IOException { + try { + // try dynamic library - the search path can be configured via "-Djava.library.path" + System.loadLibrary("opendal_java"); + return; + } catch (UnsatisfiedLinkError ignore) { + // ignore - try from classpath + } + + doLoadBundledLibrary(); + } + + private static void doLoadBundledLibrary() throws IOException { + final String libraryPath = bundledLibraryPath(); + try (final InputStream is = NativeObject.class.getResourceAsStream(libraryPath)) { + if (is == null) { + throw new IOException("cannot find " + libraryPath); + } + final int dot = libraryPath.indexOf('.'); + final File tmpFile = File.createTempFile(libraryPath.substring(0, dot), libraryPath.substring(dot)); + tmpFile.deleteOnExit(); + Files.copy(is, tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + System.load(tmpFile.getAbsolutePath()); + } + } + + private static String bundledLibraryPath() { + final String classifier = Environment.getClassifier(); + final String libraryName = System.mapLibraryName("opendal_java"); + return "/native/" + classifier + "/" + libraryName; + } +} diff --git a/bindings/java/src/main/java/org/apache/opendal/NativeObject.java b/bindings/java/src/main/java/org/apache/opendal/NativeObject.java index edd92702702..c3496ea248f 100644 --- a/bindings/java/src/main/java/org/apache/opendal/NativeObject.java +++ b/bindings/java/src/main/java/org/apache/opendal/NativeObject.java @@ -19,14 +19,6 @@ package org.apache.opendal; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.concurrent.atomic.AtomicReference; - /** * NativeObject is the base-class of all OpenDAL classes that have * a pointer to a native object. @@ -58,73 +50,8 @@ * for discussion and alternatives. */ 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)) { - try { - doLoadLibrary(); - } catch (IOException e) { - libraryLoaded.set(LibraryState.NOT_LOADED); - throw new UncheckedIOException("Unable to load the OpenDAL shared library", e); - } - libraryLoaded.set(LibraryState.LOADED); - return; - } - - while (libraryLoaded.get() == LibraryState.LOADING) { - try { - Thread.sleep(10); - } catch (InterruptedException ignore) { - } - } - } - - private static void doLoadLibrary() throws IOException { - try { - // try dynamic library - the search path can be configured via "-Djava.library.path" - System.loadLibrary("opendal_java"); - return; - } catch (UnsatisfiedLinkError ignore) { - // ignore - try from classpath - } - - doLoadBundledLibrary(); - } - - private static void doLoadBundledLibrary() throws IOException { - final String libraryPath = bundledLibraryPath(); - try (final InputStream is = NativeObject.class.getResourceAsStream(libraryPath)) { - if (is == null) { - throw new IOException("cannot find " + libraryPath); - } - final int dot = libraryPath.indexOf('.'); - final File tmpFile = File.createTempFile(libraryPath.substring(0, dot), libraryPath.substring(dot)); - tmpFile.deleteOnExit(); - Files.copy(is, tmpFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - System.load(tmpFile.getAbsolutePath()); - } - } - - private static String bundledLibraryPath() { - final String classifier = Environment.getClassifier(); - final String libraryName = System.mapLibraryName("opendal_java"); - return "/native/" + classifier + "/" + libraryName; + NativeLibrary.loadLibrary(); } /** diff --git a/bindings/java/src/main/java/org/apache/opendal/OpenDAL.java b/bindings/java/src/main/java/org/apache/opendal/OpenDAL.java new file mode 100644 index 00000000000..3c7d358557b --- /dev/null +++ b/bindings/java/src/main/java/org/apache/opendal/OpenDAL.java @@ -0,0 +1,47 @@ +/* + * 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.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import lombok.experimental.UtilityClass; + +/** + * Utility facade for top-level functions. + */ +@UtilityClass +public class OpenDAL { + static { + NativeLibrary.loadLibrary(); + final Set enabledServices = new HashSet<>(Arrays.asList(loadEnabledServices())); + ENABLED_SERVICES = Collections.unmodifiableSet(enabledServices); + } + + private static final Set ENABLED_SERVICES; + + public static Collection enabledServices() { + return ENABLED_SERVICES; + } + + private static native String[] loadEnabledServices(); +} diff --git a/bindings/java/src/test/java/org/apache/opendal/test/UtilityTest.java b/bindings/java/src/test/java/org/apache/opendal/test/UtilityTest.java new file mode 100644 index 00000000000..4d1d1222aa4 --- /dev/null +++ b/bindings/java/src/test/java/org/apache/opendal/test/UtilityTest.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.test; + +import static org.assertj.core.api.Assertions.assertThat; +import java.util.Collection; +import org.apache.opendal.OpenDAL; +import org.junit.jupiter.api.Test; + +public class UtilityTest { + @Test + public void testLoadEnabledServices() { + final Collection enabledServices = OpenDAL.enabledServices(); + assertThat(enabledServices).isNotEmpty(); + } +} diff --git a/bindings/java/src/utility.rs b/bindings/java/src/utility.rs new file mode 100644 index 00000000000..eed9f62d30b --- /dev/null +++ b/bindings/java/src/utility.rs @@ -0,0 +1,49 @@ +// 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 jni::objects::{JClass, JObject}; +use jni::sys::{jobjectArray, jsize}; +use jni::JNIEnv; +use opendal::Scheme; + +use crate::{string_to_jstring, Result}; + +/// # Safety +/// +/// This function should not be called before the Operator are ready. +#[no_mangle] +pub unsafe extern "system" fn Java_org_apache_opendal_OpenDAL_loadEnabledServices( + mut env: JNIEnv, + _: JClass, +) -> jobjectArray { + intern_load_enabled_services(&mut env).unwrap_or_else(|e| { + e.throw(&mut env); + JObject::default().into_raw() + }) +} + +fn intern_load_enabled_services(env: &mut JNIEnv) -> Result { + let services = Scheme::enabled(); + let res = env.new_object_array(services.len() as jsize, "java/lang/String", JObject::null())?; + + for (idx, service) in services.iter().enumerate() { + let srv = string_to_jstring(env, Some(&service.to_string()))?; + env.set_object_array_element(&res, idx as jsize, srv)?; + } + + Ok(res.into_raw()) +}