View Javadoc
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          // prevent instantiation
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          * Sets the level of randomness applied to the next retry interval. The next
127          * calculated retry interval is multiplied by
128          * {@code (1 - jitter * Math.random())}. A value of zero means no jitter, 1
129          * means max jitter.
130          * 
131          * @param jitter level of randomness applied to the retry interval
132          * @return this
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 }