diff --git a/pom.xml b/pom.xml index 63018a1b..97bb3550 100644 --- a/pom.xml +++ b/pom.xml @@ -235,7 +235,7 @@ UTF-8 - 1.9.0 + 1.9.1-SNAPSHOT diff --git a/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java b/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java index 6af61278..91f055ad 100644 --- a/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java +++ b/src/main/java/org/gaul/s3proxy/S3ProxyHandler.java @@ -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; @@ -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; @@ -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 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); } } diff --git a/src/test/java/org/gaul/s3proxy/S3ProxyTest.java b/src/test/java/org/gaul/s3proxy/S3ProxyTest.java index bee521bb..f45d7694 100644 --- a/src/test/java/org/gaul/s3proxy/S3ProxyTest.java +++ b/src/test/java/org/gaul/s3proxy/S3ProxyTest.java @@ -20,6 +20,8 @@ import java.io.InputStream; import java.net.URI; +import java.util.Date; +import java.util.Map; import java.util.Properties; import java.util.Random; @@ -27,6 +29,7 @@ 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; @@ -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; @@ -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 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 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);