Skip to content

Commit 0281350

Browse files
authored
S3 URI Parser (#3874)
* S3 URI Parser * S3 URI Parser * S3 URI Parser * S3 URI Parser * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring * Refactoring
1 parent c9dfad1 commit 0281350

File tree

4 files changed

+795
-2
lines changed

4 files changed

+795
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon S3",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Adding feature for parsing S3 URIs"
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.s3;
17+
18+
import java.net.URI;
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Objects;
26+
import java.util.Optional;
27+
import software.amazon.awssdk.annotations.Immutable;
28+
import software.amazon.awssdk.annotations.SdkPublicApi;
29+
import software.amazon.awssdk.regions.Region;
30+
import software.amazon.awssdk.utils.CollectionUtils;
31+
import software.amazon.awssdk.utils.ToString;
32+
import software.amazon.awssdk.utils.Validate;
33+
import software.amazon.awssdk.utils.builder.CopyableBuilder;
34+
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
35+
36+
/**
37+
* Object that represents a parsed S3 URI. Can be used to easily retrieve the bucket, key, region, style, and query parameters
38+
* of the URI. Only path-style and virtual-hosted-style URI parsing is supported, including CLI-style URIs, e.g.,
39+
* "s3://bucket/key". AccessPoints and Outposts URI parsing is not supported. If you work with object keys and/or query
40+
* parameters with special characters, they must be URL-encoded, e.g., replace " " with "%20". If you work with
41+
* virtual-hosted-style URIs with bucket names that contain a dot, i.e., ".", the dot must not be URL-encoded. Encoded buckets,
42+
* keys, and query parameters will be returned decoded.
43+
*/
44+
@Immutable
45+
@SdkPublicApi
46+
public final class S3Uri implements ToCopyableBuilder<S3Uri.Builder, S3Uri> {
47+
48+
private final URI uri;
49+
private final String bucket;
50+
private final String key;
51+
private final Region region;
52+
private final boolean isPathStyle;
53+
private final Map<String, List<String>> queryParams;
54+
55+
private S3Uri(Builder builder) {
56+
this.uri = Validate.notNull(builder.uri, "URI must not be null");
57+
this.bucket = builder.bucket;
58+
this.key = builder.key;
59+
this.region = builder.region;
60+
this.isPathStyle = Validate.notNull(builder.isPathStyle, "Path style flag must not be null");
61+
this.queryParams = builder.queryParams == null ? new HashMap<>() : CollectionUtils.deepCopyMap(builder.queryParams);
62+
}
63+
64+
public static Builder builder() {
65+
return new Builder();
66+
}
67+
68+
@Override
69+
public Builder toBuilder() {
70+
return new Builder(this);
71+
}
72+
73+
/**
74+
* Returns the original URI that was used to instantiate the {@link S3Uri}
75+
*/
76+
public URI uri() {
77+
return uri;
78+
}
79+
80+
/**
81+
* Returns the bucket specified in the URI. Returns an empty optional if no bucket is specified.
82+
*/
83+
public Optional<String> bucket() {
84+
return Optional.ofNullable(bucket);
85+
}
86+
87+
/**
88+
* Returns the key specified in the URI. Returns an empty optional if no key is specified.
89+
*/
90+
public Optional<String> key() {
91+
return Optional.ofNullable(key);
92+
}
93+
94+
/**
95+
* Returns the region specified in the URI. Returns an empty optional if no region is specified, i.e., global endpoint.
96+
*/
97+
public Optional<Region> region() {
98+
return Optional.ofNullable(region);
99+
}
100+
101+
/**
102+
* Returns true if the URI is path-style, false if the URI is virtual-hosted style.
103+
*/
104+
public boolean isPathStyle() {
105+
return isPathStyle;
106+
}
107+
108+
/**
109+
* Returns a map of the query parameters specified in the URI. Returns an empty map if no queries are specified.
110+
*/
111+
public Map<String, List<String>> rawQueryParameters() {
112+
return queryParams;
113+
}
114+
115+
/**
116+
* Returns the list of values for a specified query parameter. A empty list is returned if the URI does not contain the
117+
* specified query parameter.
118+
*/
119+
public List<String> firstMatchingRawQueryParameters(String key) {
120+
List<String> queryValues = queryParams.get(key);
121+
if (queryValues == null) {
122+
return new ArrayList<>();
123+
}
124+
List<String> queryValuesCopy = Arrays.asList(new String[queryValues.size()]);
125+
Collections.copy(queryValuesCopy, queryValues);
126+
return queryValuesCopy;
127+
}
128+
129+
/**
130+
* Returns the value for the specified query parameter. If there are multiple values for the query parameter, the first
131+
* value is returned. An empty optional is returned if the URI does not contain the specified query parameter.
132+
*/
133+
public Optional<String> firstMatchingRawQueryParameter(String key) {
134+
return Optional.ofNullable(queryParams.get(key)).map(q -> q.get(0));
135+
}
136+
137+
@Override
138+
public String toString() {
139+
return ToString.builder("S3Uri")
140+
.add("uri", uri)
141+
.add("bucket", bucket)
142+
.add("key", key)
143+
.add("region", region)
144+
.add("isPathStyle", isPathStyle)
145+
.add("queryParams", queryParams)
146+
.build();
147+
}
148+
149+
@Override
150+
public boolean equals(Object o) {
151+
if (this == o) {
152+
return true;
153+
}
154+
if (o == null || getClass() != o.getClass()) {
155+
return false;
156+
}
157+
158+
S3Uri s3Uri = (S3Uri) o;
159+
return Objects.equals(uri, s3Uri.uri)
160+
&& Objects.equals(bucket, s3Uri.bucket)
161+
&& Objects.equals(key, s3Uri.key)
162+
&& Objects.equals(region, s3Uri.region)
163+
&& Objects.equals(isPathStyle, s3Uri.isPathStyle)
164+
&& Objects.equals(queryParams, s3Uri.queryParams);
165+
}
166+
167+
@Override
168+
public int hashCode() {
169+
int result = uri != null ? uri.hashCode() : 0;
170+
result = 31 * result + (bucket != null ? bucket.hashCode() : 0);
171+
result = 31 * result + (key != null ? key.hashCode() : 0);
172+
result = 31 * result + (region != null ? region.hashCode() : 0);
173+
result = 31 * result + Boolean.hashCode(isPathStyle);
174+
result = 31 * result + (queryParams != null ? queryParams.hashCode() : 0);
175+
return result;
176+
}
177+
178+
/**
179+
* A builder for creating a {@link S3Uri}
180+
*/
181+
public static final class Builder implements CopyableBuilder<Builder, S3Uri> {
182+
private URI uri;
183+
private String bucket;
184+
private String key;
185+
private Region region;
186+
private boolean isPathStyle;
187+
private Map<String, List<String>> queryParams;
188+
189+
private Builder() {
190+
}
191+
192+
private Builder(S3Uri s3Uri) {
193+
this.uri = s3Uri.uri;
194+
this.bucket = s3Uri.bucket;
195+
this.key = s3Uri.key;
196+
this.region = s3Uri.region;
197+
this.isPathStyle = s3Uri.isPathStyle;
198+
this.queryParams = s3Uri.queryParams;
199+
}
200+
201+
/**
202+
* Configure the URI
203+
*/
204+
public Builder uri(URI uri) {
205+
this.uri = uri;
206+
return this;
207+
}
208+
209+
/**
210+
* Configure the bucket
211+
*/
212+
public Builder bucket(String bucket) {
213+
this.bucket = bucket;
214+
return this;
215+
}
216+
217+
/**
218+
* Configure the key
219+
*/
220+
public Builder key(String key) {
221+
this.key = key;
222+
return this;
223+
}
224+
225+
/**
226+
* Configure the region
227+
*/
228+
public Builder region(Region region) {
229+
this.region = region;
230+
return this;
231+
}
232+
233+
/**
234+
* Configure the path style flag
235+
*/
236+
public Builder isPathStyle(boolean isPathStyle) {
237+
this.isPathStyle = isPathStyle;
238+
return this;
239+
}
240+
241+
/**
242+
* Configure the map of query parameters
243+
*/
244+
public Builder queryParams(Map<String, List<String>> queryParams) {
245+
this.queryParams = queryParams;
246+
return this;
247+
}
248+
249+
@Override
250+
public S3Uri build() {
251+
return new S3Uri(this);
252+
}
253+
}
254+
255+
}

0 commit comments

Comments
 (0)