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/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 0c44e07c4 feat(java): add WriteOptions for write methods (#5664)
0c44e07c4 is described below

commit 0c44e07c49f65a10cc4f6d56c377e851abb34876
Author: Drew Gallardo <[email protected]>
AuthorDate: Mon Mar 3 06:00:19 2025 -0800

    feat(java): add WriteOptions for write methods (#5664)
    
    * feat(java): add WriteOptions for write methods
    
    * Address feedback
    
    * use write options on first append in test
    
    * Fix other test
---
 bindings/java/src/async_operator.rs                |  60 +++++++++--
 bindings/java/src/convert.rs                       |  30 ++++++
 bindings/java/src/lib.rs                           |   6 +-
 .../java/org/apache/opendal/AsyncOperator.java     |  18 +++-
 .../main/java/org/apache/opendal/Capability.java   |  29 ++++++
 .../src/main/java/org/apache/opendal/Operator.java |  12 ++-
 .../main/java/org/apache/opendal/WriteOptions.java |  88 ++++++++++++++++
 bindings/java/src/operator.rs                      |  28 ++++-
 .../test/behavior/AsyncWriteOptionsTest.java       | 116 +++++++++++++++++++++
 .../test/behavior/BlockingWriteOptionTest.java     |  92 ++++++++++++++++
 10 files changed, 461 insertions(+), 18 deletions(-)

diff --git a/bindings/java/src/async_operator.rs 
b/bindings/java/src/async_operator.rs
index 239b97b89..5f4721742 100644
--- a/bindings/java/src/async_operator.rs
+++ b/bindings/java/src/async_operator.rs
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use std::future::Future;
 use std::str::FromStr;
 use std::time::Duration;
 
@@ -29,12 +30,15 @@ use jni::sys::jobject;
 use jni::sys::jsize;
 use jni::JNIEnv;
 use opendal::layers::BlockingLayer;
+use opendal::operator_futures::FutureWrite;
 use opendal::raw::PresignedRequest;
-use opendal::Operator;
 use opendal::Scheme;
+use opendal::{Metadata, Operator};
 
-use crate::convert::jmap_to_hashmap;
 use crate::convert::jstring_to_string;
+use crate::convert::{
+    get_optional_map_from_object, get_optional_string_from_object, 
jmap_to_hashmap,
+};
 use crate::executor::executor_or_default;
 use crate::executor::get_current_env;
 use crate::executor::Executor;
@@ -110,8 +114,9 @@ pub unsafe extern "system" fn 
Java_org_apache_opendal_AsyncOperator_write(
     executor: *const Executor,
     path: JString,
     content: JByteArray,
+    write_options: JObject,
 ) -> jlong {
-    intern_write(&mut env, op, executor, path, content).unwrap_or_else(|e| {
+    intern_write(&mut env, op, executor, path, content, 
write_options).unwrap_or_else(|e| {
         e.throw(&mut env);
         0
     })
@@ -123,25 +128,62 @@ fn intern_write(
     executor: *const Executor,
     path: JString,
     content: JByteArray,
+    options: JObject,
 ) -> Result<jlong> {
     let op = unsafe { &mut *op };
     let id = request_id(env)?;
 
     let path = jstring_to_string(env, &path)?;
     let content = env.convert_byte_array(content)?;
+    let content_type = get_optional_string_from_object(env, &options, 
"getContentType")?;
+    let content_disposition =
+        get_optional_string_from_object(env, &options, 
"getContentDisposition")?;
+    let content_encoding = get_optional_string_from_object(env, &options, 
"getContentEncoding")?;
+    let cache_control = get_optional_string_from_object(env, &options, 
"getCacheControl")?;
+    let if_match = get_optional_string_from_object(env, &options, 
"getIfMatch")?;
+    let if_none_match = get_optional_string_from_object(env, &options, 
"getIfNoneMatch")?;
+    let append = env.call_method(&options, "isAppend", "()Z", &[])?.z()?;
+    let if_not_exists = env
+        .call_method(&options, "isIfNotExists", "()Z", &[])?
+        .z()?;
+    let user_metadata = get_optional_map_from_object(env, &options, 
"getUserMetadata");
+
+    let mut write_op = op.write_with(&path, content);
+    if let Some(ct) = content_type {
+        write_op = write_op.content_type(&ct);
+    }
+    if let Some(cd) = content_disposition {
+        write_op = write_op.content_disposition(&cd);
+    }
+    if let Some(ce) = content_encoding {
+        write_op = write_op.content_encoding(&ce);
+    }
+    if let Some(cc) = cache_control {
+        write_op = write_op.cache_control(&cc);
+    }
+    if let Some(im) = if_match {
+        write_op = write_op.if_match(&im);
+    }
+    if let Some(inm) = if_none_match {
+        write_op = write_op.if_none_match(&inm);
+    }
+    if let Ok(Some(um)) = user_metadata {
+        write_op = write_op.user_metadata(um);
+    }
+    write_op = write_op.if_not_exists(if_not_exists);
+    write_op = write_op.append(append);
 
     executor_or_default(env, executor)?.spawn(async move {
-        let result = do_write(op, path, content).await;
-        complete_future(id, result.map(|_| JValueOwned::Void))
+        let result = write_op
+            .await
+            .map(|_| JValueOwned::Void)
+            .map_err(|e| e.into());
+        complete_future(id, result)
     });
 
     Ok(id)
 }
 
-async fn do_write(op: &mut Operator, path: String, content: Vec<u8>) -> 
Result<()> {
-    Ok(op.write(&path, content).await.map(|_| ())?)
-}
-
 /// # Safety
 ///
 /// This function should not be called before the Operator is ready.
diff --git a/bindings/java/src/convert.rs b/bindings/java/src/convert.rs
index e347fffc8..c0af00334 100644
--- a/bindings/java/src/convert.rs
+++ b/bindings/java/src/convert.rs
@@ -69,6 +69,36 @@ pub(crate) fn string_to_jstring<'a>(
     )
 }
 
+pub(crate) fn get_optional_string_from_object<'a>(
+    env: &mut JNIEnv<'a>,
+    obj: &JObject,
+    method: &str,
+) -> crate::Result<Option<String>> {
+    let result = env
+        .call_method(obj, method, "()Ljava/lang/String;", &[])?
+        .l()?;
+    if result.is_null() {
+        Ok(None)
+    } else {
+        Ok(Some(jstring_to_string(env, &JString::from(result))?))
+    }
+}
+
+pub(crate) fn get_optional_map_from_object<'a>(
+    env: &mut JNIEnv<'a>,
+    obj: &JObject,
+    method: &str,
+) -> crate::Result<Option<HashMap<String, String>>> {
+    let result = env
+        .call_method(obj, method, "()Ljava/util/Map;", &[])?
+        .l()?;
+    if result.is_null() {
+        Ok(None)
+    } else {
+        Ok(Some(jmap_to_hashmap(env, &result)?))
+    }
+}
+
 /// # Safety
 ///
 /// The caller must guarantee that the Object passed in is an instance
diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs
index 45a0554ed..e67d7f479 100644
--- a/bindings/java/src/lib.rs
+++ b/bindings/java/src/lib.rs
@@ -94,7 +94,7 @@ fn make_operator_info<'a>(env: &mut JNIEnv<'a>, info: 
OperatorInfo) -> Result<JO
 fn make_capability<'a>(env: &mut JNIEnv<'a>, cap: Capability) -> 
