diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index a671f6c6136f..e9c77ecaf434 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -46,6 +46,9 @@ default = [ "services-s3", "services-webdav", "services-webhdfs", + "layers-mime-guess", + "layers-throttle", + "layers-timeout", ] services-all = [ @@ -154,6 +157,11 @@ services-webdav = ["opendal/services-webdav"] services-webhdfs = ["opendal/services-webhdfs"] services-yandex-disk = ["opendal/services-yandex-disk"] +layers-logging = ["opendal/layers-logging"] +layers-mime-guess = ["opendal/layers-mime-guess"] +layers-throttle = ["opendal/layers-throttle"] +layers-timeout = ["opendal/layers-timeout"] + [dependencies] anyhow = { version = "1.0.100" } jni = { version = "0.21.1" } diff --git a/bindings/java/src/layer.rs b/bindings/java/src/layer.rs index c649bfa0286b..3a751ec9fbcf 100644 --- a/bindings/java/src/layer.rs +++ b/bindings/java/src/layer.rs @@ -24,7 +24,11 @@ use jni::sys::jfloat; use jni::sys::jlong; use opendal::Operator; use opendal::layers::ConcurrentLimitLayer; +use opendal::layers::LoggingLayer; +use opendal::layers::MimeGuessLayer; use opendal::layers::RetryLayer; +use opendal::layers::ThrottleLayer; +use opendal::layers::TimeoutLayer; #[unsafe(no_mangle)] pub extern "system" fn Java_org_apache_opendal_layer_RetryLayer_doLayer( @@ -62,3 +66,53 @@ pub extern "system" fn Java_org_apache_opendal_layer_ConcurrentLimitLayer_doLaye let concurrent_limit = ConcurrentLimitLayer::new(permits as usize); Box::into_raw(Box::new(op.clone().layer(concurrent_limit))) as jlong } + +#[unsafe(no_mangle)] +pub extern "system" fn Java_org_apache_opendal_layer_MimeGuessLayer_doLayer( + _: JNIEnv, + _: JClass, + op: *mut Operator, +) -> jlong { + let op = unsafe { &*op }; + let mime_guess = MimeGuessLayer::new(); + Box::into_raw(Box::new(op.clone().layer(mime_guess))) as jlong +} + +#[unsafe(no_mangle)] +pub extern "system" fn Java_org_apache_opendal_layer_TimeoutLayer_doLayer( + _: JNIEnv, + _: JClass, + op: *mut Operator, + timeout_nanos: jlong, + io_timeout_nanos: jlong, +) -> jlong { + let op = unsafe { &*op }; + let timeout = TimeoutLayer::new() + .with_timeout(Duration::from_nanos(timeout_nanos as u64)) + .with_io_timeout(Duration::from_nanos(io_timeout_nanos as u64)); + Box::into_raw(Box::new(op.clone().layer(timeout))) as jlong +} + +#[unsafe(no_mangle)] +pub extern "system" fn Java_org_apache_opendal_layer_LoggingLayer_doLayer( + _: JNIEnv, + _: JClass, + op: *mut Operator, +) -> jlong { + let op = unsafe { &*op }; + let logging = LoggingLayer::default(); + Box::into_raw(Box::new(op.clone().layer(logging))) as jlong +} + +#[unsafe(no_mangle)] +pub extern "system" fn Java_org_apache_opendal_layer_ThrottleLayer_doLayer( + _: JNIEnv, + _: JClass, + op: *mut Operator, + bandwidth: jlong, + burst: jlong, +) -> jlong { + let op = unsafe { &*op }; + let throttle = ThrottleLayer::new(bandwidth as u32, burst as u32); + Box::into_raw(Box::new(op.clone().layer(throttle))) as jlong +} diff --git a/bindings/java/src/main/java/org/apache/opendal/layer/LoggingLayer.java b/bindings/java/src/main/java/org/apache/opendal/layer/LoggingLayer.java new file mode 100644 index 000000000000..202d291d6720 --- /dev/null +++ b/bindings/java/src/main/java/org/apache/opendal/layer/LoggingLayer.java @@ -0,0 +1,45 @@ +/* + * 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.layer; + +import org.apache.opendal.Layer; + +/** + * This layer adds structured logging for every operation. + * + *

Note: This layer requires proper logging setup (e.g., log4j2, java.util.logging) to work. + * Without logging configuration, this layer is a no-op. + * + * @see LoggingLayer's rustdoc + */ +public class LoggingLayer extends Layer { + + /** + * Create a new LoggingLayer. + */ + public LoggingLayer() {} + + @Override + protected long layer(long nativeOp) { + return doLayer(nativeOp); + } + + private static native long doLayer(long nativeHandle); +} diff --git a/bindings/java/src/main/java/org/apache/opendal/layer/MimeGuessLayer.java b/bindings/java/src/main/java/org/apache/opendal/layer/MimeGuessLayer.java new file mode 100644 index 000000000000..61e210fdc1bb --- /dev/null +++ b/bindings/java/src/main/java/org/apache/opendal/layer/MimeGuessLayer.java @@ -0,0 +1,42 @@ +/* + * 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.layer; + +import org.apache.opendal.Layer; + +/** + * This layer will automatically set Content-Type based on the file extension in the path. + * + * @see MimeGuessLayer's rustdoc + */ +public class MimeGuessLayer extends Layer { + + /** + * Create a new MimeGuessLayer. + */ + public MimeGuessLayer() {} + + @Override + protected long layer(long nativeOp) { + return doLayer(nativeOp); + } + + private static native long doLayer(long nativeHandle); +} diff --git a/bindings/java/src/main/java/org/apache/opendal/layer/ThrottleLayer.java b/bindings/java/src/main/java/org/apache/opendal/layer/ThrottleLayer.java new file mode 100644 index 000000000000..3f0521df5f44 --- /dev/null +++ b/bindings/java/src/main/java/org/apache/opendal/layer/ThrottleLayer.java @@ -0,0 +1,58 @@ +/* + * 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.layer; + +import org.apache.opendal.Layer; + +/** + * This layer adds a bandwidth rate limiter to the underlying services. + * + * @see ThrottleLayer's rustdoc + */ +public class ThrottleLayer extends Layer { + + private final long bandwidth; + + private final long burst; + + /** + * Create a new ThrottleLayer with given bandwidth and burst. + * + * @param bandwidth the maximum number of bytes allowed to pass through per second + * @param burst the maximum number of bytes allowed to pass through at once + */ + public ThrottleLayer(long bandwidth, long burst) { + if (bandwidth <= 0) { + throw new IllegalArgumentException("bandwidth must be positive"); + } + if (burst <= 0) { + throw new IllegalArgumentException("burst must be positive"); + } + this.bandwidth = bandwidth; + this.burst = burst; + } + + @Override + protected long layer(long nativeOp) { + return doLayer(nativeOp, bandwidth, burst); + } + + private static native long doLayer(long nativeHandle, long bandwidth, long burst); +} diff --git a/bindings/java/src/main/java/org/apache/opendal/layer/TimeoutLayer.java b/bindings/java/src/main/java/org/apache/opendal/layer/TimeoutLayer.java new file mode 100644 index 000000000000..540c56f8cc2e --- /dev/null +++ b/bindings/java/src/main/java/org/apache/opendal/layer/TimeoutLayer.java @@ -0,0 +1,82 @@ +/* + * 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.layer; + +import java.time.Duration; +import org.apache.opendal.Layer; + +/** + * This layer adds timeout for every operation to avoid slow or unexpected hang operations. + * + * @see TimeoutLayer's rustdoc + */ +public class TimeoutLayer extends Layer { + + private final Duration timeout; + + private final Duration ioTimeout; + + /** + * Create a new TimeoutLayer with default settings. + * Default timeout: 60 seconds for non-IO operations, 10 seconds for IO operations. + */ + public TimeoutLayer() { + this.timeout = Duration.ofSeconds(60); + this.ioTimeout = Duration.ofSeconds(10); + } + + /** + * Create a new TimeoutLayer with custom timeout settings. + * + * @param timeout timeout for non-IO operations (stat, delete, etc.) + * @param ioTimeout timeout for IO operations (read, write, etc.) + */ + public TimeoutLayer(Duration timeout, Duration ioTimeout) { + this.timeout = timeout; + this.ioTimeout = ioTimeout; + } + + /** + * Set timeout for non-IO operations. + * + * @param timeout the timeout duration + * @return this TimeoutLayer for chaining + */ + public TimeoutLayer withTimeout(Duration timeout) { + return new TimeoutLayer(timeout, this.ioTimeout); + } + + /** + * Set timeout for IO operations. + * + * @param ioTimeout the IO timeout duration + * @return this TimeoutLayer for chaining + */ + public TimeoutLayer withIoTimeout(Duration ioTimeout) { + return new TimeoutLayer(this.timeout, ioTimeout); + } + + @Override + protected long layer(long nativeOp) { + return doLayer(nativeOp, timeout.toNanos(), ioTimeout.toNanos()); + } + + private static native long doLayer(long nativeHandle, long timeout, long ioTimeout); +} diff --git a/bindings/java/src/test/java/org/apache/opendal/test/LayerTest.java b/bindings/java/src/test/java/org/apache/opendal/test/LayerTest.java index 98b7ff1a9b6c..43d630125d11 100644 --- a/bindings/java/src/test/java/org/apache/opendal/test/LayerTest.java +++ b/bindings/java/src/test/java/org/apache/opendal/test/LayerTest.java @@ -25,7 +25,11 @@ import org.apache.opendal.Layer; import org.apache.opendal.ServiceConfig; import org.apache.opendal.layer.ConcurrentLimitLayer; +import org.apache.opendal.layer.LoggingLayer; +import org.apache.opendal.layer.MimeGuessLayer; import org.apache.opendal.layer.RetryLayer; +import org.apache.opendal.layer.ThrottleLayer; +import org.apache.opendal.layer.TimeoutLayer; import org.junit.jupiter.api.Test; public class LayerTest { @@ -48,4 +52,44 @@ void testOperatorWithConcurrentLimitLayer() { @Cleanup final AsyncOperator layeredOp = op.layer(concurrentLimitLayer); assertThat(layeredOp.info).isNotNull(); } + + @Test + void testOperatorWithMimeGuessLayer() { + final ServiceConfig.Memory memory = + ServiceConfig.Memory.builder().root("/opendal/").build(); + final Layer mimeGuessLayer = new MimeGuessLayer(); + @Cleanup final AsyncOperator op = AsyncOperator.of(memory); + @Cleanup final AsyncOperator layeredOp = op.layer(mimeGuessLayer); + assertThat(layeredOp.info).isNotNull(); + } + + @Test + void testOperatorWithTimeoutLayer() { + final ServiceConfig.Memory memory = + ServiceConfig.Memory.builder().root("/opendal/").build(); + final Layer timeoutLayer = new TimeoutLayer(); + @Cleanup final AsyncOperator op = AsyncOperator.of(memory); + @Cleanup final AsyncOperator layeredOp = op.layer(timeoutLayer); + assertThat(layeredOp.info).isNotNull(); + } + + @Test + void testOperatorWithLoggingLayer() { + final ServiceConfig.Memory memory = + ServiceConfig.Memory.builder().root("/opendal/").build(); + final Layer loggingLayer = new LoggingLayer(); + @Cleanup final AsyncOperator op = AsyncOperator.of(memory); + @Cleanup final AsyncOperator layeredOp = op.layer(loggingLayer); + assertThat(layeredOp.info).isNotNull(); + } + + @Test + void testOperatorWithThrottleLayer() { + final ServiceConfig.Memory memory = + ServiceConfig.Memory.builder().root("/opendal/").build(); + final Layer throttleLayer = new ThrottleLayer(1024, 1024); + @Cleanup final AsyncOperator op = AsyncOperator.of(memory); + @Cleanup final AsyncOperator layeredOp = op.layer(throttleLayer); + assertThat(layeredOp.info).isNotNull(); + } }