Last active
March 6, 2019 20:20
-
-
Save jhaber/464afddcb3eb2a8cdac3e30467621077 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.SequenceInputStream; | |
import java.nio.file.Files; | |
import java.nio.file.Path; | |
import java.nio.file.StandardCopyOption; | |
import java.util.function.Supplier; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.util.FastByteArrayOutputStream; | |
import com.amazonaws.services.s3.AmazonS3; | |
import com.amazonaws.services.s3.Headers; | |
import com.amazonaws.services.s3.model.ObjectMetadata; | |
import com.amazonaws.services.s3.model.PutObjectRequest; | |
import com.amazonaws.services.s3.model.PutObjectResult; | |
import com.google.common.io.ByteStreams; | |
import com.hubspot.s3.S3Decorator; | |
public class EnsureContentLengthS3Decorator extends S3Decorator { | |
private static final Logger LOG = LoggerFactory.getLogger(EnsureContentLengthS3Decorator.class); | |
private final AmazonS3 delegate; | |
private final long bufferSize; | |
private EnsureContentLengthS3Decorator(AmazonS3 delegate, long bufferSize) { | |
this.delegate = delegate; | |
this.bufferSize = bufferSize; | |
} | |
public static AmazonS3 decorate(AmazonS3 delegate, long bufferSize) { | |
return new EnsureContentLengthS3Decorator(delegate, bufferSize); | |
} | |
@Override | |
protected AmazonS3 getDelegate() { | |
return delegate; | |
} | |
@Override | |
protected <T> T call(Supplier<T> callable) { | |
return callable.get(); | |
} | |
@Override | |
public PutObjectResult putObject(PutObjectRequest putObjectRequest) { | |
if (putObjectRequest.getFile() != null || putObjectRequest.getInputStream() == null) { | |
// not using an input stream; carry on | |
return super.putObject(putObjectRequest); | |
} else if (hasContentLength(putObjectRequest.getMetadata())) { | |
// using an input stream, but content length is specified; carry on | |
return super.putObject(putObjectRequest); | |
} else { | |
return putInputStream(putObjectRequest); | |
} | |
} | |
private PutObjectResult putInputStream(PutObjectRequest putObjectRequest) { | |
try (InputStream input = putObjectRequest.getInputStream()) { | |
FastByteArrayOutputStream buffer = new FastByteArrayOutputStream(); | |
long bytesRead = ByteStreams.copy(ByteStreams.limit(input, bufferSize), buffer); | |
if (bytesRead < bufferSize) { | |
// the whole input stream fit in the buffer, no need to write to disk | |
metadata(putObjectRequest).setContentLength(bytesRead); | |
return super.putObject(putObjectRequest.withInputStream(buffer.getInputStream())); | |
} else { | |
// input stream is bigger than buffer, write to disk | |
String key = putObjectRequest.getKey(); | |
Path temporaryFile = createTemporaryFile(key); | |
LOG.info("File upload {} missing content length, will buffer to temporary file", key); | |
try { | |
InputStream concatenated = new SequenceInputStream(buffer.getInputStream(), input); | |
Files.copy(concatenated, temporaryFile, StandardCopyOption.REPLACE_EXISTING); | |
return super.putObject(putObjectRequest.withInputStream(null).withFile(temporaryFile.toFile())); | |
} finally { | |
delete(temporaryFile); | |
} | |
} | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
private boolean hasContentLength(ObjectMetadata metadata) { | |
return metadata != null && metadata.getRawMetadataValue(Headers.CONTENT_LENGTH) != null; | |
} | |
private ObjectMetadata metadata(PutObjectRequest putObjectRequest) { | |
ObjectMetadata metadata = putObjectRequest.getMetadata(); | |
if (metadata == null) { | |
metadata = new ObjectMetadata(); | |
putObjectRequest.setMetadata(metadata); | |
} | |
return metadata; | |
} | |
private Path createTemporaryFile(String key) { | |
try { | |
return Files.createTempFile(key + "-", ".tmp"); | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
private void delete(Path path) { | |
try { | |
Files.delete(path); | |
} catch (IOException e) { | |
LOG.warn("Error deleting temporary file {}", path, e); | |
} | |
} | |
} |
Sorry for the delay, I didn't see your message. I've updated the example to have imports. As written it depends on Guava, slf4j, spring-core, and our S3Decorators library, but could be updated to remove any/all of these dependencies.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This looks very helpful for a project I'm working on, but could you provide a bit more context such as the imports used class extended from?