Skip to content

Instantly share code, notes, and snippets.

@jhaber
Last active March 6, 2019 20:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jhaber/464afddcb3eb2a8cdac3e30467621077 to your computer and use it in GitHub Desktop.
Save jhaber/464afddcb3eb2a8cdac3e30467621077 to your computer and use it in GitHub Desktop.
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);
}
}
}
@peavers
Copy link

peavers commented Jan 30, 2018

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?

@jhaber
Copy link
Author

jhaber commented Mar 6, 2019

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