1 package com.github.davidmoten.aws.lw.client;
2
3 import java.io.IOException;
4 import java.io.UncheckedIOException;
5 import java.util.Arrays;
6 import java.util.Collection;
7 import java.util.HashSet;
8 import java.util.Optional;
9 import java.util.Set;
10 import java.util.concurrent.TimeUnit;
11 import java.util.function.Function;
12 import java.util.function.Predicate;
13
14 import com.github.davidmoten.aws.lw.client.internal.Clock;
15 import com.github.davidmoten.aws.lw.client.internal.Environment;
16 import com.github.davidmoten.aws.lw.client.internal.ExceptionFactoryExtended;
17 import com.github.davidmoten.aws.lw.client.internal.Retries;
18 import com.github.davidmoten.aws.lw.client.internal.util.Preconditions;
19
20 public final class Client {
21
22 private final Clock clock;
23 private final String serviceName;
24 private final Optional<String> region;
25 private final Credentials credentials;
26 private final HttpClient httpClient;
27 private final int connectTimeoutMs;
28 private final int readTimeoutMs;
29 private final ExceptionFactory exceptionFactory;
30 private final BaseUrlFactory baseUrlFactory;
31 private final Retries<ResponseInputStream> retries;
32
33 private Client(Clock clock, String serviceName, Optional<String> region, Credentials credentials,
34 HttpClient httpClient, int connectTimeoutMs, int readTimeoutMs, ExceptionFactory exceptionFactory,
35 BaseUrlFactory baseUrlFactory, Retries<ResponseInputStream> retries) {
36 this.clock = clock;
37 this.serviceName = serviceName;
38 this.region = region;
39 this.credentials = credentials;
40 this.httpClient = httpClient;
41 this.connectTimeoutMs = connectTimeoutMs;
42 this.readTimeoutMs = readTimeoutMs;
43 this.exceptionFactory = exceptionFactory;
44 this.baseUrlFactory = baseUrlFactory;
45 this.retries = retries;
46 }
47
48 public static Builder service(String serviceName) {
49 Preconditions.checkNotNull(serviceName);
50 return new Builder(serviceName);
51 }
52
53
54
55
56
57
58
59
60 public static Builder s3() {
61 return service("s3");
62 }
63
64 public static Builder sqs() {
65 return service("sqs");
66 }
67
68 public static Builder iam() {
69 return service("iam");
70 }
71
72 public static Builder ec2() {
73 return service("ec2");
74 }
75
76 public static Builder sns() {
77 return service("sns");
78 }
79
80 public static Builder lambda() {
81 return service("lambda");
82 }
83
84
85
86 String serviceName() {
87 return serviceName;
88 }
89
90 public Optional<String> region() {
91 return region;
92 }
93
94 Credentials credentials() {
95 return credentials;
96 }
97
98 HttpClient httpClient() {
99 return httpClient;
100 }
101
102 Clock clock() {
103 return clock;
104 }
105
106 ExceptionFactory exceptionFactory() {
107 return exceptionFactory;
108 }
109
110 BaseUrlFactory baseUrlFactory() {
111 return baseUrlFactory;
112 }
113
114 int connectTimeoutMs() {
115 return connectTimeoutMs;
116 }
117
118 int readTimeoutMs() {
119 return readTimeoutMs;
120 }
121
122 Retries<ResponseInputStream> retries() {
123 return retries;
124 }
125
126 public Request url(String url) {
127 Preconditions.checkNotNull(url);
128 return new Request(this, url);
129 }
130
131
132
133
134
135
136
137 public Request path(String... segments) {
138 Preconditions.checkNotNull(segments, "segments cannot be null");
139 return new Request(this, null, segments);
140 }
141
142 public Request query(String name, String value) {
143 Preconditions.checkNotNull(name, "name cannot be null");
144 Preconditions.checkNotNull(value, "value cannot be null");
145 return path("").query(name, value);
146 }
147
148 public Request attributePrefix(String attributePrefix) {
149 return path("").attributePrefix(attributePrefix);
150 }
151
152 public Request attribute(String name, String value) {
153 Preconditions.checkNotNull(name, "name cannot be null");
154 Preconditions.checkNotNull(value, "value cannot be null");
155 return path("").attribute(name, value);
156 }
157
158 public static final class Builder {
159
160
161
162 private static final Set<Integer> RETRY_STATUS_CODES = new HashSet<>(
163 Arrays.asList(
164 400,
165 403,
166 408,
167 429,
168 500,
169 502,
170 503,
171 509
172 ));
173
174
175 private final String serviceName;
176 private Optional<String> region = Optional.empty();
177 private String accessKey;
178 private Credentials credentials;
179 private HttpClient httpClient = HttpClient.defaultClient();
180 private int connectTimeoutMs = 30000;
181 private int readTimeoutMs = 300000;
182 private ExceptionFactory exceptionFactory = ExceptionFactory.DEFAULT;
183 private Clock clock = Clock.DEFAULT;
184 private Environment environment = Environment.instance();
185 private BaseUrlFactory baseUrlFactory = BaseUrlFactory.DEFAULT;
186 private Retries<ResponseInputStream> retries = Retries.create(
187 ris -> RETRY_STATUS_CODES.contains(ris.statusCode()),
188 t -> t instanceof IOException || t instanceof UncheckedIOException);
189
190 private Builder(String serviceName) {
191 this.serviceName = serviceName;
192 }
193
194
195 Builder environment(Environment environment) {
196 Preconditions.checkNotNull(environment, "environment cannot be null");
197 this.environment = environment;
198 return this;
199 }
200
201 public Builder4 defaultClient() {
202 return regionFromEnvironment().credentialsFromEnvironment();
203 }
204
205 public Builder4 from(Client client) {
206 Preconditions.checkNotNull(client, "client cannot be null");
207 this.region = client.region;
208 this.credentials = client.credentials;
209 this.httpClient = client.httpClient;
210 this.connectTimeoutMs = client.connectTimeoutMs;
211 this.readTimeoutMs = client.readTimeoutMs;
212 this.exceptionFactory = client.exceptionFactory;
213 return new Builder4(this);
214 }
215
216 public Builder2 regionFromEnvironment() {
217 return region(environment.get("AWS_REGION"));
218 }
219
220 public Builder2 region(Optional<String> region) {
221 Preconditions.checkNotNull(region, "region cannot be null");
222 this.region = region;
223 return new Builder2(this);
224 }
225
226 public Builder2 region(String region) {
227 Preconditions.checkNotNull(region, "region cannot be null");
228 return region(Optional.of(region));
229 }
230
231 public Builder2 regionNone() {
232 return region(Optional.empty());
233 }
234 }
235
236 public static final class Builder2 {
237 private final Builder b;
238
239 private Builder2(Builder b) {
240 this.b = b;
241 }
242
243 public Builder4 credentialsFromEnvironment() {
244 b.credentials = b.environment.credentials();
245 return new Builder4(b);
246 }
247
248 public Builder4 credentialsFromSystemProperties() {
249 return credentials(Credentials.fromSystemProperties());
250 }
251
252 public Builder3 accessKey(String accessKey) {
253 Preconditions.checkNotNull(accessKey);
254 b.accessKey = accessKey;
255 return new Builder3(b);
256 }
257
258 public Builder4 credentials(Credentials credentials) {
259 Preconditions.checkNotNull(credentials);
260 b.credentials = credentials;
261 return new Builder4(b);
262 }
263 }
264
265 public static final class Builder3 {
266 private final Builder b;
267
268 private Builder3(Builder b) {
269 this.b = b;
270 }
271
272 public Builder4 secretKey(String secretKey) {
273 Preconditions.checkNotNull(secretKey);
274 b.credentials = Credentials.of(b.accessKey, secretKey);
275 return new Builder4(b);
276 }
277 }
278
279 public static final class Builder4 {
280 private final Builder b;
281
282 private Builder4(Builder b) {
283 this.b = b;
284 }
285
286 public Builder4 baseUrlFactory(BaseUrlFactory factory) {
287 b.baseUrlFactory = factory;
288 return this;
289 }
290
291 public Builder4 httpClient(HttpClient httpClient) {
292 b.httpClient = httpClient;
293 return this;
294 }
295
296 public Builder4 retryInitialInterval(long duration, TimeUnit unit) {
297 Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
298 Preconditions.checkNotNull(unit, "unit cannot be null");
299 b.retries = b.retries.withInitialIntervalMs(unit.toMillis(duration));
300 return this;
301 }
302
303 public Builder4 retryMaxAttempts(int maxAttempts) {
304 Preconditions.checkArgument(maxAttempts >= 0, "maxAttempts cannot be negative");
305 b.retries = b.retries.withMaxAttempts( maxAttempts);
306 return this;
307 }
308
309 public Builder4 retryBackoffFactor(double factor) {
310 Preconditions.checkArgument(factor >= 0, "backoffFactor cannot be negative");
311 b.retries = b.retries.withBackoffFactor(factor);
312 return this;
313 }
314
315 public Builder4 retryMaxInterval(long duration, TimeUnit unit) {
316 Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
317 Preconditions.checkNotNull(unit, "unit cannot be null");
318 b.retries = b.retries.withMaxIntervalMs(unit.toMillis(duration));
319 return this;
320 }
321
322
323
324
325
326
327
328
329
330
331 public Builder4 retryJitter(double jitter) {
332 Preconditions.checkArgument(jitter >= 0 && jitter <= 1, "jitter must be between 0 and 1");
333 b.retries = b.retries.withJitter(jitter);
334 return this;
335 }
336
337 public Builder4 retryCondition(Predicate<? super ResponseInputStream> shouldRetry) {
338 Preconditions.checkNotNull(shouldRetry, "shouldRetry cannot be null");
339 b.retries = b.retries.withValueShouldRetry(shouldRetry);
340 return this;
341 }
342
343 public Builder4 retryStatusCodes(Integer... statusCodes) {
344 return retryStatusCodes(Arrays.asList(statusCodes));
345 }
346
347 public Builder4 retryStatusCodes(Collection<Integer> statusCodes) {
348 Preconditions.checkNotNull(statusCodes, "statusCodes cannot be null");
349 Set<Integer> set = new HashSet<>(statusCodes);
350 return retryCondition(ris -> set.contains(ris.statusCode()));
351 }
352
353
354
355
356
357
358
359 public Builder4 retryException(Predicate<? super Throwable> shouldRetry) {
360 Preconditions.checkNotNull(shouldRetry, "shouldRetry cannot be null");
361 b.retries = b.retries.withThrowableShouldRetry(shouldRetry);
362 return this;
363 }
364
365 public Builder4 connectTimeout(long duration, TimeUnit unit) {
366 Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
367 Preconditions.checkNotNull(unit, "unit cannot be null");
368 b.connectTimeoutMs = (int) unit.toMillis(duration);
369 return this;
370 }
371
372 public Builder4 readTimeout(long duration, TimeUnit unit) {
373 Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
374 Preconditions.checkNotNull(unit, "unit cannot be null");
375 b.readTimeoutMs = (int) unit.toMillis(duration);
376 return this;
377 }
378
379 public Builder4 exceptionFactory(ExceptionFactory exceptionFactory) {
380 b.exceptionFactory = exceptionFactory;
381 return this;
382 }
383
384 public Builder4 exception(Predicate<? super Response> predicate,
385 Function<? super Response, ? extends RuntimeException> factory) {
386 b.exceptionFactory = new ExceptionFactoryExtended(b.exceptionFactory, predicate, factory);
387 return this;
388 }
389
390 public Builder4 clock(Clock clock) {
391 b.clock = clock;
392 return this;
393 }
394
395 public Client build() {
396 return new Client(b.clock, b.serviceName, b.region, b.credentials, b.httpClient, b.connectTimeoutMs,
397 b.readTimeoutMs, b.exceptionFactory, b.baseUrlFactory, b.retries);
398 }
399 }
400
401 }