Skip to content

Commit

Permalink
Add native object copy support
Browse files Browse the repository at this point in the history
Fixes #46.
  • Loading branch information
gaul committed Apr 9, 2015
1 parent 35b37c3 commit 7b937c1
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 48 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jclouds.version>1.9.0</jclouds.version>
<jclouds.version>1.9.1-SNAPSHOT</jclouds.version>
</properties>

<prerequisites>
Expand Down
103 changes: 56 additions & 47 deletions src/main/java/org/gaul/s3proxy/S3ProxyHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import org.jclouds.blobstore.domain.ContainerAccess;
import org.jclouds.blobstore.domain.PageSet;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.blobstore.options.CopyOptions;
import org.jclouds.blobstore.options.CreateContainerOptions;
import org.jclouds.blobstore.options.GetOptions;
import org.jclouds.blobstore.options.ListContainerOptions;
Expand All @@ -91,6 +92,7 @@
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.io.ContentMetadata;
import org.jclouds.io.ContentMetadataBuilder;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.util.Throwables2;
import org.slf4j.Logger;
Expand Down Expand Up @@ -1093,58 +1095,65 @@ private void handleCopyBlob(HttpServletRequest request,
throw new S3Exception(S3ErrorCode.INVALID_REQUEST);
}

Blob blob = blobStore.getBlob(sourceContainerName, sourceBlobName);
if (blob == null) {
throw new S3Exception(S3ErrorCode.NO_SUCH_KEY);
CopyOptions.Builder options = CopyOptions.builder();
if (replaceMetadata) {
ContentMetadataBuilder contentMetadata =
ContentMetadataBuilder.create();
ImmutableMap.Builder<String, String> userMetadata =
ImmutableMap.builder();
for (String headerName : Collections.list(
request.getHeaderNames())) {
String headerValue = Strings.nullToEmpty(request.getHeader(
headerName));
if (headerName.equalsIgnoreCase(
HttpHeaders.CONTENT_DISPOSITION)) {
contentMetadata.contentDisposition(headerValue);
} else if (headerName.equalsIgnoreCase(
HttpHeaders.CONTENT_ENCODING)) {
contentMetadata.contentEncoding(headerValue);
} else if (headerName.equalsIgnoreCase(
HttpHeaders.CONTENT_LANGUAGE)) {
contentMetadata.contentLanguage(headerValue);
} else if (headerName.equalsIgnoreCase(
HttpHeaders.CONTENT_TYPE)) {
contentMetadata.contentType(headerValue);
} else if (headerName.toLowerCase().startsWith(
USER_METADATA_PREFIX)) {
userMetadata.put(
headerName.substring(USER_METADATA_PREFIX.length()),
headerValue);
}
// TODO: Expires
}
options.contentMetadata(contentMetadata.build());
options.userMetadata(userMetadata.build());
}

try (InputStream is = blob.getPayload().openStream()) {
ContentMetadata metadata = blob.getMetadata().getContentMetadata();
long contentLength = metadata.getContentLength();
BlobBuilder.PayloadBlobBuilder builder = blobStore
.blobBuilder(destBlobName)
.payload(is)
.contentLength(contentLength);
if (replaceMetadata) {
addContentMetdataFromHttpRequest(builder, request);
} else {
builder.contentDisposition(metadata.getContentDisposition())
.contentEncoding(metadata.getContentEncoding())
.contentLanguage(metadata.getContentLanguage())
.contentType(metadata.getContentType())
.userMetadata(blob.getMetadata().getUserMetadata());
}
String eTag = blobStore.copyBlob(
sourceContainerName, sourceBlobName,
destContainerName, destBlobName, options.build());
BlobMetadata blobMetadata = blobStore.blobMetadata(destContainerName,
destBlobName);
try (Writer writer = response.getWriter()) {
XMLStreamWriter xml = xmlOutputFactory.createXMLStreamWriter(
writer);
xml.writeStartDocument();
xml.writeStartElement("CopyObjectResult");
xml.writeDefaultNamespace(AWS_XMLNS);

PutOptions options = new PutOptions();
String blobStoreType = getBlobStoreType(blobStore);
if (blobStoreType.equals("azureblob") &&
contentLength > 64 * 1024 * 1024) {
options.multipart(true);
}
String eTag = blobStore.putBlob(destContainerName,
builder.build(), options);
Date lastModified = blob.getMetadata().getLastModified();
try (Writer writer = response.getWriter()) {
XMLStreamWriter xml = xmlOutputFactory.createXMLStreamWriter(
writer);
xml.writeStartDocument();
xml.writeStartElement("CopyObjectResult");
xml.writeDefaultNamespace(AWS_XMLNS);

xml.writeStartElement("LastModified");
xml.writeCharacters(blobStore.getContext().utils().date()
.iso8601DateFormat(lastModified));
xml.writeEndElement();
xml.writeStartElement("LastModified");
xml.writeCharacters(blobStore.getContext().utils().date()
.iso8601DateFormat(blobMetadata.getLastModified()));
xml.writeEndElement();

xml.writeStartElement("ETag");
xml.writeCharacters("\"" + eTag + "\"");
xml.writeEndElement();
xml.writeStartElement("ETag");
xml.writeCharacters("\"" + eTag + "\"");
xml.writeEndElement();

xml.writeEndElement();
xml.flush();
} catch (XMLStreamException xse) {
throw new IOException(xse);
}
xml.writeEndElement();
xml.flush();
} catch (XMLStreamException xse) {
throw new IOException(xse);
}
}

Expand Down
114 changes: 114 additions & 0 deletions src/test/java/org/gaul/s3proxy/S3ProxyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@

import java.io.InputStream;
import java.net.URI;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.Random;

import javax.servlet.http.HttpServletResponse;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteSource;
import com.google.common.io.Resources;
Expand All @@ -44,10 +47,13 @@
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.PageSet;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.blobstore.options.CopyOptions;
import org.jclouds.blobstore.options.ListContainerOptions;
import org.jclouds.blobstore.options.PutOptions;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.io.ContentMetadata;
import org.jclouds.io.ContentMetadataBuilder;
import org.jclouds.io.Payload;
import org.jclouds.io.payloads.ByteSourcePayload;
import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
Expand Down Expand Up @@ -388,6 +394,114 @@ public void testMultipartUpload() throws Exception {
new PutOptions().multipart(true));
}

