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.transfer.s3.model; 17 18 import java.io.ByteArrayInputStream; 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.io.OutputStream; 22 import java.nio.charset.StandardCharsets; 23 import java.nio.file.Files; 24 import java.nio.file.Path; 25 import java.time.Instant; 26 import java.util.Objects; 27 import java.util.Optional; 28 import java.util.OptionalLong; 29 import java.util.function.Consumer; 30 import software.amazon.awssdk.annotations.SdkPublicApi; 31 import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; 32 import software.amazon.awssdk.core.SdkBytes; 33 import software.amazon.awssdk.core.exception.SdkClientException; 34 import software.amazon.awssdk.services.s3.model.PutObjectRequest; 35 import software.amazon.awssdk.transfer.s3.S3TransferManager; 36 import software.amazon.awssdk.transfer.s3.config.TransferRequestOverrideConfiguration; 37 import software.amazon.awssdk.transfer.s3.internal.serialization.ResumableFileUploadSerializer; 38 import software.amazon.awssdk.utils.IoUtils; 39 import software.amazon.awssdk.utils.ToString; 40 import software.amazon.awssdk.utils.Validate; 41 import software.amazon.awssdk.utils.builder.CopyableBuilder; 42 import software.amazon.awssdk.utils.builder.ToCopyableBuilder; 43 44 /** 45 * POJO class that holds the state and can be used to resume a paused upload file operation. 46 * <p> 47 * <b>Serialization: </b>When serializing this token, the following structures will not be preserved/persisted: 48 * <ul> 49 * <li>{@link TransferRequestOverrideConfiguration}</li> 50 * <li>{@link AwsRequestOverrideConfiguration} (from {@link PutObjectRequest})</li> 51 * </ul> 52 * 53 * @see S3TransferManager#uploadFile(UploadFileRequest) 54 * @see S3TransferManager#resumeUploadFile(ResumableFileUpload) 55 */ 56 @SdkPublicApi 57 public final class ResumableFileUpload implements ResumableTransfer, 58 ToCopyableBuilder<ResumableFileUpload.Builder, ResumableFileUpload> { 59 60 private final UploadFileRequest uploadFileRequest; 61 private final Instant fileLastModified; 62 private final String multipartUploadId; 63 private final Long partSizeInBytes; 64 private final Long totalParts; 65 private final long fileLength; 66 private final Long transferredParts; 67 ResumableFileUpload(DefaultBuilder builder)68 private ResumableFileUpload(DefaultBuilder builder) { 69 this.uploadFileRequest = Validate.paramNotNull(builder.uploadFileRequest, "uploadFileRequest"); 70 this.fileLastModified = Validate.paramNotNull(builder.fileLastModified, "fileLastModified"); 71 this.fileLength = Validate.paramNotNull(builder.fileLength, "fileLength"); 72 this.multipartUploadId = builder.multipartUploadId; 73 this.totalParts = builder.totalParts; 74 this.partSizeInBytes = builder.partSizeInBytes; 75 this.transferredParts = builder.transferredParts; 76 } 77 78 @Override equals(Object o)79 public boolean equals(Object o) { 80 if (this == o) { 81 return true; 82 } 83 if (o == null || getClass() != o.getClass()) { 84 return false; 85 } 86 87 ResumableFileUpload that = (ResumableFileUpload) o; 88 89 if (fileLength != that.fileLength) { 90 return false; 91 } 92 if (!uploadFileRequest.equals(that.uploadFileRequest)) { 93 return false; 94 } 95 if (!fileLastModified.equals(that.fileLastModified)) { 96 return false; 97 } 98 if (!Objects.equals(multipartUploadId, that.multipartUploadId)) { 99 return false; 100 } 101 if (!Objects.equals(partSizeInBytes, that.partSizeInBytes)) { 102 return false; 103 } 104 105 if (!Objects.equals(transferredParts, that.transferredParts)) { 106 return false; 107 } 108 109 return Objects.equals(totalParts, that.totalParts); 110 } 111 112 @Override hashCode()113 public int hashCode() { 114 int result = uploadFileRequest.hashCode(); 115 result = 31 * result + fileLastModified.hashCode(); 116 result = 31 * result + (multipartUploadId != null ? multipartUploadId.hashCode() : 0); 117 result = 31 * result + (partSizeInBytes != null ? partSizeInBytes.hashCode() : 0); 118 result = 31 * result + (totalParts != null ? totalParts.hashCode() : 0); 119 result = 31 * result + (transferredParts != null ? transferredParts.hashCode() : 0); 120 result = 31 * result + (int) (fileLength ^ (fileLength >>> 32)); 121 return result; 122 } 123 builder()124 public static Builder builder() { 125 return new DefaultBuilder(); 126 } 127 128 /** 129 * @return the {@link UploadFileRequest} to resume 130 */ uploadFileRequest()131 public UploadFileRequest uploadFileRequest() { 132 return uploadFileRequest; 133 } 134 135 /** 136 * Last modified time of the file since last pause 137 */ fileLastModified()138 public Instant fileLastModified() { 139 return fileLastModified; 140 } 141 142 /** 143 * File length since last pause 144 */ fileLength()145 public long fileLength() { 146 return fileLength; 147 } 148 149 /** 150 * Return the part size in bytes or {@link OptionalLong#empty()} if unknown 151 */ partSizeInBytes()152 public OptionalLong partSizeInBytes() { 153 return partSizeInBytes == null ? OptionalLong.empty() : OptionalLong.of(partSizeInBytes); 154 } 155 156 /** 157 * Return the total number of parts associated with this transfer or {@link OptionalLong#empty()} if unknown 158 */ totalParts()159 public OptionalLong totalParts() { 160 return totalParts == null ? OptionalLong.empty() : OptionalLong.of(totalParts); 161 } 162 163 /** 164 * The multipart upload ID, or {@link Optional#empty()} if unknown 165 * 166 * @return the optional total size of the transfer. 167 */ multipartUploadId()168 public Optional<String> multipartUploadId() { 169 return Optional.ofNullable(multipartUploadId); 170 } 171 172 /** 173 * Return the total number of parts completed with this transfer or {@link OptionalLong#empty()} if unknown 174 */ transferredParts()175 public OptionalLong transferredParts() { 176 return transferredParts == null ? OptionalLong.empty() : OptionalLong.of(transferredParts); 177 } 178 179 @Override serializeToFile(Path path)180 public void serializeToFile(Path path) { 181 try { 182 Files.write(path, ResumableFileUploadSerializer.toJson(this)); 183 } catch (IOException e) { 184 throw SdkClientException.create("Failed to write to " + path, e); 185 } 186 } 187 188 @Override serializeToOutputStream(OutputStream outputStream)189 public void serializeToOutputStream(OutputStream outputStream) { 190 byte[] bytes = ResumableFileUploadSerializer.toJson(this); 191 try { 192 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); 193 IoUtils.copy(byteArrayInputStream, outputStream); 194 } catch (IOException e) { 195 throw SdkClientException.create("Failed to write this download object to the given OutputStream", e); 196 } 197 } 198 199 @Override serializeToString()200 public String serializeToString() { 201 return new String(ResumableFileUploadSerializer.toJson(this), StandardCharsets.UTF_8); 202 } 203 204 @Override serializeToBytes()205 public SdkBytes serializeToBytes() { 206 return SdkBytes.fromByteArrayUnsafe(ResumableFileUploadSerializer.toJson(this)); 207 } 208 209 @Override serializeToInputStream()210 public InputStream serializeToInputStream() { 211 return new ByteArrayInputStream(ResumableFileUploadSerializer.toJson(this)); 212 } 213 214 /** 215 * Deserializes data at the given path into a {@link ResumableFileUpload}. 216 * 217 * @param path The {@link Path} to the file with serialized data 218 * @return the deserialized {@link ResumableFileUpload} 219 */ fromFile(Path path)220 public static ResumableFileUpload fromFile(Path path) { 221 try (InputStream stream = Files.newInputStream(path)) { 222 return ResumableFileUploadSerializer.fromJson(stream); 223 } catch (IOException e) { 224 throw SdkClientException.create("Failed to create a ResumableFileUpload from " + path, e); 225 } 226 } 227 228 /** 229 * Deserializes bytes with JSON data into a {@link ResumableFileUpload}. 230 * 231 * @param bytes the serialized data 232 * @return the deserialized {@link ResumableFileUpload} 233 */ fromBytes(SdkBytes bytes)234 public static ResumableFileUpload fromBytes(SdkBytes bytes) { 235 return ResumableFileUploadSerializer.fromJson(bytes.asByteArrayUnsafe()); 236 } 237 238 /** 239 * Deserializes a string with JSON data into a {@link ResumableFileUpload}. 240 * 241 * @param contents the serialized data 242 * @return the deserialized {@link ResumableFileUpload} 243 */ fromString(String contents)244 public static ResumableFileUpload fromString(String contents) { 245 return ResumableFileUploadSerializer.fromJson(contents); 246 } 247 248 249 @Override toString()250 public String toString() { 251 return ToString.builder("ResumableFileUpload") 252 .add("fileLastModified", fileLastModified) 253 .add("multipartUploadId", multipartUploadId) 254 .add("uploadFileRequest", uploadFileRequest) 255 .add("fileLength", fileLength) 256 .add("totalParts", totalParts) 257 .add("partSizeInBytes", partSizeInBytes) 258 .add("transferredParts", transferredParts) 259 .build(); 260 } 261 262 @Override toBuilder()263 public Builder toBuilder() { 264 return new DefaultBuilder(this); 265 } 266 267 public interface Builder extends CopyableBuilder<Builder, ResumableFileUpload> { 268 269 /** 270 * Sets the upload file request 271 * 272 * @param uploadFileRequest the upload file request 273 * @return a reference to this object so that method calls can be chained together. 274 */ uploadFileRequest(UploadFileRequest uploadFileRequest)275 Builder uploadFileRequest(UploadFileRequest uploadFileRequest); 276 277 /** 278 * The {@link UploadFileRequest} request 279 * 280 * <p> 281 * This is a convenience method that creates an instance of the {@link UploadFileRequest} builder avoiding the 282 * need to create one manually via {@link UploadFileRequest#builder()}. 283 * 284 * @param uploadFileRequestBuilder the upload file request builder 285 * @return a reference to this object so that method calls can be chained together. 286 * @see #uploadFileRequest(UploadFileRequest) 287 */ uploadFileRequest(Consumer<UploadFileRequest.Builder> uploadFileRequestBuilder)288 default ResumableFileUpload.Builder uploadFileRequest(Consumer<UploadFileRequest.Builder> 289 uploadFileRequestBuilder) { 290 UploadFileRequest request = UploadFileRequest.builder() 291 .applyMutation(uploadFileRequestBuilder) 292 .build(); 293 uploadFileRequest(request); 294 return this; 295 } 296 297 /** 298 * Sets multipart ID associated with this transfer 299 * 300 * @param multipartUploadId the multipart ID 301 * @return a reference to this object so that method calls can be chained together. 302 */ multipartUploadId(String multipartUploadId)303 Builder multipartUploadId(String multipartUploadId); 304 305 /** 306 * Sets the last modified time of the object 307 * 308 * @param fileLastModified the last modified time of the file 309 * @return a reference to this object so that method calls can be chained together. 310 */ fileLastModified(Instant fileLastModified)311 Builder fileLastModified(Instant fileLastModified); 312 313 /** 314 * Sets the file length 315 * 316 * @param fileLength the last modified time of the object 317 * @return a reference to this object so that method calls can be chained together. 318 */ fileLength(Long fileLength)319 Builder fileLength(Long fileLength); 320 321 /** 322 * Sets the total number of parts 323 * 324 * @param totalParts the total number of parts 325 * @return a reference to this object so that method calls can be chained together. 326 */ totalParts(Long totalParts)327 Builder totalParts(Long totalParts); 328 329 /** 330 * Set the total number of parts transferred 331 * 332 * @param transferredParts the number of parts completed 333 * @return a reference to this object so that method calls can be chained together. 334 */ transferredParts(Long transferredParts)335 Builder transferredParts(Long transferredParts); 336 337 /** 338 * The part size associated with this transfer 339 * @param partSizeInBytes the part size in bytes 340 * @return a reference to this object so that method calls can be chained together. 341 */ partSizeInBytes(Long partSizeInBytes)342 Builder partSizeInBytes(Long partSizeInBytes); 343 } 344 345 private static final class DefaultBuilder implements Builder { 346 347 private String multipartUploadId; 348 private UploadFileRequest uploadFileRequest; 349 private Long partSizeInBytes; 350 private Long totalParts; 351 private Instant fileLastModified; 352 private Long fileLength; 353 354 private Long transferredParts; 355 DefaultBuilder()356 private DefaultBuilder() { 357 } 358 DefaultBuilder(ResumableFileUpload persistableFileUpload)359 private DefaultBuilder(ResumableFileUpload persistableFileUpload) { 360 this.multipartUploadId = persistableFileUpload.multipartUploadId; 361 this.uploadFileRequest = persistableFileUpload.uploadFileRequest; 362 this.partSizeInBytes = persistableFileUpload.partSizeInBytes; 363 this.fileLastModified = persistableFileUpload.fileLastModified; 364 this.totalParts = persistableFileUpload.totalParts; 365 this.fileLength = persistableFileUpload.fileLength; 366 this.transferredParts = persistableFileUpload.transferredParts; 367 } 368 369 @Override uploadFileRequest(UploadFileRequest uploadFileRequest)370 public Builder uploadFileRequest(UploadFileRequest uploadFileRequest) { 371 this.uploadFileRequest = uploadFileRequest; 372 return this; 373 } 374 375 @Override multipartUploadId(String mutipartUploadId)376 public Builder multipartUploadId(String mutipartUploadId) { 377 this.multipartUploadId = mutipartUploadId; 378 return this; 379 } 380 381 382 @Override fileLastModified(Instant fileLastModified)383 public Builder fileLastModified(Instant fileLastModified) { 384 this.fileLastModified = fileLastModified; 385 return this; 386 } 387 388 @Override fileLength(Long fileLength)389 public Builder fileLength(Long fileLength) { 390 this.fileLength = fileLength; 391 return this; 392 } 393 394 @Override totalParts(Long totalParts)395 public Builder totalParts(Long totalParts) { 396 this.totalParts = totalParts; 397 return this; 398 } 399 400 @Override transferredParts(Long transferredParts)401 public Builder transferredParts(Long transferredParts) { 402 this.transferredParts = transferredParts; 403 return this; 404 } 405 406 @Override partSizeInBytes(Long partSizeInBytes)407 public Builder partSizeInBytes(Long partSizeInBytes) { 408 this.partSizeInBytes = partSizeInBytes; 409 return this; 410 } 411 412 @Override build()413 public ResumableFileUpload build() { 414 return new ResumableFileUpload(this); 415 } 416 } 417 } 418