From 6518d58041d5434eaaa52ec4ab06a3ec7e756500 Mon Sep 17 00:00:00 2001 From: Zack Shoylev Date: Mon, 18 Aug 2014 19:03:50 -0500 Subject: [PATCH] JCLOUDS-568 This ensures content-type will not be escaped in swift metadata. This will allow changing the content type without reuploading the payload. --- .../v1/binders/BindMetadataToHeaders.java | 30 +++--- .../swift/v1/features/ObjectApi.java | 92 ++++++++-------- .../swift/v1/filters/ContentTypeFilter.java | 40 +++++++ .../swift/v1/features/ObjectApiLiveTest.java | 60 ++++++++--- .../swift/v1/features/ObjectApiMockTest.java | 102 +++++++++++------- 5 files changed, 209 insertions(+), 115 deletions(-) create mode 100644 openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/filters/ContentTypeFilter.java diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java index 4dabc68b40..499128b1b9 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java @@ -16,38 +16,36 @@ */ package org.jclouds.openstack.swift.v1.binders; -import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_METADATA_PREFIX; -import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX; -import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableMultimap.Builder; +import org.jclouds.http.HttpRequest; +import org.jclouds.rest.Binder; import java.util.Map; import java.util.Map.Entry; -import org.jclouds.http.HttpRequest; -import org.jclouds.rest.Binder; - -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableMultimap.Builder; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX; /** * Will lower-case header keys due to a swift implementation to return headers * in a different case than sent. ex. - * + * *
  * >> X-Account-Meta-MyDelete1: foo
  * >> X-Account-Meta-MyDelete2: bar
  * 
- * + * * results in: - * + * *
  * << X-Account-Meta-Mydelete1: foo
  * << X-Account-Meta-Mydelete2: bar
  * 
- * + * *

Note


* HTTP response headers keys are known to be case-insensitive, but this * practice of mixing up case will prevent metadata keys such as those in @@ -132,6 +130,8 @@ public ImmutableMultimap toHeaders(Map metadata) String keyInLowercase = keyVal.getKey().toLowerCase(); if (keyVal.getKey().startsWith(metadataPrefix)) { putMetadata(builder, keyInLowercase, keyVal.getValue()); + } else if (keyInLowercase.equals("content-type")) { + putMetadata(builder, "Content-Type", keyVal.getValue()); } else { putMetadata(builder, String.format("%s%s", metadataPrefix, keyInLowercase), keyVal.getValue()); } diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java index e4fccc620c..e379eaa9f4 100644 --- a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java @@ -16,23 +16,7 @@ */ package org.jclouds.openstack.swift.v1.features; -import static com.google.common.net.HttpHeaders.EXPECT; -import static javax.ws.rs.core.MediaType.APPLICATION_JSON; -import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_COPY_FROM; - -import java.util.Map; - -import javax.inject.Named; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.HEAD; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; - +import com.google.common.annotations.Beta; import org.jclouds.Fallbacks.FalseOnNotFoundOr404; import org.jclouds.Fallbacks.NullOnNotFoundOr404; import org.jclouds.Fallbacks.VoidOnNotFoundOr404; @@ -46,6 +30,7 @@ import org.jclouds.openstack.swift.v1.binders.SetPayload; import org.jclouds.openstack.swift.v1.domain.ObjectList; import org.jclouds.openstack.swift.v1.domain.SwiftObject; +import org.jclouds.openstack.swift.v1.filters.ContentTypeFilter; import org.jclouds.openstack.swift.v1.functions.ETagHeader; import org.jclouds.openstack.swift.v1.functions.ParseObjectFromResponse; import org.jclouds.openstack.swift.v1.functions.ParseObjectListFromResponse; @@ -58,7 +43,21 @@ import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.ResponseParser; -import com.google.common.annotations.Beta; +import javax.inject.Named; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import java.util.Map; + +import static com.google.common.net.HttpHeaders.EXPECT; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_COPY_FROM; /** * Provides access to the OpenStack Object Storage (Swift) Object API features. @@ -74,7 +73,7 @@ public interface ObjectApi { /** * Lists up to 10,000 objects. - * + * * @return an {@link ObjectList} of {@link SwiftObject} ordered by name or {@code null}. */ @Named("object:list") @@ -90,10 +89,10 @@ public interface ObjectApi { * Lists up to 10,000 objects. To control a large list of containers beyond * 10,000 objects, use the {@code marker} and {@code endMarker} parameters in the * {@link ListContainerOptions} class. - * - * @param options + * + * @param options * the {@link ListContainerOptions} for controlling the returned list. - * + * * @return an {@link ObjectList} of {@link SwiftObject} ordered by name or {@code null}. */ @Named("object:list") @@ -107,7 +106,7 @@ public interface ObjectApi { /** * Creates or updates a {@link SwiftObject}. - * + * * @param objectName * corresponds to {@link SwiftObject#getName()}. * @param payload @@ -124,14 +123,14 @@ public interface ObjectApi { /** * Creates or updates a {@link SwiftObject}. - * + * * @param objectName * corresponds to {@link SwiftObject#getName()}. * @param payload * corresponds to {@link SwiftObject#getPayload()}. * @param options * {@link PutOptions options} to control creating the {@link SwiftObject}. - * + * * @return {@link SwiftObject#getETag()} of the object. */ @Named("object:put") @@ -144,10 +143,10 @@ String put(@PathParam("objectName") String objectName, @BinderParam(SetPayload.c /** * Gets the {@link SwiftObject} metadata without its {@link Payload#openStream() body}. - * + * * @param objectName * corresponds to {@link SwiftObject#getName()}. - * + * * @return the {@link SwiftObject} or {@code null}, if not found. */ @Named("object:getWithoutBody") @@ -160,10 +159,10 @@ String put(@PathParam("objectName") String objectName, @BinderParam(SetPayload.c /** * Gets the {@link SwiftObject} including its {@link Payload#openStream() body}. - * + * * @param objectName * corresponds to {@link SwiftObject#getName()}. - * + * * @return the {@link SwiftObject} or {@code null}, if not found. */ @Named("object:get") @@ -176,12 +175,12 @@ String put(@PathParam("objectName") String objectName, @BinderParam(SetPayload.c /** * Gets the {@link SwiftObject} including its {@link Payload#openStream() body}. - * + * * @param objectName * corresponds to {@link SwiftObject#getName()}. * @param options * options to control the download. - * + * * @return the {@link SwiftObject} or {@code null}, if not found. */ @Named("object:get") @@ -194,32 +193,33 @@ String put(@PathParam("objectName") String objectName, @BinderParam(SetPayload.c /** * Creates or updates the metadata for a {@link SwiftObject}. - * + * * @param objectName * corresponds to {@link SwiftObject#getName()}. * @param metadata * the metadata to create or update. - * - * @return {@code true} if the metadata was successfully created or updated, + * + * @return {@code true} if the metadata was successfully created or updated, * {@code false} if not. */ @Named("object:updateMetadata") @POST @Fallback(FalseOnNotFoundOr404.class) @Path("/{objectName}") - @Produces("") + //@Produces("") + @RequestFilters(ContentTypeFilter.class) boolean updateMetadata(@PathParam("objectName") String objectName, @BinderParam(BindObjectMetadataToHeaders.class) Map metadata); /** * Deletes the metadata from a {@link SwiftObject}. - * + * * @param objectName * corresponds to {@link SwiftObject#getName()}. * @param metadata * corresponds to {@link SwiftObject#getMetadata()}. - * - * @return {@code true} if the metadata was successfully deleted, + * + * @return {@code true} if the metadata was successfully deleted, * {@code false} if not. */ @Named("object:deleteMetadata") @@ -231,7 +231,7 @@ boolean deleteMetadata(@PathParam("objectName") String objectName, /** * Deletes an object, if present. - * + * * @param objectName * corresponds to {@link SwiftObject#getName()}. */ @@ -242,20 +242,20 @@ boolean deleteMetadata(@PathParam("objectName") String objectName, void delete(@PathParam("objectName") String objectName); /** - * Copies an object from one container to another. - * + * Copies an object from one container to another. + * *

NOTE

* This is a server side copy. - * + * * @param destinationObject * the destination object name. * @param sourceContainer * the source container name. * @param sourceObject * the source object name. - * + * * @return {@code true} if the object was successfully copied, {@code false} if not. - * + * * @throws org.jclouds.openstack.swift.v1.CopyObjectException if the source or destination container do not exist. */ @Named("object:copy") @@ -288,14 +288,14 @@ boolean copy(@PathParam("destinationObject") String destinationObject, /** * Creates or updates a {@link SwiftObject}. - * + * * @param objectName * corresponds to {@link SwiftObject#getName()}. * @param payload * corresponds to {@link SwiftObject#getPayload()}. * @param metadata * corresponds to {@link SwiftObject#getMetadata()}. - * + * * @return {@link SwiftObject#getEtag()} of the object. * * @deprecated Please use {@link #put(String, Payload)} or {@link #put(String, Payload, PutOptions)} diff --git a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/filters/ContentTypeFilter.java b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/filters/ContentTypeFilter.java new file mode 100644 index 0000000000..197fcf061f --- /dev/null +++ b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/filters/ContentTypeFilter.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.openstack.swift.v1.filters; + +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; + +import javax.inject.Singleton; + +/** + * Signs the Keystone-based request. This will update the Authentication Token before 24 hours is up. + */ +@Singleton +public class ContentTypeFilter implements HttpRequestFilter { + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + if(!request.getHeaders().containsKey("Content-Type")) { + return request.toBuilder().addHeader("Content-Type", "").build(); + } else { + return request; + } + } + +} diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java index b73039812f..0339d26e97 100644 --- a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java +++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java @@ -16,21 +16,8 @@ */ package org.jclouds.openstack.swift.v1.features; -import static org.jclouds.http.options.GetOptions.Builder.tail; -import static org.jclouds.io.Payloads.newByteSourcePayload; -import static org.jclouds.openstack.swift.v1.options.ListContainerOptions.Builder.marker; -import static org.jclouds.util.Strings2.toStringAndClose; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; - -import java.io.IOException; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.TimeUnit; - +import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteSource; import org.jclouds.http.options.GetOptions; import org.jclouds.io.Payload; import org.jclouds.openstack.swift.v1.CopyObjectException; @@ -43,8 +30,20 @@ import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import com.google.common.collect.ImmutableMap; -import com.google.common.io.ByteSource; +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import static org.jclouds.http.options.GetOptions.Builder.tail; +import static org.jclouds.io.Payloads.newByteSourcePayload; +import static org.jclouds.openstack.swift.v1.options.ListContainerOptions.Builder.marker; +import static org.jclouds.util.Strings2.toStringAndClose; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; /** * Provides live tests for the {@link ObjectApi}. @@ -178,6 +177,33 @@ public void testUpdateMetadata() throws Exception { } } + public void testUpdateContentType() throws Exception { + for (String regionId : regions) { + ObjectApi objectApi = api.getObjectApiForRegionAndContainer(regionId, containerName); + + Map meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar", "Content-Type", "test/special"); + assertTrue(objectApi.updateMetadata(name, meta)); + + SwiftObject object = objectApi.get(name); + assertEquals(object.getPayload().getContentMetadata().getContentType(), "test/special"); + } + } + + public void testEmptyContentType() throws Exception { + for (String regionId : regions) { + ObjectApi objectApi = api.getObjectApiForRegionAndContainer(regionId, containerName); + + Map meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar", "Content-Type", "test/special1"); + assertTrue(objectApi.updateMetadata(name, meta)); + + Map meta2 = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar"); + assertTrue(objectApi.updateMetadata(name, meta2)); + + SwiftObject object = objectApi.get(name); + assertEquals(object.getPayload().getContentMetadata().getContentType(), "test/special1"); + } + } + public void testGet() throws Exception { for (String regionId : regions) { SwiftObject object = api.getObjectApiForRegionAndContainer(regionId, containerName).get(name, GetOptions.NONE); diff --git a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java index 348700e342..43a37bd96c 100644 --- a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java +++ b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java @@ -16,6 +16,31 @@ */ package org.jclouds.openstack.swift.v1.features; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteSource; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; +import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.http.HttpResponseException; +import org.jclouds.io.Payload; +import org.jclouds.io.payloads.ByteSourcePayload; +import org.jclouds.openstack.swift.v1.CopyObjectException; +import org.jclouds.openstack.swift.v1.SwiftApi; +import org.jclouds.openstack.swift.v1.domain.ObjectList; +import org.jclouds.openstack.swift.v1.domain.SwiftObject; +import org.jclouds.openstack.swift.v1.options.ListContainerOptions; +import org.jclouds.openstack.swift.v1.reference.SwiftHeaders; +import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.net.URI; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + import static com.google.common.base.Charsets.US_ASCII; import static com.google.common.net.HttpHeaders.RANGE; import static org.jclouds.Constants.PROPERTY_MAX_RETRIES; @@ -35,32 +60,6 @@ import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; -import java.io.IOException; -import java.net.URI; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; - -import org.jclouds.date.internal.SimpleDateFormatDateService; -import org.jclouds.http.HttpResponseException; -import org.jclouds.io.Payload; -import org.jclouds.io.payloads.ByteSourcePayload; -import org.jclouds.openstack.swift.v1.CopyObjectException; -import org.jclouds.openstack.swift.v1.SwiftApi; -import org.jclouds.openstack.swift.v1.domain.ObjectList; -import org.jclouds.openstack.swift.v1.domain.SwiftObject; -import org.jclouds.openstack.swift.v1.options.ListContainerOptions; -import org.jclouds.openstack.swift.v1.reference.SwiftHeaders; -import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest; -import org.testng.annotations.Test; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.io.ByteSource; -import com.squareup.okhttp.mockwebserver.MockResponse; -import com.squareup.okhttp.mockwebserver.MockWebServer; -import com.squareup.okhttp.mockwebserver.RecordedRequest; - /** * Provides mock tests for the {@link ObjectApi}. */ @@ -136,7 +135,7 @@ public void testListWithOptions() throws Exception { server.shutdown(); } } - + public void testListOptions() throws Exception { MockWebServer server = mockOpenStackServer(); server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); @@ -320,7 +319,7 @@ public void testCreateWithTimeout() throws Exception { overrides.setProperty(PROPERTY_RETRY_DELAY_START, 0 + ""); // exponential backoff already working for this call. This is the delay BETWEEN attempts. final SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift", overrides); - + api.getObjectApiForRegionAndContainer("DFW", "myContainer").put("myObject", new ByteSourcePayload(ByteSource.wrap("swifty".getBytes())), metadata(metadata)); fail("testReplaceTimeout test should have failed with an HttpResponseException."); @@ -353,7 +352,7 @@ public void testUpdateMetadata() throws Exception { } } - public void testUpdateMetadataContentType() throws Exception { + public void testUpdateMetadataContentTypeEmpty() throws Exception { MockWebServer server = mockOpenStackServer(); server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); server.enqueue(addCommonHeaders(objectResponse() @@ -380,6 +379,35 @@ public void testUpdateMetadataContentType() throws Exception { } } + public void testUpdateMetadataContentTypeUpdate() throws Exception { + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + server.enqueue(addCommonHeaders(objectResponse() + .addHeader(OBJECT_METADATA_PREFIX + "ApiName", "swift") + .addHeader(OBJECT_METADATA_PREFIX + "ApiVersion", "v1.1"))); + + try { + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); + + Map metadataContentType = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1", "content-type", "special/test"); + assertTrue(api.getObjectApiForRegionAndContainer("DFW", "myContainer").updateMetadata("myObject", metadataContentType)); + + assertEquals(server.getRequestCount(), 2); + assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); + RecordedRequest replaceRequest = server.takeRequest(); + assertEquals(replaceRequest.getHeaders("Content-Type").get(0), "special/test", "updateMetadata should send special/test content-type header, but sent " + + replaceRequest.getHeaders("Content-Type").get(0).toString()); + + assertEquals(replaceRequest.getRequestLine(), + "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject HTTP/1.1"); + for (Entry entry : metadata.entrySet()) { + assertEquals(replaceRequest.getHeader(OBJECT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue()); + } + } finally { + server.shutdown(); + } + } + public void testDeleteMetadata() throws Exception { MockWebServer server = mockOpenStackServer(); server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); @@ -439,7 +467,7 @@ public void testAlreadyDeleted() throws Exception { server.shutdown(); } } - + public void testCopyObject() throws Exception { MockWebServer server = mockOpenStackServer(); server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); @@ -449,10 +477,10 @@ public void testCopyObject() throws Exception { SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); assertTrue(api.getObjectApiForRegionAndContainer("DFW", "foo") .copy("bar.txt", "bar", "foo.txt")); - + assertEquals(server.getRequestCount(), 2); assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); - + RecordedRequest copyRequest = server.takeRequest(); assertEquals(copyRequest.getRequestLine(), "PUT /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/foo/bar.txt HTTP/1.1"); @@ -460,23 +488,23 @@ public void testCopyObject() throws Exception { server.shutdown(); } } - + @Test(expectedExceptions = CopyObjectException.class) public void testCopyObjectFail() throws InterruptedException, IOException { MockWebServer server = mockOpenStackServer(); server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404) .addHeader(SwiftHeaders.OBJECT_COPY_FROM, "/bogus/foo.txt"))); - + try { SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift"); // the following line will throw the CopyObjectException - api.getObjectApiForRegionAndContainer("DFW", "foo").copy("bar.txt", "bogus", "foo.txt"); + api.getObjectApiForRegionAndContainer("DFW", "foo").copy("bar.txt", "bogus", "foo.txt"); } finally { server.shutdown(); - } + } } - + private static final Map metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1"); static MockResponse objectResponse() {