@Test
public void testCopyObjectPreserveMetadata() throws Exception {
String fromName = "from-name";
String toName = "to-name";
ByteSource byteSource = ByteSource.wrap(new byte[42]);
String contentDisposition = "attachment; filename=old.jpg";
String contentEncoding = "gzip";
String contentLanguage = "en";
String contentType = "audio/ogg";
Date expires = new Date(1000);
Map<String, String> userMetadata = ImmutableMap.of(
"key1", "value1",
"key2", "value2");
Blob fromBlob = s3BlobStore.blobBuilder(fromName)
.payload(byteSource)
.contentLength(byteSource.size())
.contentDisposition(contentDisposition)
.contentEncoding(contentEncoding)
.contentLanguage(contentLanguage)
.contentType(contentType)
.expires(expires)
.userMetadata(userMetadata)
.build();
s3BlobStore.putBlob(containerName, fromBlob);

s3BlobStore.copyBlob(containerName, fromName, containerName, toName,
CopyOptions.NONE);

Blob toBlob = s3BlobStore.getBlob(containerName, toName);
try (InputStream actual = toBlob.getPayload().openStream();
InputStream expected = byteSource.openStream()) {
assertThat(actual).hasContentEqualTo(expected);
}
ContentMetadata contentMetadata =
toBlob.getMetadata().getContentMetadata();
assertThat(contentMetadata.getContentDisposition()).isEqualTo(
contentDisposition);
assertThat(contentMetadata.getContentEncoding()).isEqualTo(
contentEncoding);
assertThat(contentMetadata.getContentLanguage()).isEqualTo(
contentLanguage);
assertThat(contentMetadata.getContentType()).isEqualTo(
contentType);
// TODO: expires
assertThat(toBlob.getMetadata().getUserMetadata()).isEqualTo(
userMetadata);
}

@Test
public void testCopyObjectReplaceMetadata() throws Exception {
String fromName = "from-name";
String toName = "to-name";
ByteSource byteSource = ByteSource.wrap(new byte[42]);
Blob fromBlob = s3BlobStore.blobBuilder(fromName)
.payload(byteSource)
.contentLength(byteSource.size())
.contentDisposition("attachment; filename=old.jpg")
.contentEncoding("compress")
.contentLanguage("en")
.contentType("audio/ogg")
.expires(new Date(1000))
.userMetadata(ImmutableMap.of(
"key1", "value1",
"key2", "value2"))
.build();
s3BlobStore.putBlob(containerName, fromBlob);

String contentDisposition = "attachment; filename=new.jpg";
String contentEncoding = "gzip";
String contentLanguage = "fr";
String contentType = "audio/mp4";
Date expires = new Date(2000);
ContentMetadata contentMetadata = ContentMetadataBuilder.create()
.contentDisposition(contentDisposition)
.contentEncoding(contentEncoding)
.contentLanguage(contentLanguage)
.contentType(contentType)
.expires(expires)
.build();
Map<String, String> userMetadata = ImmutableMap.of(
"key3", "value3",
"key4", "value4");
s3BlobStore.copyBlob(containerName, fromName, containerName, toName,
CopyOptions.builder()
.contentMetadata(contentMetadata)
.userMetadata(userMetadata)
.build());

Blob toBlob = s3BlobStore.getBlob(containerName, toName);
try (InputStream actual = toBlob.getPayload().openStream();
InputStream expected = byteSource.openStream()) {
assertThat(actual).hasContentEqualTo(expected);
}
ContentMetadata toContentMetadata =
toBlob.getMetadata().getContentMetadata();
assertThat(toContentMetadata.getContentDisposition()).isEqualTo(
contentDisposition);
assertThat(toContentMetadata.getContentEncoding()).isEqualTo(
contentEncoding);
assertThat(toContentMetadata.getContentLanguage()).isEqualTo(
contentLanguage);
assertThat(toContentMetadata.getContentType()).isEqualTo(
contentType);
// TODO: expires
assertThat(toBlob.getMetadata().getUserMetadata()).isEqualTo(
userMetadata);
}

@Test
public void testUnknownParameter() throws Exception {
S3Client s3Client = s3Context.unwrapApi(S3Client.class);
Expand Down

0 comments on commit 7b937c1

Please sign in to comment.