Skip to content

Commit a612f32

Browse files
authored
feat(bindings/java): Add StatOptions support for new options API (#6255)
1 parent 0a58ea1 commit a612f32

File tree

11 files changed

+574
-12
lines changed

11 files changed

+574
-12
lines changed

bindings/java/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ opendal = { version = ">=0", path = "../../core", features = [
156156
"blocking",
157157
] }
158158
tokio = { version = "1.28.1", features = ["full"] }
159+
chrono = "0.4"
159160

160161
# This is not optimal. See also the Cargo issue:
161162
# https://github.com/rust-lang/cargo/issues/1197#issuecomment-1641086954

bindings/java/src/async_operator.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use crate::make_metadata;
4444
use crate::make_operator_info;
4545
use crate::make_presigned_request;
4646
use crate::Result;
47-
use crate::{make_entry, make_list_options, make_write_options};
47+
use crate::{make_entry, make_list_options, make_stat_options, make_write_options};
4848

4949
#[no_mangle]
5050
pub extern "system" fn Java_org_apache_opendal_AsyncOperator_constructor(
@@ -147,8 +147,9 @@ pub unsafe extern "system" fn Java_org_apache_opendal_AsyncOperator_stat(
147147
op: *mut Operator,
148148
executor: *const Executor,
149149
path: JString,
150+
stat_options: JObject,
150151
) -> jlong {
151-
intern_stat(&mut env, op, executor, path).unwrap_or_else(|e| {
152+
intern_stat(&mut env, op, executor, path, stat_options).unwrap_or_else(|e| {
152153
e.throw(&mut env);
153154
0
154155
})
@@ -159,14 +160,16 @@ fn intern_stat(
159160
op: *mut Operator,
160161
executor: *const Executor,
161162
path: JString,
163+
options: JObject,
162164
) -> Result<jlong> {
163165
let op = unsafe { &mut *op };
164166
let id = request_id(env)?;
165167

166168
let path = jstring_to_string(env, &path)?;
169+
let stat_opts = make_stat_options(env, &options)?;
167170

168171
executor_or_default(env, executor)?.spawn(async move {
169-
let metadata = op.stat(&path).await.map_err(Into::into);
172+
let metadata = op.stat_options(&path, stat_opts).await.map_err(Into::into);
170173
let mut env = unsafe { get_current_env() };
171174
let result = metadata.and_then(|metadata| make_metadata(&mut env, metadata));
172175
complete_future(id, result.map(JValueOwned::Object))

bindings/java/src/convert.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717

1818
use crate::Result;
19+
use chrono::{DateTime, Utc};
1920
use jni::objects::JObject;
2021
use jni::objects::JString;
2122
use jni::objects::{JByteArray, JMap};
@@ -122,6 +123,34 @@ pub(crate) fn read_jlong_field_to_usize(
122123
}
123124
}
124125

126+
pub(crate) fn read_instant_field_to_date_time(
127+
env: &mut JNIEnv<'_>,
128+
obj: &JObject,
129+
field: &str,
130+
) -> Result<Option<DateTime<Utc>>> {
131+
let result = env.get_field(obj, field, "Ljava/time/Instant;")?.l()?;
132+
if result.is_null() {
133+
return Ok(None);
134+
}
135+
136+
let epoch_second = env
137+
.call_method(&result, "getEpochSecond", "()J", &[])?
138+
.j()?;
139+
let nano = env.call_method(&result, "getNano", "()I", &[])?.i()?;
140+
DateTime::from_timestamp(epoch_second, nano as u32)
141+
.map(Some)
142+
.ok_or_else(|| {
143+
Error::new(
144+
ErrorKind::Unexpected,
145+
format!(
146+
"Invalid timestamp: seconds={}, nanos={}",
147+
epoch_second, nano
148+
),
149+
)
150+
.into()
151+
})
152+
}
153+
125154
pub(crate) fn offset_length_to_range(offset: i64, length: i64) -> Result<(Bound<u64>, Bound<u64>)> {
126155
let offset = u64::try_from(offset)
127156
.map_err(|_| Error::new(ErrorKind::RangeNotSatisfied, "offset must be non-negative"))?;

bindings/java/src/lib.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,14 @@ fn make_operator_info<'a>(env: &mut JNIEnv<'a>, info: OperatorInfo) -> Result<JO
9494
fn make_capability<'a>(env: &mut JNIEnv<'a>, cap: Capability) -> Result<JObject<'a>> {
9595
let capability = env.new_object(
9696
"org/apache/opendal/Capability",
97-
"(ZZZZZZZZZZZZZZZZZZZJJZZZZZZZZZZZZZZZ)V",
97+
"(ZZZZZZZZZZZZZZZZZZZZZZJJZZZZZZZZZZZZZZZ)V",
9898
&[
9999
JValue::Bool(cap.stat as jboolean),
100100
JValue::Bool(cap.stat_with_if_match as jboolean),
101101
JValue::Bool(cap.stat_with_if_none_match as jboolean),
102+
JValue::Bool(cap.stat_with_if_modified_since as jboolean),
103+
JValue::Bool(cap.stat_with_if_unmodified_since as jboolean),
104+
JValue::Bool(cap.stat_with_version as jboolean),
102105
JValue::Bool(cap.read as jboolean),
103106
JValue::Bool(cap.read_with_if_match as jboolean),
104107
JValue::Bool(cap.read_with_if_none_match as jboolean),
@@ -246,3 +249,28 @@ fn make_list_options<'a>(
246249
deleted: convert::read_bool_field(env, options, "deleted").unwrap_or_default(),
247250
})
248251
}
252+
253+
fn make_stat_options(env: &mut JNIEnv, options: &JObject) -> Result<opendal::options::StatOptions> {
254+
Ok(opendal::options::StatOptions {
255+
if_match: convert::read_string_field(env, options, "ifMatch")?,
256+
if_none_match: convert::read_string_field(env, options, "ifNoneMatch")?,
257+
if_modified_since: convert::read_instant_field_to_date_time(
258+
env,
259+
options,
260+
"ifModifiedSince",
261+
)?,
262+
if_unmodified_since: convert::read_instant_field_to_date_time(
263+
env,
264+
options,
265+
"ifUnmodifiedSince",
266+
)?,
267+
version: convert::read_string_field(env, options, "version")?,
268+
override_content_type: convert::read_string_field(env, options, "overrideContentType")?,
269+
override_cache_control: convert::read_string_field(env, options, "overrideCacheControl")?,
270+
override_content_disposition: convert::read_string_field(
271+
env,
272+
options,
273+
"overrideContentDisposition",
274+
)?,
275+
})
276+
}

bindings/java/src/main/java/org/apache/opendal/AsyncOperator.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,11 @@ public CompletableFuture<Void> write(String path, byte[] content, WriteOptions o
228228
}
229229

230230
public CompletableFuture<Metadata> stat(String path) {
231-
final long requestId = stat(nativeHandle, executorHandle, path);
231+
return stat(path, StatOptions.builder().build());
232+
}
233+
234+
public CompletableFuture<Metadata> stat(String path, StatOptions options) {
235+
final long requestId = stat(nativeHandle, executorHandle, path, options);
232236
return AsyncRegistry.take(requestId);
233237
}
234238

@@ -311,7 +315,7 @@ private static native long write(
311315

312316
private static native long delete(long nativeHandle, long executorHandle, String path);
313317

314-
private static native long stat(long nativeHandle, long executorHandle, String path);
318+
private static native long stat(long nativeHandle, long executorHandle, String path, StatOptions options);
315319

316320
private static native long presignRead(long nativeHandle, long executorHandle, String path, long duration);
317321

bindings/java/src/main/java/org/apache/opendal/Capability.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,21 @@ public class Capability {
3939
*/
4040
public final boolean statWithIfNoneMatch;
4141

42+
/**
43+
* If operator supports stat with if modified since.
44+
*/
45+
public final boolean statWithIfModifiedSince;
46+
47+
/**
48+
* If operator supports stat with if unmodified since.
49+
*/
50+
public final boolean statWithIfUnmodifiedSince;
51+
52+
/**
53+
* If operator supports stat with versions.
54+
*/
55+
public final boolean statWithVersion;
56+
4257
/**
4358
* If operator supports read.
4459
*/
@@ -211,6 +226,9 @@ public Capability(
211226
boolean stat,
212227
boolean statWithIfMatch,
213228
boolean statWithIfNoneMatch,
229+
boolean statWithIfModifiedSince,
230+
boolean statWithIfUnmodifiedSince,
231+
boolean statWithVersion,
214232
boolean read,
215233
boolean readWithIfMatch,
216234
boolean readWithIfNoneMatch,
@@ -247,6 +265,9 @@ public Capability(
247265
this.stat = stat;
248266
this.statWithIfMatch = statWithIfMatch;
249267
this.statWithIfNoneMatch = statWithIfNoneMatch;
268+
this.statWithIfModifiedSince = statWithIfModifiedSince;
269+
this.statWithIfUnmodifiedSince = statWithIfUnmodifiedSince;
270+
this.statWithVersion = statWithVersion;
250271
this.read = read;
251272
this.readWithIfMatch = readWithIfMatch;
252273
this.readWithIfNoneMatch = readWithIfNoneMatch;

bindings/java/src/main/java/org/apache/opendal/Operator.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,11 @@ public void delete(String path) {
116116
}
117117

118118
public Metadata stat(String path) {
119-
return stat(nativeHandle, path);
119+
return stat(nativeHandle, path, StatOptions.builder().build());
120+
}
121+
122+
public Metadata stat(String path, StatOptions options) {
123+
return stat(nativeHandle, path, options);
120124
}
121125

122126
public void createDir(String path) {
@@ -154,7 +158,7 @@ public List<Entry> list(String path, ListOptions options) {
154158

155159
private static native void delete(long op, String path);
156160

157-
private static native Metadata stat(long op, String path);
161+
private static native Metadata stat(long op, String path, StatOptions options);
158162

159163
private static native long createDir(long op, String path);
160164

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.opendal;
21+
22+
import java.time.Instant;
23+
import lombok.Builder;
24+
import lombok.Data;
25+
26+
@Data
27+
@Builder
28+
public class StatOptions {
29+
30+
/**
31+
* Sets if-match condition for this operation.
32+
* If file exists and its etag doesn't match, an error will be returned.
33+
*/
34+
private String ifMatch;
35+
36+
/**
37+
* Sets if-none-match condition for this operation.
38+
* If file exists and its etag matches, an error will be returned.
39+
*/
40+
private String ifNoneMatch;
41+
42+
/**
43+
* Sets if-modified-since condition for this operation.
44+
* If file exists and hasn't been modified since the specified time, an error will be returned.
45+
*/
46+
private Instant ifModifiedSince;
47+
48+
/**
49+
* Sets if-unmodified-since condition for this operation.
50+
* If file exists and has been modified since the specified time, an error will be returned.
51+
*/
52+
private Instant ifUnmodifiedSince;
53+
54+
/**
55+
* Sets version for this operation.
56+
* Retrieves data of a specified version of the given path.
57+
*/
58+
private String version;
59+
60+
/**
61+
* Specifies the content-type header for presigned operations.
62+
* Only meaningful when used along with presign.
63+
*/
64+
private String overrideContentType;
65+
66+
/**
67+
* Specifies the cache-control header for presigned operations.
68+
* Only meaningful when used along with presign.
69+
*/
70+
private String overrideCacheControl;
71+
72+
/**
73+
* Specifies the content-disposition header for presigned operations.
74+
* Only meaningful when used along with presign.
75+
*/
76+
private String overrideContentDisposition;
77+
}

bindings/java/src/operator.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use crate::convert::{
3333
};
3434
use crate::make_metadata;
3535
use crate::Result;
36-
use crate::{make_entry, make_list_options, make_write_options};
36+
use crate::{make_entry, make_list_options, make_stat_options, make_write_options};
3737

3838
/// # Safety
3939
///
@@ -141,16 +141,23 @@ pub unsafe extern "system" fn Java_org_apache_opendal_Operator_stat(
141141
_: JClass,
142142
op: *mut blocking::Operator,
143143
path: JString,
144+
stat_options: JObject,
144145
) -> jobject {
145-
intern_stat(&mut env, &mut *op, path).unwrap_or_else(|e| {
146+
intern_stat(&mut env, &mut *op, path, stat_options).unwrap_or_else(|e| {
146147
e.throw(&mut env);
147148
JObject::default().into_raw()
148149
})
149150
}
150151

151-
fn intern_stat(env: &mut JNIEnv, op: &mut blocking::Operator, path: JString) -> Result<jobject> {
152+
fn intern_stat(
153+
env: &mut JNIEnv,
154+
op: &mut blocking::Operator,
155+
path: JString,
156+
options: JObject,
157+
) -> Result<jobject> {
152158
let path = jstring_to_string(env, &path)?;
153-
let metadata = op.stat(&path)?;
159+
let stat_opts = make_stat_options(env, &options)?;
160+
let metadata = op.stat_options(&path, stat_opts)?;
154161
Ok(make_metadata(env, metadata)?.into_raw())
155162
}
156163

0 commit comments

Comments
 (0)