1 package com.github.davidmoten.aws.lw.client;
2
3 import java.io.BufferedInputStream;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.OutputStream;
9 import java.io.UncheckedIOException;
10 import java.util.concurrent.Callable;
11 import java.util.concurrent.ExecutorService;
12 import java.util.concurrent.Executors;
13 import java.util.concurrent.TimeUnit;
14 import java.util.function.Function;
15
16 import com.github.davidmoten.aws.lw.client.internal.Retries;
17 import com.github.davidmoten.aws.lw.client.internal.util.Preconditions;
18
19 public final class Multipart {
20
21 private Multipart() {
22
23 }
24
25 public static Builder s3(Client s3) {
26 Preconditions.checkNotNull(s3);
27 return new Builder(s3);
28 }
29
30 public static final class Builder {
31
32 private final Client s3;
33 private String bucket;
34 public String key;
35 public ExecutorService executor;
36 public long timeoutMs = TimeUnit.HOURS.toMillis(1);
37 public Function<? super Request, ? extends Request> transform = x -> x;
38 public int partSize = 5 * 1024 * 1024;
39 public Retries<Void> retries;
40
41 Builder(Client s3) {
42 this.s3 = s3;
43 this.retries = s3.retries().withValueShouldRetry(values -> false);
44 }
45
46 public Builder2 bucket(String bucket) {
47 Preconditions.checkNotNull(bucket, "bucket cannot be null");
48 this.bucket = bucket;
49 return new Builder2(this);
50 }
51 }
52
53 public static final class Builder2 {
54
55 private final Builder b;
56
57 Builder2(Builder b) {
58 this.b = b;
59 }
60
61 public Builder3 key(String key) {
62 Preconditions.checkNotNull(key, "key cannot be null");
63 b.key = key;
64 return new Builder3(b);
65 }
66 }
67
68 public static final class Builder3 {
69
70 private final Builder b;
71
72 Builder3(Builder b) {
73 this.b = b;
74 }
75
76 public Builder3 executor(ExecutorService executor) {
77 Preconditions.checkNotNull(executor, "executor cannot be null");
78 b.executor = executor;
79 return this;
80 }
81
82 public Builder3 partTimeout(long duration, TimeUnit unit) {
83 Preconditions.checkArgument(duration > 0, "duration must be positive");
84 Preconditions.checkNotNull(unit, "unit cannot be null");
85 b.timeoutMs = unit.toMillis(duration);
86 return this;
87 }
88
89 public Builder3 partSize(int partSize) {
90 Preconditions.checkArgument(partSize >= 5 * 1024 * 1024);
91 b.partSize = partSize;
92 return this;
93 }
94
95 public Builder3 partSizeMb(int partSizeMb) {
96 return partSize(partSizeMb * 1024 * 1024);
97 }
98
99 public Builder3 maxAttemptsPerAction(int maxAttempts) {
100 Preconditions.checkArgument(maxAttempts >= 1, "maxAttempts must be at least one");
101 b.retries = b.retries.withMaxAttempts(maxAttempts);
102 return this;
103 }
104
105 public Builder3 retryInitialInterval(long duration, TimeUnit unit) {
106 Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
107 Preconditions.checkNotNull(unit, "unit cannot be null");
108 b.retries = b.retries.withInitialIntervalMs(unit.toMillis(duration));
109 return this;
110 }
111
112 public Builder3 retryBackoffFactor(double factor) {
113 Preconditions.checkArgument(factor >= 0, "retryBackoffFactory cannot be negative");
114 b.retries = b.retries.withBackoffFactor(factor);
115 return this;
116 }
117
118 public Builder3 retryMaxInterval(long duration, TimeUnit unit) {
119 Preconditions.checkArgument(duration >= 0, "duration cannot be negative");
120 Preconditions.checkNotNull(unit, "unit cannot be null");
121 b.retries = b.retries.withMaxIntervalMs(unit.toMillis(duration));
122 return this;
123 }
124
125
126
127
128
129
130
131
132
133
134 public Builder3 retryJitter(double jitter) {
135 Preconditions.checkArgument(jitter >= 0 && jitter <= 1, "jitter must be between 0 and 1");
136 b.retries = b.retries.withJitter(jitter);
137 return this;
138 }
139
140
141 public Builder3 transformCreateRequest(Function<? super Request, ? extends Request> transform) {
142 Preconditions.checkNotNull(transform, "transform cannot be null");
143 b.transform = transform;
144 return this;
145 }
146
147 public void upload(byte[] bytes, int offset, int length) {
148 Preconditions.checkNotNull(bytes, "bytes cannot be null");
149 try (OutputStream out = outputStream()) {
150 out.write(bytes, offset, length);
151 } catch (IOException e) {
152 throw new UncheckedIOException(e);
153 }
154 }
155
156 public void upload(byte[] bytes) {
157 upload(bytes, 0, bytes.length);
158 }
159
160 public void upload(File file) {
161 Preconditions.checkNotNull(file, "file cannot be null");
162 upload(() -> new BufferedInputStream(new FileInputStream(file)));
163 }
164
165 public void upload(Callable<? extends InputStream> factory) {
166 Preconditions.checkNotNull(factory, "factory cannot be null");
167 try (InputStream in = factory.call(); MultipartOutputStream out = outputStream()) {
168 copy(in, out);
169 } catch (IOException e) {
170 throw new UncheckedIOException(e);
171 } catch (Exception e) {
172 throw new RuntimeException(e);
173 }
174 }
175
176 public MultipartOutputStream outputStream() {
177 if (b.executor == null) {
178 b.executor = Executors.newCachedThreadPool();
179 }
180 return new MultipartOutputStream(b.s3, b.bucket, b.key, b.transform, b.executor, b.timeoutMs, b.retries,
181 b.partSize);
182 }
183 }
184
185 private static void copy(InputStream in, OutputStream out) throws IOException {
186 byte[] buffer = new byte[8192];
187 int n;
188 while ((n = in.read(buffer)) != -1) {
189 out.write(buffer, 0, n);
190 }
191 }
192 }