Result<JObject<'a>> {
     let capability = env.new_object(
         "org/apache/opendal/Capability",
-        "(ZZZZZZZZZZZZZZZJJZZZZZZZZZZZZZZ)V",
+        "(ZZZZZZZZZZZZZZZZZZZJJZZZZZZZZZZZZZZ)V",
         &[
             JValue::Bool(cap.stat as jboolean),
             JValue::Bool(cap.stat_with_if_match as jboolean),
@@ -111,6 +111,10 @@ fn make_capability<'a>(env: &mut JNIEnv<'a>, cap: 
Capability) -> Result<JObject<
             JValue::Bool(cap.write_with_content_type as jboolean),
             JValue::Bool(cap.write_with_content_disposition as jboolean),
             JValue::Bool(cap.write_with_cache_control as jboolean),
+            JValue::Bool(cap.write_with_if_match as jboolean),
+            JValue::Bool(cap.write_with_if_none_match as jboolean),
+            JValue::Bool(cap.write_with_if_not_exists as jboolean),
+            JValue::Bool(cap.write_with_user_metadata as jboolean),
             JValue::Long(convert::usize_to_jlong(cap.write_multi_max_size)),
             JValue::Long(convert::usize_to_jlong(cap.write_multi_min_size)),
             JValue::Bool(cap.create_dir as jboolean),
diff --git a/bindings/java/src/main/java/org/apache/opendal/AsyncOperator.java 
b/bindings/java/src/main/java/org/apache/opendal/AsyncOperator.java
index cd6b67236..cbb32b9f8 100644
--- a/bindings/java/src/main/java/org/apache/opendal/AsyncOperator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/AsyncOperator.java
@@ -190,11 +190,22 @@ public class AsyncOperator extends NativeObject {
     }
 
     public CompletableFuture<Void> write(String path, String content) {
-        return write(path, content.getBytes(StandardCharsets.UTF_8));
+        return write(
+                path,
+                content.getBytes(StandardCharsets.UTF_8),
+                WriteOptions.builder().build());
     }
 
     public CompletableFuture<Void> write(String path, byte[] content) {
-        final long requestId = write(nativeHandle, executorHandle, path, 
content);
+        return write(path, content, WriteOptions.builder().build());
+    }
+
+    public CompletableFuture<Void> write(String path, String content, 
WriteOptions options) {
+        return write(path, content.getBytes(StandardCharsets.UTF_8), options);
+    }
+
+    public CompletableFuture<Void> write(String path, byte[] content, 
WriteOptions options) {
+        final long requestId = write(nativeHandle, executorHandle, path, 
content, options);
         return AsyncRegistry.take(requestId);
     }
 
@@ -272,7 +283,8 @@ public class AsyncOperator extends NativeObject {
 
     private static native long read(long nativeHandle, long executorHandle, 
String path);
 
-    private static native long write(long nativeHandle, long executorHandle, 
String path, byte[] content);
+    private static native long write(
+            long nativeHandle, long executorHandle, String path, byte[] 
content, WriteOptions options);
 
     private static native long append(long nativeHandle, long executorHandle, 
String path, byte[] content);
 
diff --git a/bindings/java/src/main/java/org/apache/opendal/Capability.java 
b/bindings/java/src/main/java/org/apache/opendal/Capability.java
index 159a03d61..668156c89 100644
--- a/bindings/java/src/main/java/org/apache/opendal/Capability.java
+++ b/bindings/java/src/main/java/org/apache/opendal/Capability.java
@@ -98,6 +98,27 @@ public class Capability {
      */
     public final boolean writeWithCacheControl;
 
+    /**
+     * If operator supports write with if match.
+     */
+    public final boolean writeWithIfMatch;
+
+    /**
+     * If operator supports write with if none match.
+     *
+     */
+    public final boolean writeWithIfNoneMatch;
+
+    /**
+     * If operator supports write with if not exists.
+     */
+    public final boolean writeWithIfNotExists;
+
+    /**
+     * If operator supports write with user metadata.
+     */
+    public final boolean writeWithUserMetadata;
+
     /**
      * write_multi_max_size is the max size that services support in 
write_multi.
      * For example, AWS S3 supports 5GiB as max in write_multi.
@@ -196,6 +217,10 @@ public class Capability {
             boolean writeWithContentType,
             boolean writeWithContentDisposition,
             boolean writeWithCacheControl,
+            boolean writeWithIfMatch,
+            boolean writeWithIfNoneMatch,
+            boolean writeWithIfNotExists,
+            boolean writeWithUserMetadata,
             long writeMultiMaxSize,
             long writeMultiMinSize,
             boolean createDir,
@@ -227,6 +252,10 @@ public class Capability {
         this.writeWithContentType = writeWithContentType;
         this.writeWithContentDisposition = writeWithContentDisposition;
         this.writeWithCacheControl = writeWithCacheControl;
+        this.writeWithIfMatch = writeWithIfMatch;
+        this.writeWithIfNoneMatch = writeWithIfNoneMatch;
+        this.writeWithIfNotExists = writeWithIfNotExists;
+        this.writeWithUserMetadata = writeWithUserMetadata;
         this.writeMultiMaxSize = writeMultiMaxSize;
         this.writeMultiMinSize = writeMultiMinSize;
         this.createDir = createDir;
diff --git a/bindings/java/src/main/java/org/apache/opendal/Operator.java 
b/bindings/java/src/main/java/org/apache/opendal/Operator.java
index be496d2cb..9489509ba 100644
--- a/bindings/java/src/main/java/org/apache/opendal/Operator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/Operator.java
@@ -76,7 +76,15 @@ public class Operator extends NativeObject {
     }
 
     public void write(String path, byte[] content) {
-        write(nativeHandle, path, content);
+        write(nativeHandle, path, content, WriteOptions.builder().build());
+    }
+
+    public void write(String path, String content, WriteOptions options) {
+        write(path, content.getBytes(StandardCharsets.UTF_8), options);
+    }
+
+    public void write(String path, byte[] content, WriteOptions options) {
+        write(nativeHandle, path, content, options);
     }
 
     public OperatorOutputStream createOutputStream(String path) {
@@ -128,7 +136,7 @@ public class Operator extends NativeObject {
 
     private static native long duplicate(long op);
 
-    private static native void write(long op, String path, byte[] content);
+    private static native void write(long op, String path, byte[] content, 
WriteOptions options);
 
     private static native byte[] read(long op, String path);
 
diff --git a/bindings/java/src/main/java/org/apache/opendal/WriteOptions.java 
b/bindings/java/src/main/java/org/apache/opendal/WriteOptions.java
new file mode 100644
index 000000000..82a7cea65
--- /dev/null
+++ b/bindings/java/src/main/java/org/apache/opendal/WriteOptions.java
@@ -0,0 +1,88 @@
+/*
+ * 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.Map;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class WriteOptions {
+
+    /**
+     * Sets the Content-Type header for the object.
+     * Requires capability: writeWithContentType
+     */
+    private String contentType;
+
+    /**
+     * Sets the Content-Disposition header for the object
+     * Requires capability: writeWithContentDisposition
+     */
+    private String contentDisposition;
+
+    /**
+     * Sets the Cache-Control header for the object
+     * Requires capability: writeWithCacheControl
+     */
+    private String cacheControl;
+
+    /**
+     * Sets the Content-Encoding header for the object
+     */
+    private String contentEncoding;
+
+    /**
+     * Sets the If-Match header for conditional writes
+     * Requires capability: writeWithIfMatch
+     */
+    private String ifMatch;
+
+    /**
+     * Sets the If-None-Match header for conditional writes
+     * Requires capability: writeWithIfNoneMatch
+     */
+    private String ifNoneMatch;
+
+    /**
+     * Sets custom metadata for the file.
+     * Requires capability: writeWithUserMetadata
+     */
+    private Map<String, String> userMetadata;
+
+    /**
+     * Enables append mode for writing.
+     * When true, data will be appended to the end of existing file.
+     * Requires capability: writeCanAppend
+     */
+    private boolean append;
+
+    /**
+     * Write only if the file does not exist.
+     * Operation will fail if the file at the designated path already exists.
+     * Requires capability: writeWithIfNotExists
+     */
+    private boolean ifNotExists;
+}
diff --git a/bindings/java/src/operator.rs b/bindings/java/src/operator.rs
index 9e457feda..db0280129 100644
--- a/bindings/java/src/operator.rs
+++ b/bindings/java/src/operator.rs
@@ -27,7 +27,7 @@ use jni::sys::jsize;
 use jni::JNIEnv;
 use opendal::BlockingOperator;
 
-use crate::convert::jstring_to_string;
+use crate::convert::{get_optional_string_from_object, jstring_to_string};
 use crate::make_entry;
 use crate::make_metadata;
 use crate::Result;
@@ -90,8 +90,9 @@ pub unsafe extern "system" fn 
Java_org_apache_opendal_Operator_write(
     op: *mut BlockingOperator,
     path: JString,
     content: JByteArray,
+    write_options: JObject,
 ) {
-    intern_write(&mut env, &mut *op, path, content).unwrap_or_else(|e| {
+    intern_write(&mut env, &mut *op, path, content, 
write_options).unwrap_or_else(|e| {
         e.throw(&mut env);
     })
 }
@@ -101,10 +102,31 @@ fn intern_write(
     op: &mut BlockingOperator,
     path: JString,
     content: JByteArray,
+    write_options: JObject,
 ) -> Result<()> {
     let path = jstring_to_string(env, &path)?;
     let content = env.convert_byte_array(content)?;
-    Ok(op.write(&path, content).map(|_| ())?)
+
+    let content_type = get_optional_string_from_object(env, &write_options, 
"getContentType")?;
+    let content_disposition =
+        get_optional_string_from_object(env, &write_options, 
"getContentDisposition")?;
+    let cache_control = get_optional_string_from_object(env, &write_options, 
"getCacheControl")?;
+    let append = env
+        .call_method(&write_options, "isAppend", "()Z", &[])?
+        .z()?;
+
+    let mut write_op = op.write_with(&path, content);
+    if let Some(ct) = content_type {
+        write_op = write_op.content_type(&ct);
+    }
+    if let Some(cd) = content_disposition {
+        write_op = write_op.content_disposition(&cd);
+    }
+    if let Some(cc) = cache_control {
+        write_op = write_op.cache_control(&cc);
+    }
+    write_op = write_op.append(append);
+    Ok(write_op.call().map(|_| ())?)
 }
 
 /// # Safety
diff --git 
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/AsyncWriteOptionsTest.java
 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/AsyncWriteOptionsTest.java
new file mode 100644
index 000000000..537b25d15
--- /dev/null
+++ 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/AsyncWriteOptionsTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.behavior;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import java.util.UUID;
+import org.apache.opendal.OpenDALException.Code;
+import org.apache.opendal.WriteOptions;
+import org.apache.opendal.test.condition.OpenDALExceptionCondition;
+import org.junit.jupiter.api.Test;
+
+public class AsyncWriteOptionsTest extends BehaviorTestBase {
+
+    @Test
+    void testIfNotExists() {
+        assumeTrue(asyncOp().info.fullCapability.writeWithIfNotExists);
+        final String path = UUID.randomUUID().toString();
+        final byte[] content = generateBytes();
+        WriteOptions options = 
WriteOptions.builder().ifNotExists(true).build();
+        asyncOp().write(path, content, options).join();
+
+        assertThatThrownBy(() -> asyncOp().write(path, content, 
options).join())
+                .is(OpenDALExceptionCondition.ofAsync(Code.ConditionNotMatch));
+    }
+
+    @Test
+    void testWriteWithCacheControl() {
+        assumeTrue(asyncOp().info.fullCapability.writeWithCacheControl);
+        final String path = UUID.randomUUID().toString();
+        final byte[] content = generateBytes();
+        final String cacheControl = "max-age=3600";
+
+        WriteOptions options = 
WriteOptions.builder().cacheControl(cacheControl).build();
+
+        asyncOp().write(path, content, options).join();
+
+        String actualCacheControl = 
asyncOp().stat(path).join().getCacheControl();
+        assertThat(actualCacheControl).isEqualTo(cacheControl);
+    }
+
+    @Test
+    void testWriteWithIfNoneMatch() {
+        assumeTrue(asyncOp().info.fullCapability.writeWithIfNoneMatch);
+        final String path = UUID.randomUUID().toString();
+        final byte[] content = generateBytes();
+
+        asyncOp().write(path, content).join();
+        String etag = asyncOp().stat(path).join().getEtag();
+
+        WriteOptions options = 
WriteOptions.builder().ifNoneMatch(etag).build();
+
+        assertThatThrownBy(() -> asyncOp().write(path, content, 
options).join())
+                .is(OpenDALExceptionCondition.ofAsync(Code.ConditionNotMatch));
+    }
+
+    @Test
+    void testWriteWithIfMatch() {
+        assumeTrue(asyncOp().info.fullCapability.writeWithIfMatch);
+
+        final String pathA = UUID.randomUUID().toString();
+        final String pathB = UUID.randomUUID().toString();
+        final byte[] contentA = generateBytes();
+        final byte[] contentB = generateBytes();
+
+        asyncOp().write(pathA, contentA).join();
+        asyncOp().write(pathB, contentB).join();
+
+        String etagA = asyncOp().stat(pathA).join().getEtag();
+        String etagB = asyncOp().stat(pathB).join().getEtag();
+
+        WriteOptions optionsA = WriteOptions.builder().ifMatch(etagA).build();
+
+        asyncOp().write(pathA, contentA, optionsA).join();
+
+        WriteOptions optionsB = WriteOptions.builder().ifMatch(etagB).build();
+
+        assertThatThrownBy(() -> asyncOp().write(pathA, contentA, 
optionsB).join())
+                .is(OpenDALExceptionCondition.ofAsync(Code.ConditionNotMatch));
+    }
+
+    @Test
+    void testWriteWithAppend() {
+        assumeTrue(asyncOp().info.fullCapability.writeCanAppend);
+
+        final String path = UUID.randomUUID().toString();
+        final byte[] contentOne = "Test".getBytes();
+        final byte[] contentTwo = " Data".getBytes();
+
+        WriteOptions appendOptions = 
WriteOptions.builder().append(true).build();
+        asyncOp().write(path, contentOne, appendOptions).join();
+        asyncOp().write(path, contentTwo, appendOptions).join();
+
+        byte[] result = asyncOp().read(path).join();
+        assertThat(result.length).isEqualTo(contentOne.length + 
contentTwo.length);
+        assertThat(result).isEqualTo("Test Data".getBytes());
+    }
+}
diff --git 
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/BlockingWriteOptionTest.java
 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/BlockingWriteOptionTest.java
new file mode 100644
index 000000000..973f63a0e
--- /dev/null
+++ 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/BlockingWriteOptionTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.behavior;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+import java.util.UUID;
+import org.apache.opendal.WriteOptions;
+import org.junit.jupiter.api.Test;
+
+public class BlockingWriteOptionTest extends BehaviorTestBase {
+
+    @Test
+    void testWriteWithCacheControl() {
+        assumeTrue(op().info.fullCapability.writeWithCacheControl);
+
+        final String path = UUID.randomUUID().toString();
+        final byte[] content = generateBytes();
+        final String cacheControl = "max-age=3600";
+
+        WriteOptions options = 
WriteOptions.builder().cacheControl(cacheControl).build();
+        op().write(path, content, options);
+
+        String actualCacheControl = op().stat(path).getCacheControl();
+        assertThat(actualCacheControl).isEqualTo(cacheControl);
+    }
+
+    @Test
+    void testWriteWithContentType() {
+        assumeTrue(op().info.fullCapability.writeWithContentType);
+
+        final String path = UUID.randomUUID().toString();
+        final byte[] content = generateBytes();
+        final String contentType = "application/json";
+
+        WriteOptions options = 
WriteOptions.builder().contentType(contentType).build();
+        op().write(path, content, options);
+
+        String actualContentType = op().stat(path).getContentType();
+        assertThat(actualContentType).isEqualTo(contentType);
+    }
+
+    @Test
+    void testWriteWithContentDisposition() {
+        assumeTrue(op().info.fullCapability.writeWithContentDisposition);
+
+        final String path = UUID.randomUUID().toString();
+        final byte[] content = generateBytes();
+        final String disposition = "attachment; filename=\"test.txt\"";
+
+        WriteOptions options =
+                WriteOptions.builder().contentDisposition(disposition).build();
+        op().write(path, content, options);
+
+        String actualDisposition = op().stat(path).getContentDisposition();
+        assertThat(actualDisposition).isEqualTo(disposition);
+    }
+
+    @Test
+    void testWriteWithAppend() {
+        assumeTrue(op().info.fullCapability.writeCanAppend);
+
+        final String path = UUID.randomUUID().toString();
+        final byte[] contentOne = "Test".getBytes();
+        final byte[] contentTwo = " Data".getBytes();
+        WriteOptions appendOptions = 
WriteOptions.builder().append(true).build();
+
+        op().write(path, contentOne, appendOptions);
+        op().write(path, contentTwo, appendOptions);
+
+        byte[] result = op().read(path);
+        assertThat(result.length).isEqualTo(contentOne.length + 
contentTwo.length);
+        assertThat(result).isEqualTo("Test Data".getBytes());
+    }
+}

Reply via email to