You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by an...@apache.org on 2014/10/09 00:15:44 UTC

[1/5] Import openstack-swift from labs.

Repository: jclouds
Updated Branches:
  refs/heads/master 8432c66b7 -> 34663f3c2


http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiLiveTest.java
new file mode 100644
index 0000000..3e92af5
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiLiveTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.features;
+
+import static java.lang.String.format;
+import static org.jclouds.io.Payloads.newByteSourcePayload;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.domain.Segment;
+import org.jclouds.openstack.swift.v1.domain.SwiftObject;
+import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteSource;
+
+@Test(groups = "live", testName = "StaticLargeObjectApiLiveTest")
+public class StaticLargeObjectApiLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
+
+   private String name = getClass().getSimpleName();
+   private String containerName = getClass().getSimpleName() + "Container";
+   private byte[] megOf1s;
+   private byte[] megOf2s;
+
+   public void testNotPresentWhenDeleting() throws Exception {
+      for (String regionId : regions) {
+         api.getStaticLargeObjectApi(regionId, containerName).delete(UUID.randomUUID().toString());
+      }
+   }
+
+   public void testReplaceManifest() throws Exception {
+      for (String regionId : regions) {
+         ObjectApi objectApi = api.getObjectApi(regionId, containerName);
+
+         String etag1s = objectApi.put(name + "/1", newByteSourcePayload(ByteSource.wrap(megOf1s)));
+         assertMegabyteAndETagMatches(regionId, name + "/1", etag1s);
+
+         String etag2s = objectApi.put(name + "/2", newByteSourcePayload(ByteSource.wrap(megOf2s)));
+         assertMegabyteAndETagMatches(regionId, name + "/2", etag2s);
+
+         List<Segment> segments = ImmutableList.<Segment> builder()
+               .add(Segment.builder()
+                     .path(format("%s/%s/1", containerName, name)).etag(etag1s).sizeBytes(1024 * 1024)
+                     .build())
+               .add(Segment.builder()
+                     .path(format("%s/%s/2", containerName, name)).etag(etag2s).sizeBytes(1024 * 1024)
+                     .build())
+                     .build();
+
+         String etagOfEtags = api.getStaticLargeObjectApi(regionId, containerName).replaceManifest(
+               name, segments, ImmutableMap.of("myfoo", "Bar"));
+
+         assertNotNull(etagOfEtags);
+
+         SwiftObject bigObject = api.getObjectApi(regionId, containerName).get(name);
+         assertEquals(bigObject.getETag(), etagOfEtags);
+         assertEquals(bigObject.getPayload().getContentMetadata().getContentLength(), Long.valueOf(2 * 1024 * 1024));
+         assertEquals(bigObject.getMetadata(), ImmutableMap.of("myfoo", "Bar"));
+
+         // segments are visible
+         assertEquals(api.getContainerApi(regionId).get(containerName).getObjectCount(), 3);
+      }
+   }
+
+   @Test(dependsOnMethods = "testReplaceManifest")
+   public void testDelete() throws Exception {
+      for (String regionId : regions) {
+         api.getStaticLargeObjectApi(regionId, containerName).delete(name);
+         assertEquals(api.getContainerApi(regionId).get(containerName).getObjectCount(), 0);
+      }
+   }
+
+   protected void assertMegabyteAndETagMatches(String regionId, String name, String etag1s) {
+      SwiftObject object1s = api.getObjectApi(regionId, containerName).get(name);
+      assertEquals(object1s.getETag(), etag1s);
+      assertEquals(object1s.getPayload().getContentMetadata().getContentLength(), Long.valueOf(1024 * 1024));
+   }
+
+   @Override
+   @BeforeClass(groups = "live")
+   public void setup() {
+      super.setup();
+      for (String regionId : regions) {
+         boolean created = api.getContainerApi(regionId).create(containerName);
+         if (!created) {
+            deleteAllObjectsInContainer(regionId, containerName);
+         }
+      }
+
+      megOf1s = new byte[1024 * 1024];
+      megOf2s = new byte[1024 * 1024];
+
+      Arrays.fill(megOf1s, (byte) 1);
+      Arrays.fill(megOf2s, (byte) 2);
+   }
+
+   @Override
+   @AfterClass(groups = "live")
+   public void tearDown() {
+      for (String regionId : regions) {
+         deleteAllObjectsInContainer(regionId, containerName);
+         api.getContainerApi(regionId).deleteIfEmpty(containerName);
+      }
+      super.tearDown();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiMockTest.java
new file mode 100644
index 0000000..c5627a6
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApiMockTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.features;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.domain.Segment;
+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.net.HttpHeaders;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+@Test(groups = "unit", testName = "StaticLargeObjectApiMockTest")
+public class StaticLargeObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
+
+   public void testReplaceManifest() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().addHeader(HttpHeaders.ETAG, "\"abcd\"")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertEquals(
+               api.getStaticLargeObjectApi("DFW", "myContainer").replaceManifest(
+                     "myObject",
+                     ImmutableList
+                           .<Segment> builder()
+                           .add(Segment.builder().path("/mycontainer/objseg1").etag("0228c7926b8b642dfb29554cd1f00963")
+                                 .sizeBytes(1468006).build())
+                           .add(Segment.builder().path("/mycontainer/pseudodir/seg-obj2")
+                                 .etag("5bfc9ea51a00b790717eeb934fb77b9b").sizeBytes(1572864).build())
+                           .add(Segment.builder().path("/other-container/seg-final")
+                                 .etag("b9c3da507d2557c1ddc51f27c54bae51").sizeBytes(256).build()).build(),
+                     ImmutableMap.of("MyFoo", "Bar")), "abcd");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+
+         RecordedRequest replaceRequest = server.takeRequest();
+         assertRequest(replaceRequest, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject?multipart-manifest=put");
+         assertEquals(replaceRequest.getHeader(OBJECT_METADATA_PREFIX + "myfoo"), "Bar");
+         assertEquals(
+               new String(replaceRequest.getBody()),
+         "[{\"path\":\"/mycontainer/objseg1\",\"etag\":\"0228c7926b8b642dfb29554cd1f00963\",\"size_bytes\":1468006}," +
+          "{\"path\":\"/mycontainer/pseudodir/seg-obj2\",\"etag\":\"5bfc9ea51a00b790717eeb934fb77b9b\",\"size_bytes\":1572864}," +
+          "{\"path\":\"/other-container/seg-final\",\"etag\":\"b9c3da507d2557c1ddc51f27c54bae51\",\"size_bytes\":256}]");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testDelete() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         api.getStaticLargeObjectApi("DFW", "myContainer").delete("myObject");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "DELETE", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject?multipart-manifest=delete");
+
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testAlreadyDeleted() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         api.getStaticLargeObjectApi("DFW", "myContainer").delete("myObject");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "DELETE", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject?multipart-manifest=delete");
+      } finally {
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/UrlEncodeAndJoinOnNewlineTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/UrlEncodeAndJoinOnNewlineTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/UrlEncodeAndJoinOnNewlineTest.java
new file mode 100644
index 0000000..c811571
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/UrlEncodeAndJoinOnNewlineTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.features;
+
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.openstack.swift.v1.features.BulkApi.UrlEncodeAndJoinOnNewline;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+
+@Test(groups = "unit", testName = "UrlEncodeAndJoinOnNewlineTest")
+public class UrlEncodeAndJoinOnNewlineTest {
+   UrlEncodeAndJoinOnNewline binder = new UrlEncodeAndJoinOnNewline();
+
+   public void urlEncodesPaths() {
+      HttpRequest request = HttpRequest.builder()
+                                       .method("DELETE")
+                                       .endpoint("https://storage101.dfw1.clouddrive.com/v1/MossoCloudFS_XXXXXX/")
+                                       .addQueryParam("bulk-delete").build();
+
+      request = binder.bindToRequest(request, ImmutableList.<String> builder()
+            .add("/v1/12345678912345/mycontainer/home/xx<yy")
+            .add("/v1/12345678912345/mycontainer/../image.gif").build());
+
+      assertEquals(request.getPayload().getRawContent(), "/v1/12345678912345/mycontainer/home/xx%3Cyy\n"
+            + "/v1/12345678912345/mycontainer/../image.gif");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/internal/BaseSwiftApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/internal/BaseSwiftApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/internal/BaseSwiftApiLiveTest.java
new file mode 100644
index 0000000..a5cf538
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/internal/BaseSwiftApiLiveTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.internal;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.apis.BaseApiLiveTest;
+import org.jclouds.location.reference.LocationConstants;
+import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties;
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.domain.BulkDeleteResponse;
+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.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Uninterruptibles;
+
+@Test(groups = "live", testName = "BaseSwiftApiLiveTest")
+public abstract class BaseSwiftApiLiveTest<A extends SwiftApi> extends BaseApiLiveTest<A> {
+
+   protected Set<String> regions;
+
+   protected BaseSwiftApiLiveTest() {
+      provider = "openstack-swift";
+   }
+
+   @Override
+   @BeforeClass(groups = "live")
+   public void setup() {
+      super.setup();
+      String providedRegion = System.getProperty("test." + LocationConstants.PROPERTY_REGION);
+      if (providedRegion != null) {
+        regions = ImmutableSet.of(providedRegion);
+      } else {
+        regions = api.getConfiguredRegions();
+      }
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      setIfTestSystemPropertyPresent(props, KeystoneProperties.CREDENTIAL_TYPE);
+      setIfTestSystemPropertyPresent(props, LocationConstants.PROPERTY_REGION);
+      return props;
+   }
+
+   protected void deleteAllObjectsInContainer(String regionId, final String containerName) {
+      Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
+
+      ObjectList objects = api.getObjectApi(regionId, containerName).list(new ListContainerOptions());
+      if (objects == null) {
+         return;
+      }
+      List<String> pathsToDelete = Lists.transform(objects, new Function<SwiftObject, String>() {
+         public String apply(SwiftObject input) {
+            return containerName + "/" + input.getName();
+         }
+      });
+      if (!pathsToDelete.isEmpty()) {
+         BulkDeleteResponse response = api.getBulkApi(regionId).bulkDelete(pathsToDelete);
+         checkState(response.getErrors().isEmpty(), "Errors deleting paths %s: %s", pathsToDelete, response);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptionsTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptionsTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptionsTest.java
new file mode 100644
index 0000000..1da648b
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptionsTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.options;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.VERSIONS_LOCATION;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_QUOTA_BYTES;
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Tests behavior of {@link CreateContainerOptions}.
+ */
+@Test(groups = "unit")
+public class CreateContainerOptionsTest {
+
+   public void testMetadata() {
+      CreateContainerOptions options =
+            new CreateContainerOptions().metadata(ImmutableMap.of("ApiName", "swift", "metaKey2", "Value2", "METAKEY3", "VALUE 3 "));
+
+      Multimap<String, String> headers = options.buildRequestHeaders();
+      assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "apiname"), ImmutableList.of("swift"));
+      assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "metakey2"), ImmutableList.of("Value2"));
+      assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "metakey3"), ImmutableList.of("VALUE 3 "));
+   }
+
+   public void testHeaders() {
+      CreateContainerOptions options =
+            new CreateContainerOptions().headers(ImmutableMultimap.of(CONTAINER_QUOTA_BYTES, "5120", CONTAINER_METADATA_PREFIX + "apiname", "swift"));
+
+      Multimap<String, String> headers = options.buildRequestHeaders();
+      assertEquals(headers.get(CONTAINER_QUOTA_BYTES), ImmutableList.of("5120"));
+      assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "apiname"), ImmutableList.of("swift"));
+   }
+
+   public void testAnybodyRead() {
+      CreateContainerOptions options =
+            new CreateContainerOptions().headers(ImmutableMultimap.of(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ));
+      assertEquals(options.buildRequestHeaders().get(CONTAINER_READ), ImmutableList.of(CONTAINER_ACL_ANYBODY_READ));
+   }
+
+   public void testVersionsLocation() {
+      CreateContainerOptions options =
+            new CreateContainerOptions().headers(ImmutableMultimap.of(VERSIONS_LOCATION, "containerWithVersions"));
+      assertEquals(options.buildRequestHeaders().get(VERSIONS_LOCATION), ImmutableList.of("containerWithVersions"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/PutOptionsTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/PutOptionsTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/PutOptionsTest.java
new file mode 100644
index 0000000..fddf4cd
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/PutOptionsTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.options;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_DELETE_AT;
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+
+/**
+ * Tests behavior of {@link PutOptions}.
+ */
+@Test(groups = "unit")
+public class PutOptionsTest {
+
+   public void testPutMetadata() {
+      PutOptions options = 
+            new PutOptions().metadata(ImmutableMap.of("ApiName", "swift"));
+      assertEquals(options.buildRequestHeaders().get(OBJECT_METADATA_PREFIX + "apiname"), ImmutableList.of("swift"));
+
+   }
+
+   public void testPutHeaders() {
+      PutOptions options = 
+            new PutOptions().headers(ImmutableMultimap.of(OBJECT_DELETE_AT, "123456789"));
+      assertEquals(options.buildRequestHeaders().get(OBJECT_DELETE_AT), ImmutableList.of("123456789"));
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptionsTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptionsTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptionsTest.java
new file mode 100644
index 0000000..00f95f7
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptionsTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.options;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_QUOTA_BYTES;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.VERSIONS_LOCATION;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_DIRECTORY_TYPE;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_ERROR;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_INDEX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_LISTINGS;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.STATIC_WEB_LISTINGS_CSS;
+import static org.testng.Assert.assertEquals;
+
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.net.MediaType;
+
+/**
+ * Tests behavior of {@link UpdateContainerOptions}.
+ */
+@Test(groups = "unit")
+public class UpdateContainerOptionsTest {
+
+   public void testAnybodyRead() {
+      UpdateContainerOptions options = new UpdateContainerOptions().anybodyRead();
+      assertEquals(options.buildRequestHeaders().get(CONTAINER_READ), ImmutableList.of(CONTAINER_ACL_ANYBODY_READ));
+   }
+
+   public void testAnybodyReadViaHeaders() {
+      UpdateContainerOptions options =
+            new UpdateContainerOptions().headers(ImmutableMultimap.of(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ));
+      assertEquals(options.buildRequestHeaders().get(CONTAINER_READ), ImmutableList.of(CONTAINER_ACL_ANYBODY_READ));
+   }
+
+   public void testVersionsLocation() {
+      UpdateContainerOptions options = new UpdateContainerOptions().versionsLocation("containerWithVersions");
+      assertEquals(options.buildRequestHeaders().get(VERSIONS_LOCATION), ImmutableList.of("containerWithVersions"));
+   }
+
+   public void testVersionsLocationViaHeaders() {
+      UpdateContainerOptions options =
+            new UpdateContainerOptions().headers(ImmutableMultimap.of(VERSIONS_LOCATION, "containerWithVersions"));
+      assertEquals(options.buildRequestHeaders().get(VERSIONS_LOCATION), ImmutableList.of("containerWithVersions"));
+   }
+
+   public void testMetadata() {
+      UpdateContainerOptions options =
+            new UpdateContainerOptions().metadata(ImmutableMap.of("ApiName", "swift", "metaKey2", "Value2", "METAKEY3", "VALUE 3 "));
+
+      Multimap<String, String> headers = options.buildRequestHeaders();
+      assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "apiname"), ImmutableList.of("swift"));
+      assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "metakey2"), ImmutableList.of("Value2"));
+      assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "metakey3"), ImmutableList.of("VALUE 3 "));
+   }
+
+   public void testHeaders() {
+      UpdateContainerOptions options =
+            new UpdateContainerOptions().headers(ImmutableMultimap.of(CONTAINER_QUOTA_BYTES, "5120", CONTAINER_METADATA_PREFIX + "apiname", "swift"));
+
+      Multimap<String, String> headers = options.buildRequestHeaders();
+      assertEquals(headers.get(CONTAINER_QUOTA_BYTES), ImmutableList.of("5120"));
+      assertEquals(headers.get(CONTAINER_METADATA_PREFIX + "apiname"), ImmutableList.of("swift"));
+   }
+
+   public void testStaticWebsiteDirectoryType() {
+      MediaType appDir = MediaType.create("application", "directory");
+      Multimap<String, String> headers = ImmutableMultimap.of(STATIC_WEB_DIRECTORY_TYPE, appDir.toString());
+      UpdateContainerOptions options = new UpdateContainerOptions().headers(headers);
+      assertEquals(options.buildRequestHeaders().get(STATIC_WEB_DIRECTORY_TYPE), ImmutableList.of(appDir.toString()));
+   }
+
+   public void testStaticWebsiteIndexPage() {
+      Multimap<String, String> headers = ImmutableMultimap.of(STATIC_WEB_INDEX, "index.html");
+      UpdateContainerOptions options = new UpdateContainerOptions().headers(headers);
+      assertEquals(options.buildRequestHeaders().get(STATIC_WEB_INDEX), ImmutableList.of("index.html"));
+   }
+
+   public void testStaticWebsiteErrorPage() {
+      Multimap<String, String> headers = ImmutableMultimap.of(STATIC_WEB_ERROR, "error.html");
+      UpdateContainerOptions options = new UpdateContainerOptions().headers(headers);
+      assertEquals(options.buildRequestHeaders().get(STATIC_WEB_ERROR), ImmutableList.of("error.html"));
+   }
+
+   public void testEnableStaticWebsiteListings() {
+      Multimap<String, String> headers = ImmutableMultimap.of(STATIC_WEB_LISTINGS, "true");
+      UpdateContainerOptions options = new UpdateContainerOptions().headers(headers);
+      assertEquals(options.buildRequestHeaders().get(STATIC_WEB_LISTINGS), ImmutableList.of("true"));
+   }
+
+   public void testDiableStaticWebsiteListings() {
+      Multimap<String, String> headers = ImmutableMultimap.of(STATIC_WEB_LISTINGS, "false");
+      UpdateContainerOptions options = new UpdateContainerOptions().headers(headers);
+      assertEquals(options.buildRequestHeaders().get(STATIC_WEB_LISTINGS), ImmutableList.of("false"));
+   }
+
+   public void testStaticWebsiteListingsCSS() {
+      Multimap<String, String> headers = ImmutableMultimap.of(STATIC_WEB_LISTINGS_CSS, "listings.css");
+      UpdateContainerOptions options = new UpdateContainerOptions().headers(headers);
+      assertEquals(options.buildRequestHeaders().get(STATIC_WEB_LISTINGS_CSS), ImmutableList.of("listings.css"));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/resources/access.json
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/resources/access.json b/apis/openstack-swift/src/test/resources/access.json
new file mode 100644
index 0000000..8e0a69c
--- /dev/null
+++ b/apis/openstack-swift/src/test/resources/access.json
@@ -0,0 +1,249 @@
+{
+    "access":{
+        "token":{
+            "id":"bb03a23aa8271291a7aaa9aaa2aaaaaa",
+            "expires":"2013-08-02T16:55:24.229-05:00",
+            "tenant":{
+                "id":"888888",
+                "name":"888888"
+            },
+            "RAX-AUTH:authenticatedBy":[
+                "PASSWORD"
+            ]
+        },
+        "serviceCatalog":[
+            {
+                "name":"cloudFilesCDN",
+                "endpoints":[
+                    {
+                        "region":"ORD",
+                        "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                        "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"
+                    },
+                    {
+                        "region":"DFW",
+                        "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                        "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"
+                    },
+                    {
+                        "region":"SYD",
+                        "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                        "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"
+                    }
+                ],
+                "type":"rax:object-cdn"
+            },
+            {
+                "name":"cloudFiles",
+                "endpoints":[
+                    {
+                        "region":"ORD",
+                        "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                        "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                        "internalURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"
+                    },
+                    {
+                        "region":"DFW",
+                        "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                        "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                        "internalURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"
+                    },
+                    {
+                        "region":"SYD",
+                        "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                        "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                        "internalURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9"
+                    }
+                ],
+                "type":"object-store"
+            },
+            {
+                "name":"cloudLoadBalancers",
+                "endpoints":[
+                    {
+                        "region":"SYD",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888"
+                    },
+                    {
+                        "region":"DFW",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888"
+                    },
+                    {
+                        "region":"ORD",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888"
+                    }
+                ],
+                "type":"rax:load-balancer"
+            },
+            {
+                "name":"cloudDatabases",
+                "endpoints":[
+                    {
+                        "region":"SYD",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888"
+                    },
+                    {
+                        "region":"DFW",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888"
+                    },
+                    {
+                        "region":"ORD",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888"
+                    }
+                ],
+                "type":"rax:database"
+            },
+            {
+                "name":"cloudBlockStorage",
+                "endpoints":[
+                    {
+                        "region":"SYD",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1\/888888"
+                    },
+                    {
+                        "region":"DFW",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1\/888888"
+                    },
+                    {
+                        "region":"ORD",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1\/888888"
+                    }
+                ],
+                "type":"volume"
+            },
+            {
+                "name":"cloudServersOpenStack",
+                "endpoints":[
+                    {
+                        "region":"SYD",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v2\/888888",
+                        "versionInfo":"https:\/\/syd.servers.api.rackspacecloud.com\/v2",
+                        "versionList":"https:\/\/syd.servers.api.rackspacecloud.com\/",
+                        "versionId":"2"
+                    },
+                    {
+                        "region":"DFW",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v2\/888888",
+                        "versionInfo":"https:\/\/dfw.servers.api.rackspacecloud.com\/v2",
+                        "versionList":"https:\/\/dfw.servers.api.rackspacecloud.com\/",
+                        "versionId":"2"
+                    },
+                    {
+                        "region":"ORD",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v2\/888888",
+                        "versionInfo":"https:\/\/ord.servers.api.rackspacecloud.com\/v2",
+                        "versionList":"https:\/\/ord.servers.api.rackspacecloud.com\/",
+                        "versionId":"2"
+                    }
+                ],
+                "type":"compute"
+            },
+            {
+                "name":"autoscale",
+                "endpoints":[
+                    {
+                        "region":"ORD",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888",
+                        "versionInfo":null,
+                        "versionList":null,
+                        "versionId":"1.0"
+                    },
+                    {
+                        "region":"DFW",
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888",
+                        "versionInfo":null,
+                        "versionList":null,
+                        "versionId":"1.0"
+                    }
+                ],
+                "type":"rax:autoscale"
+            },
+            {
+                "name":"cloudMonitoring",
+                "endpoints":[
+                    {
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888"
+                    }
+                ],
+                "type":"rax:monitor"
+            },
+            {
+                "name":"cloudBackup",
+                "endpoints":[
+                    {
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888"
+                    }
+                ],
+                "type":"rax:backup"
+            },
+            {
+                "name":"cloudServers",
+                "endpoints":[
+                    {
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888",
+                        "versionInfo":"https:\/\/servers.api.rackspacecloud.com\/v1.0",
+                        "versionList":"https:\/\/servers.api.rackspacecloud.com\/",
+                        "versionId":"1.0"
+                    }
+                ],
+                "type":"compute"
+            },
+            {
+                "name":"cloudDNS",
+                "endpoints":[
+                    {
+                        "tenantId":"888888",
+                        "publicURL":"URL/v1.0\/888888"
+                    }
+                ],
+                "type":"rax:dns"
+            }
+        ],
+        "user":{
+            "id":"335853",
+            "roles":[
+                {
+                    "id":"10000150",
+                    "description":"Checkmate Access role",
+                    "name":"checkmate"
+                },
+                {
+                    "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9",
+                    "id":"5",
+                    "description":"A Role that allows a user access to keystone Service methods",
+                    "name":"object-store:default"
+                },
+                {
+                    "tenantId":"888888",
+                    "id":"6",
+                    "description":"A Role that allows a user access to keystone Service methods",
+                    "name":"compute:default"
+                },
+                {
+                    "id":"3",
+                    "description":"User Admin Role.",
+                    "name":"identity:user-admin"
+                }
+            ],
+            "name":"test",
+            "RAX-AUTH:defaultRegion":"ORD"
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/resources/container_list.json
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/resources/container_list.json b/apis/openstack-swift/src/test/resources/container_list.json
new file mode 100644
index 0000000..554f5de
--- /dev/null
+++ b/apis/openstack-swift/src/test/resources/container_list.json
@@ -0,0 +1,12 @@
+[
+    {
+        "name": "test_container_1",
+        "count": 2,
+        "bytes": 78
+    },
+    {
+        "name": "test_container_2",
+        "count": 1,
+        "bytes": 17
+    }
+]

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/resources/logback.xml
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/resources/logback.xml b/apis/openstack-swift/src/test/resources/logback.xml
new file mode 100644
index 0000000..ce891f1
--- /dev/null
+++ b/apis/openstack-swift/src/test/resources/logback.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+<!--
+
+    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.
+
+-->
+<configuration scan="false">
+    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
+        <file>target/test-data/jclouds.log</file>
+
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+
+    <appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">
+        <file>target/test-data/jclouds-wire.log</file>
+
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+
+    <appender name="BLOBSTOREFILE" class="ch.qos.logback.core.FileAppender">
+        <file>target/test-data/jclouds-blobstore.log</file>
+
+        <encoder>
+            <Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
+        </encoder>
+    </appender>
+    
+    <root>
+        <level value="warn" />
+    </root>
+
+    <logger name="org.jclouds">
+        <level value="DEBUG" />
+        <appender-ref ref="FILE" />
+    </logger>
+
+<!--
+    <logger name="jclouds.wire">
+        <level value="DEBUG" />
+        <appender-ref ref="WIREFILE" />
+    </logger>
+-->
+
+    <logger name="jclouds.headers">
+        <level value="DEBUG" />
+        <appender-ref ref="WIREFILE" />
+    </logger>
+
+    <logger name="jclouds.blobstore">
+        <level value="DEBUG" />
+        <appender-ref ref="BLOBSTOREFILE" />
+    </logger>
+
+</configuration>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/resources/object_list.json
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/resources/object_list.json b/apis/openstack-swift/src/test/resources/object_list.json
new file mode 100644
index 0000000..afe8c77
--- /dev/null
+++ b/apis/openstack-swift/src/test/resources/object_list.json
@@ -0,0 +1,23 @@
+[
+    {
+        "name": "test_obj_1",
+        "hash": "4281c348eaf83e70ddce0e07221c3d28",
+        "bytes": 14,
+        "content_type": "application/octet-stream",
+        "last_modified": "2009-02-03T05:26:32.612278"
+    },
+    {
+        "name": "test_obj_2",
+        "hash": "b039efe731ad111bc1b0ef221c3849d0",
+        "bytes": 64,
+        "content_type": "application/octet-stream",
+        "last_modified": "2009-02-03T05:26:32.612278"
+    },
+    {
+        "name": "test obj 3",
+        "hash": "0b2e80bd0744d9ebb20484149a57c82e",
+        "bytes": 123,
+        "content_type": "application/octet-stream",
+        "last_modified": "2014-05-20T05:26:32.612278"
+    }
+]

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/pom.xml
----------------------------------------------------------------------
diff --git a/apis/pom.xml b/apis/pom.xml
index 32722fe..5226afe 100644
--- a/apis/pom.xml
+++ b/apis/pom.xml
@@ -45,6 +45,7 @@
     <module>openstack-cinder</module>
     <module>openstack-nova</module>
     <module>openstack-nova-ec2</module>
+    <module>openstack-swift</module>
     <module>openstack-trove</module>
     <module>cloudfiles</module>
     <module>cloudservers</module>


[3/5] Import openstack-swift from labs.

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java
new file mode 100644
index 0000000..cc934e4
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectFromResponse.java
@@ -0,0 +1,87 @@
+/*
+ * 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.functions;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.net.HttpHeaders.ETAG;
+import static com.google.common.net.HttpHeaders.LAST_MODIFIED;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_DELETE_AT;
+
+import java.net.URI;
+import java.util.Date;
+
+import javax.inject.Inject;
+
+import org.jclouds.date.DateService;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.io.MutableContentMetadata;
+import org.jclouds.io.Payload;
+import org.jclouds.openstack.swift.v1.domain.SwiftObject;
+import org.jclouds.rest.InvocationContext;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+
+import com.google.common.base.Function;
+import com.google.common.hash.HashCode;
+
+public class ParseObjectFromResponse implements Function<HttpResponse, SwiftObject>,
+      InvocationContext<ParseObjectFromResponse> {
+   private final DateService dates;
+
+   @Inject
+   ParseObjectFromResponse(DateService dates) {
+      this.dates = dates;
+   }
+
+   private String uri;
+   private String name;
+
+   @Override
+   public SwiftObject apply(HttpResponse from) {
+
+      Payload payload = from.getPayload();
+      MutableContentMetadata contentMeta = payload.getContentMetadata();
+
+      String deleteAt = from.getFirstHeaderOrNull(OBJECT_DELETE_AT);
+      if (deleteAt != null) {
+         long fromEpoch = Long.parseLong(from.getFirstHeaderOrNull(OBJECT_DELETE_AT)) * 1000;
+         contentMeta.setExpires(new Date(fromEpoch));
+         payload.setContentMetadata(contentMeta);
+      }
+
+      String etag = from.getFirstHeaderOrNull(ETAG);
+      if (etag != null) {
+         payload.getContentMetadata().setContentMD5(HashCode.fromBytes(base16().lowerCase().decode(etag)));
+      }
+
+      return SwiftObject.builder()
+            .uri(URI.create(uri))
+            .name(name)
+            .etag(etag)
+            .payload(payload)
+            .lastModified(dates.rfc822DateParse(from.getFirstHeaderOrNull(LAST_MODIFIED)))
+            .headers(from.getHeaders())
+            .metadata(EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders())).build();
+   }
+
+   @Override
+   public ParseObjectFromResponse setContext(HttpRequest request) {
+      this.uri = request.getEndpoint().toString();
+      this.name = GeneratedHttpRequest.class.cast(request).getInvocation().getArgs().get(0).toString();
+      return this;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java
new file mode 100644
index 0000000..ff12e0f
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseObjectListFromResponse.java
@@ -0,0 +1,113 @@
+/*
+ * 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.functions;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static org.jclouds.http.Uris.uriBuilder;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.io.Payload;
+import org.jclouds.io.Payloads;
+import org.jclouds.openstack.swift.v1.domain.Container;
+import org.jclouds.openstack.swift.v1.domain.ObjectList;
+import org.jclouds.openstack.swift.v1.domain.SwiftObject;
+import org.jclouds.rest.InvocationContext;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.google.common.hash.HashCode;
+import com.google.common.io.ByteSource;
+
+public class ParseObjectListFromResponse implements Function<HttpResponse, ObjectList>,
+      InvocationContext<ParseObjectListFromResponse> {
+
+   private static final class InternalObject {
+      String name;
+      String hash;
+      long bytes;
+      String content_type;
+      Date last_modified;
+      Date expires;
+   }
+
+   private final ParseJson<List<InternalObject>> json;
+   private final ParseContainerFromHeaders parseContainer;
+
+   @Inject
+   ParseObjectListFromResponse(ParseJson<List<InternalObject>> json, ParseContainerFromHeaders parseContainer) {
+      this.json = json;
+      this.parseContainer = parseContainer;
+   }
+
+   private ToSwiftObject toSwiftObject;
+
+   @Override
+   public ObjectList apply(HttpResponse from) {
+      List<SwiftObject> objects = Lists.transform(json.apply(from), toSwiftObject);
+      Container container = parseContainer.apply(from);
+      return ObjectList.create(objects, container);
+   }
+
+   static class ToSwiftObject implements Function<InternalObject, SwiftObject> {
+      private final String containerUri;
+
+      ToSwiftObject(String containerUri) {
+         this.containerUri = containerUri;
+      }
+
+      @Override
+      public SwiftObject apply(InternalObject input) {
+         return SwiftObject.builder()
+               .uri(uriBuilder(containerUri).clearQuery().appendPath(input.name).build())
+               .name(input.name)
+               .etag(input.hash)
+               .payload(payload(input.bytes, input.hash, input.content_type, input.expires))
+               .lastModified(input.last_modified).build();
+      }
+   }
+
+   @Override
+   public ParseObjectListFromResponse setContext(HttpRequest request) {
+      parseContainer.name = GeneratedHttpRequest.class.cast(request).getCaller().get().getArgs().get(1).toString();
+      String containerUri = request.getEndpoint().toString();
+      int queryIndex = containerUri.indexOf('?');
+      if (queryIndex != -1) {
+         containerUri = containerUri.substring(0, queryIndex);
+      }
+      toSwiftObject = new ToSwiftObject(containerUri);
+      return this;
+   }
+
+   private static Payload payload(long bytes, String hash, String contentType, Date expires) {
+      Payload payload = Payloads.newByteSourcePayload(ByteSource.empty());
+      payload.getContentMetadata().setContentLength(bytes);
+      payload.getContentMetadata().setContentType(contentType);
+      payload.getContentMetadata().setExpires(expires);
+      if (hash != null) {
+         payload.getContentMetadata().setContentMD5(HashCode.fromBytes(base16().lowerCase().decode(hash)));
+      }
+      return payload;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java
new file mode 100644
index 0000000..2bde41c
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java
@@ -0,0 +1,91 @@
+/*
+ * 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.handlers;
+
+import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.jclouds.blobstore.ContainerNotFoundException;
+import org.jclouds.blobstore.KeyNotFoundException;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
+import org.jclouds.openstack.swift.v1.CopyObjectException;
+import org.jclouds.openstack.swift.v1.reference.SwiftHeaders;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.InsufficientResourcesException;
+
+// TODO: is there error spec someplace? let's type errors, etc.
+public class SwiftErrorHandler implements HttpErrorHandler {
+   public static final String PREFIX = "^/v[0-9][^/]*/[a-zA-Z]+_[^/]+/";
+   public static final Pattern CONTAINER_PATH = Pattern.compile(PREFIX + "([^/]+)$");
+   public static final Pattern CONTAINER_KEY_PATH = Pattern.compile(PREFIX + "([^/]+)/(.*)");
+
+   public void handleError(HttpCommand command, HttpResponse response) {
+      // it is important to always read fully and close streams
+      byte[] data = closeClientButKeepContentStream(response);
+      String message = data != null ? new String(data) : null;
+
+      Exception exception = message != null ? new HttpResponseException(command, response, message)
+               : new HttpResponseException(command, response);
+      message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(),
+               response.getStatusLine());
+      switch (response.getStatusCode()) {
+         case 401:
+            exception = new AuthorizationException(exception.getMessage(), exception);
+            break;
+         case 404:
+            Exception oldException = exception;         
+            String sourcePath = command.getCurrentRequest().getFirstHeaderOrNull(SwiftHeaders.OBJECT_COPY_FROM);
+            if (sourcePath != null) {
+               // the path returned here is in the form "/v1/tenant-id/destContainer/destObject"
+               String path = command.getCurrentRequest().getEndpoint().getPath();
+               int startOfDestinationPath = path.lastIndexOf("/", path.lastIndexOf("/") - 1);
+               // get the "/destContainer/destObject" portion of the path
+               String destinationPath = path.substring(startOfDestinationPath);
+               
+               exception = new CopyObjectException(sourcePath, destinationPath, message);
+               exception.initCause(oldException);
+            } else if (!command.getCurrentRequest().getMethod().equals("DELETE")) {
+               String path = command.getCurrentRequest().getEndpoint().getPath();
+               Matcher matcher = CONTAINER_PATH.matcher(path);
+               
+               if (matcher.find()) {
+                  exception = new ContainerNotFoundException(matcher.group(1), message);
+                  exception.initCause(oldException);
+               } else {
+                  matcher = CONTAINER_KEY_PATH.matcher(path);
+                  if (matcher.find()) {
+                     exception = new KeyNotFoundException(matcher.group(1), matcher.group(2), message);
+                     exception.initCause(oldException);
+                  }
+               }
+            }
+            break;
+         case 409:
+            exception = new IllegalStateException(exception.getMessage(), exception);
+            break;
+         case 413:
+            exception = new InsufficientResourcesException(exception.getMessage(), exception);
+            break;
+      }
+      command.setException(exception);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptions.java
new file mode 100644
index 0000000..2014f61
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/CreateContainerOptions.java
@@ -0,0 +1,108 @@
+/*
+ * 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.options;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.VERSIONS_LOCATION;
+
+import java.util.Map;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders;
+
+import com.google.common.collect.Multimap;
+
+/**
+ * Options for creating a {@link Container} 
+ * 
+ * @see ContainerApi#create(String, CreateContainerOptions)
+ */
+public class CreateContainerOptions extends BaseHttpRequestOptions {
+
+   public static final CreateContainerOptions NONE = new CreateContainerOptions();
+
+   /** 
+    * Sets the headers on a container at creation.
+    */
+   public CreateContainerOptions headers(Multimap<String, String> headers) {
+      this.headers.putAll(headers);
+      return this;
+   }
+
+   /**
+    * Sets the metadata on a container at creation. 
+    */
+   public CreateContainerOptions metadata(Map<String, String> metadata) {
+      this.headers.putAll(bindMetadataToHeaders.toHeaders(metadata));
+      return this;
+   }
+
+   /**
+    * Sets the public ACL on the container so that anybody can read it.
+    */
+   public CreateContainerOptions anybodyRead() {
+      this.headers.put(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ);
+      return this;
+   }
+
+   /**
+    * Sets the container that will contain object versions.
+    */
+   public CreateContainerOptions versionsLocation(String containerName) {
+      this.headers.put(VERSIONS_LOCATION, containerName);
+      return this;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see CreateContainerOptions#anybodyRead
+       */
+      public static CreateContainerOptions anybodyRead() {
+         CreateContainerOptions options = new CreateContainerOptions();
+         return options.anybodyRead();
+      }
+
+      /**
+       * @see CreateContainerOptions#headers
+       */
+      public static CreateContainerOptions headers(Multimap<String, String> headers) {
+         CreateContainerOptions options = new CreateContainerOptions();
+         return options.headers(headers);
+      }
+
+      /**
+       * @see CreateContainerOptions#metadata
+       */
+      public static CreateContainerOptions metadata(Map<String, String> metadata) {
+         CreateContainerOptions options = new CreateContainerOptions();
+         return options.metadata(metadata);
+      }
+
+      /**
+       * @see CreateContainerOptions#versionsLocation
+       */
+      public static CreateContainerOptions versionsLocation(String containerName) {
+         CreateContainerOptions options = new CreateContainerOptions();
+         return options.versionsLocation(containerName);
+      }
+   }
+
+   private static final BindMetadataToHeaders bindMetadataToHeaders = new BindMetadataToHeaders(CONTAINER_METADATA_PREFIX);
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainerOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainerOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainerOptions.java
new file mode 100644
index 0000000..8b01aae
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainerOptions.java
@@ -0,0 +1,132 @@
+/*
+ * 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.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+
+/**
+ * Options for listing containers. 
+ * 
+ * @see ContainerApi#list(ListContainerOptions)
+ */
+public class ListContainerOptions extends BaseHttpRequestOptions {
+   public static final ListContainerOptions NONE = new ListContainerOptions();
+
+   /** 
+    * list operation returns no more than this amount. 
+    */
+   public ListContainerOptions limit(int limit) {
+      checkState(limit >= 0, "limit must be >= 0");
+      checkState(limit <= 10000, "limit must be <= 10000");
+      queryParameters.put("limit", Integer.toString(limit));
+      return this;
+   }
+
+   /** 
+    * object names greater in value than the specified marker are returned.
+    */
+   public ListContainerOptions marker(String marker) {
+      queryParameters.put("marker", checkNotNull(marker, "marker"));
+      return this;
+   }
+
+   /** 
+    * object names less in value than the specified marker are returned.
+    */
+   public ListContainerOptions endMarker(String endMarker) {
+      queryParameters.put("end_marker", checkNotNull(endMarker, "endMarker"));
+      return this;
+   }
+
+   /** 
+    * object names beginning with this substring are returned.
+    */
+   public ListContainerOptions prefix(String prefix) {
+      queryParameters.put("prefix", checkNotNull(prefix, "prefix"));
+      return this;
+   }
+
+   /** 
+    * object names nested in the container are returned.
+    */
+   public ListContainerOptions delimiter(char delimiter) {
+      queryParameters.put("delimiter", Character.toString(delimiter));
+      return this;
+   }
+
+   /** 
+    * object names nested in the pseudo path are returned.
+    */
+   public ListContainerOptions path(String path) {
+      queryParameters.put("path", checkNotNull(path, "path"));
+      return this;
+   }
+
+   public static class Builder {
+
+      /** 
+       * @see ListContainerOptions#limit
+       */
+      public static ListContainerOptions limit(int limit) {
+         ListContainerOptions options = new ListContainerOptions();
+         return options.limit(limit);
+      }
+
+      /** 
+       * @see ListContainerOptions#marker
+       */
+      public static ListContainerOptions marker(String marker) {
+         ListContainerOptions options = new ListContainerOptions();
+         return options.marker(marker);
+      }
+
+      /** 
+       * @see ListContainerOptions#endMarker
+       */
+      public static ListContainerOptions endMarker(String endMarker) {
+         ListContainerOptions options = new ListContainerOptions();
+         return options.endMarker(endMarker);
+      }
+
+      /** 
+       * @see ListContainerOptions#prefix 
+       */
+      public static ListContainerOptions prefix(String prefix) {
+         ListContainerOptions options = new ListContainerOptions();
+         return options.prefix(prefix);
+      }
+
+      /** 
+       * @see ListContainerOptions#delimiter 
+       */
+      public static ListContainerOptions delimiter(char delimiter) {
+         ListContainerOptions options = new ListContainerOptions();
+         return options.delimiter(delimiter);
+      }
+
+      /** 
+       * @see ListContainerOptions#path 
+       */
+      public static ListContainerOptions path(String path) {
+         ListContainerOptions options = new ListContainerOptions();
+         return options.path(path);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/PutOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/PutOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/PutOptions.java
new file mode 100644
index 0000000..d4a1ed3
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/PutOptions.java
@@ -0,0 +1,71 @@
+/*
+ * 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.options;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX;
+
+import java.util.Map;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders;
+
+import com.google.common.collect.Multimap;
+
+/**
+ * Options for creating an Object. 
+ */
+public class PutOptions extends BaseHttpRequestOptions {
+
+   public static final PutOptions NONE = new PutOptions();
+
+   /**
+    * Sets the metadata on a container at creation.
+    */
+   public PutOptions metadata(Map<String, String> metadata) {
+      this.headers.putAll(bindMetadataToHeaders.toHeaders(metadata));
+      return this;
+   }
+
+   /**
+    * Sets the headers on a container at creation.
+    */
+   public PutOptions headers(Multimap<String, String> headers) {
+      this.headers.putAll(headers);
+      return this;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see PutOptions#headers
+       */
+      public static PutOptions headers(Multimap<String, String> headers) {
+         PutOptions options = new PutOptions();
+         return options.headers(headers);
+      }
+
+      /**
+       * @see PutOptions#metadata
+       */
+      public static PutOptions metadata(Map<String, String> metadata) {
+         PutOptions options = new PutOptions();
+         return options.metadata(metadata);
+      }
+   }
+
+   private static final BindMetadataToHeaders bindMetadataToHeaders = new BindMetadataToHeaders(OBJECT_METADATA_PREFIX);
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptions.java
new file mode 100644
index 0000000..f74b7fe
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/UpdateContainerOptions.java
@@ -0,0 +1,107 @@
+/*
+ * 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.options;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.VERSIONS_LOCATION;
+
+import java.util.Map;
+
+import org.jclouds.http.options.BaseHttpRequestOptions;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders;
+
+import com.google.common.collect.Multimap;
+
+/**
+ * Options for updating a {@link Container}.
+ *
+ * @see org.jclouds.openstack.swift.v1.features.ContainerApi#update(String, UpdateContainerOptions)
+ */
+public class UpdateContainerOptions extends BaseHttpRequestOptions {
+   public static final UpdateContainerOptions NONE = new UpdateContainerOptions();
+
+   /**
+    * Sets the headers on a container at creation.
+    */
+   public UpdateContainerOptions headers(Multimap<String, String> headers) {
+      this.headers.putAll(headers);
+      return this;
+   }
+
+   /**
+    * Sets the metadata on a container at creation.
+    */
+   public UpdateContainerOptions metadata(Map<String, String> metadata) {
+      this.headers.putAll(bindMetadataToHeaders.toHeaders(metadata));
+      return this;
+   }
+
+   /**
+    * Sets the public ACL on the container so that anybody can read it.
+    */
+   public UpdateContainerOptions anybodyRead() {
+      this.headers.put(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ);
+      return this;
+   }
+
+   /**
+    * Sets the container that will contain object versions.
+    */
+   public UpdateContainerOptions versionsLocation(String containerName) {
+      this.headers.put(VERSIONS_LOCATION, containerName);
+      return this;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see UpdateContainerOptions#anybodyRead
+       */
+      public static UpdateContainerOptions anybodyRead() {
+         UpdateContainerOptions options = new UpdateContainerOptions();
+         return options.anybodyRead();
+      }
+
+      /**
+       * @see UpdateContainerOptions#headers
+       */
+      public static UpdateContainerOptions headers(Multimap<String, String> headers) {
+         UpdateContainerOptions options = new UpdateContainerOptions();
+         return options.headers(headers);
+      }
+
+      /**
+       * @see UpdateContainerOptions#metadata
+       */
+      public static UpdateContainerOptions metadata(Map<String, String> metadata) {
+         UpdateContainerOptions options = new UpdateContainerOptions();
+         return options.metadata(metadata);
+      }
+
+      /**
+       * @see UpdateContainerOptions#versionsLocation
+       */
+      public static UpdateContainerOptions versionsLocation(String containerName) {
+         UpdateContainerOptions options = new UpdateContainerOptions();
+         return options.versionsLocation(containerName);
+      }
+   }
+
+   private static final BindMetadataToHeaders bindMetadataToHeaders = new BindMetadataToHeaders(CONTAINER_METADATA_PREFIX);
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/reference/SwiftHeaders.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/reference/SwiftHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/reference/SwiftHeaders.java
new file mode 100644
index 0000000..caab5e7
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/reference/SwiftHeaders.java
@@ -0,0 +1,92 @@
+/*
+ * 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.reference;
+
+/**
+ * Common headers in Swift.
+ */
+public final class SwiftHeaders {
+
+   // Common Metadata Prefixes
+   public static final String ACCOUNT_METADATA_PREFIX = "X-Account-Meta-";
+   public static final String CONTAINER_METADATA_PREFIX = "X-Container-Meta-";
+   public static final String OBJECT_METADATA_PREFIX = "X-Object-Meta-";
+   public static final String USER_METADATA_PREFIX = OBJECT_METADATA_PREFIX;
+   
+   // Metadata Removal Prefixes
+   public static final String ACCOUNT_REMOVE_METADATA_PREFIX = "X-Remove-Account-Meta-";
+   public static final String CONTAINER_REMOVE_METADATA_PREFIX = "X-Remove-Container-Meta-";
+   public static final String OBJECT_REMOVE_METADATA_PREFIX = "X-Remove-Object-Meta-";
+   
+   // TempURL
+   public static final String ACCOUNT_TEMPORARY_URL_KEY = ACCOUNT_METADATA_PREFIX + "Temp-Url-Key";
+   public static final String ACCOUNT_TEMPORARY_URL_KEY_2 = ACCOUNT_TEMPORARY_URL_KEY + "-2";
+
+   // Account Headers
+   public static final String ACCOUNT_BYTES_USED = "X-Account-Bytes-Used";
+   public static final String ACCOUNT_CONTAINER_COUNT = "X-Account-Container-Count";
+   public static final String ACCOUNT_OBJECT_COUNT = "X-Account-Object-Count";
+
+   // Container Headers
+   public static final String CONTAINER_BYTES_USED = "X-Container-Bytes-Used";
+   public static final String CONTAINER_OBJECT_COUNT = "X-Container-Object-Count";
+
+   // Public access - not supported in all Swift Impls
+   public static final String CONTAINER_READ = "X-Container-Read";
+   public static final String CONTAINER_WRITE = "X-Container-Write";
+   public static final String CONTAINER_ACL_ANYBODY_READ = ".r:*,.rlistings";
+   
+   // CORS
+   public static final String CONTAINER_ACCESS_CONTROL_ALLOW_ORIGIN = CONTAINER_METADATA_PREFIX + "Access-Control-Allow-Origin";
+   public static final String CONTAINER_ACCESS_CONTROL_MAX_AGE = CONTAINER_METADATA_PREFIX + "Access-Control-Max-Age";
+   public static final String CONTAINER_ACCESS_CONTROL_EXPOSE_HEADERS = CONTAINER_METADATA_PREFIX + "Access-Control-Expose-Headers";
+
+   // Container Quota
+   public static final String CONTAINER_QUOTA_BYTES = CONTAINER_METADATA_PREFIX + "Quota-Bytes";
+   public static final String CONTAINER_QUOTA_COUNT = CONTAINER_METADATA_PREFIX + "Quota-Count";
+
+   // Container Sync
+   public static final String CONTAINER_SYNC_KEY = "X-Container-Sync-Key";
+   public static final String CONTAINER_SYNC_TO = "X-Container-Sync-To";
+
+   // Versioning
+   public static final String VERSIONS_LOCATION = "X-Versions-Location";
+
+   // Misc functionality
+   public static final String CONTAINER_WEB_MODE = "X-Web-Mode";
+
+   public static final String OBJECT_COPY_FROM = "X-Copy-From";
+   public static final String OBJECT_DELETE_AFTER = "X-Delete-After";
+   public static final String OBJECT_DELETE_AT = "X-Delete-At";
+   public static final String OBJECT_MANIFEST = "X-Object-Manifest";
+   /** Get the newest version of the object for GET and HEAD requests */
+   public static final String OBJECT_NEWEST = "X-Newest";
+
+   // Static Large Object
+   public static final String STATIC_LARGE_OBJECT = "X-Static-Large-Object";
+
+   // Static Web
+   public static final String STATIC_WEB_INDEX = CONTAINER_METADATA_PREFIX + "Web-Index";
+   public static final String STATIC_WEB_DIRECTORY_TYPE = CONTAINER_METADATA_PREFIX + "Web-Directory-Type";
+   public static final String STATIC_WEB_ERROR = CONTAINER_METADATA_PREFIX + "Web-Error";
+   public static final String STATIC_WEB_LISTINGS = CONTAINER_METADATA_PREFIX + "Web-Listings";
+   public static final String STATIC_WEB_LISTINGS_CSS = CONTAINER_METADATA_PREFIX + "Web-Listings-CSS";
+
+   private SwiftHeaders() {
+      throw new AssertionError("intentionally unimplemented");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/apis/openstack-swift/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
new file mode 100644
index 0000000..c5b8017
--- /dev/null
+++ b/apis/openstack-swift/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+org.jclouds.openstack.swift.v1.SwiftApiMetadata

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/AuthenticationMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/AuthenticationMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/AuthenticationMockTest.java
new file mode 100644
index 0000000..8b5e895
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/AuthenticationMockTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.jclouds.openstack.swift.v1.features.AccountApiMockTest.accountResponse;
+import static org.testng.Assert.assertEquals;
+
+import java.util.Properties;
+
+import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+/**
+ * @see KeystoneProperties#CREDENTIAL_TYPE
+ */
+@Test
+public class AuthenticationMockTest extends BaseOpenStackMockTest<SwiftApi> {
+
+   @DataProvider(name = "jclouds.keystone.credential-type")
+   Object[][] credentialTypeToPostBody() {
+      Object[][] credentialTypeToPostBody = new Object[2][2];
+      credentialTypeToPostBody[0][0] = "apiAccessKeyCredentials";
+      credentialTypeToPostBody[0][1] = "{\"auth\":{\"apiAccessKeyCredentials\":{\"accessKey\":\"joe\",\"secretKey\":\"letmein\"},\"tenantName\":\"jclouds\"}}";
+      credentialTypeToPostBody[1][0] = "passwordCredentials";
+      credentialTypeToPostBody[1][1] = "{\"auth\":{\"passwordCredentials\":{\"username\":\"joe\",\"password\":\"letmein\"},\"tenantName\":\"jclouds\"}}";
+      return credentialTypeToPostBody;
+   }
+
+   @Test(dataProvider = "jclouds.keystone.credential-type")
+   public void authenticateCredentialType(String credentialType, String expectedPost) throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(accountResponse()));
+
+      try {
+         Properties overrides = new Properties();
+         overrides.setProperty("jclouds.keystone.credential-type", credentialType);
+
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift", overrides);
+
+         api.getAccountApi("DFW").get();
+
+         assertEquals(server.getRequestCount(), 2);
+         RecordedRequest authRequest = server.takeRequest();
+         assertEquals(authRequest.getRequestLine(), "POST /tokens HTTP/1.1");
+         assertEquals(new String(authRequest.getBody(), UTF_8), expectedPost);
+      } finally {
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftApiMetadataTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftApiMetadataTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftApiMetadataTest.java
new file mode 100644
index 0000000..6fbe4d9
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftApiMetadataTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import org.jclouds.View;
+import org.jclouds.apis.internal.BaseApiMetadataTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.TypeToken;
+
+@Test(groups = "unit", testName = "SwiftApiMetadataTest")
+// public class SwiftApiMetadataTest extends BaseBlobStoreApiMetadataTest {
+public class SwiftApiMetadataTest extends BaseApiMetadataTest {
+   public SwiftApiMetadataTest() {
+      super(new SwiftApiMetadata(), ImmutableSet.<TypeToken<? extends View>> of());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftErrorHandlerTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftErrorHandlerTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftErrorHandlerTest.java
new file mode 100644
index 0000000..703da9b
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/SwiftErrorHandlerTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reportMatcher;
+import static org.easymock.EasyMock.verify;
+import static org.jclouds.io.Payloads.newByteSourcePayload;
+
+import java.net.URI;
+
+import org.easymock.IArgumentMatcher;
+import org.jclouds.blobstore.ContainerNotFoundException;
+import org.jclouds.blobstore.KeyNotFoundException;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.openstack.swift.v1.handlers.SwiftErrorHandler;
+import org.testng.annotations.Test;
+
+import com.google.common.io.ByteSource;
+
+/**
+ * Tests the {@link SwiftErrorHandler}
+ */
+@Test(groups = "unit", testName = "SwiftErrorHandlerTest")
+public class SwiftErrorHandlerTest {
+
+   @Test
+   public void test404SetsKeyNotFoundExceptionMosso() {
+      assertCodeMakes("HEAD", URI
+               .create("http://host/v1/MossoCloudFS_7064cdb1d49d4dcba3c899ac33e8409d/adriancole-blobstore1/key"), 404,
+               "Not Found", "", KeyNotFoundException.class);
+   }
+
+   @Test
+   public void test404SetsKeyNotFoundExceptionSwift() {
+      assertCodeMakes("HEAD", URI
+               .create("http://67.202.39.175:8080/v1/AUTH_7064cdb1d49d4dcba3c899ac33e8409d/adriancole-blobstore1/key"),
+               404, "Not Found", "", KeyNotFoundException.class);
+   }
+
+   @Test
+   public void test404SetsContainerNotFoundExceptionMosso() {
+      assertCodeMakes("HEAD", URI
+               .create("http://host/v1/MossoCloudFS_7064cdb1d49d4dcba3c899ac33e8409d/adriancole-blobstore1"), 404,
+               "Not Found", "", ContainerNotFoundException.class);
+   }
+
+   @Test
+   public void test404SetsContainerNotFoundExceptionSwift() {
+      assertCodeMakes("HEAD", URI
+               .create("http://67.202.39.175:8080/v1/AUTH_7064cdb1d49d4dcba3c899ac33e8409d/adriancole-blobstore1"),
+               404, "Not Found", "", ContainerNotFoundException.class);
+   }
+
+   private void assertCodeMakes(String method, URI uri, int statusCode, String message, String content,
+            Class<? extends Exception> expected) {
+      assertCodeMakes(method, uri, statusCode, message, "text/plain", content, expected);
+   }
+
+   private void assertCodeMakes(String method, URI uri, int statusCode, String message, String contentType,
+            String content, Class<? extends Exception> expected) {
+
+      SwiftErrorHandler function = new SwiftErrorHandler();
+
+      HttpCommand command = createMock(HttpCommand.class);
+      HttpRequest request = HttpRequest.builder().method(method).endpoint(uri).build();
+      HttpResponse response = HttpResponse.builder().statusCode(statusCode).message(message)
+                                 .payload(newByteSourcePayload(ByteSource.wrap(content.getBytes()))).build();
+      response.getPayload().getContentMetadata().setContentType(contentType);
+
+      expect(command.getCurrentRequest()).andReturn(request).atLeastOnce();
+      command.setException(classEq(expected));
+
+      replay(command);
+
+      function.handleError(command, response);
+
+      verify(command);
+   }
+
+   public static Exception classEq(final Class<? extends Exception> in) {
+      reportMatcher(new IArgumentMatcher() {
+
+         @Override
+         public void appendTo(StringBuffer buffer) {
+            buffer.append("classEq(");
+            buffer.append(in);
+            buffer.append(")");
+         }
+
+         @Override
+         public boolean matches(Object arg) {
+            return arg.getClass() == in;
+         }
+
+      });
+      return null;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java
new file mode 100644
index 0000000..dd65210
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerLiveTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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;
+
+import static java.lang.String.format;
+import static org.jclouds.io.Payloads.newByteSourcePayload;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.UUID;
+
+import org.jclouds.openstack.swift.v1.domain.SwiftObject;
+import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest;
+import org.jclouds.util.Strings2;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.io.ByteSource;
+
+@Test(groups = "live", testName = "TemporaryUrlSignerLiveTest")
+public class TemporaryUrlSignerLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
+
+   private String name = getClass().getSimpleName();
+   private String containerName = getClass().getSimpleName() + "Container";
+
+   public void signForPublicAccess() throws Exception {
+      for (String regionId : api.getConfiguredRegions()) {
+         SwiftObject object = api.getObjectApi(regionId, containerName).get(name);
+
+         long expires = System.currentTimeMillis() / 1000 + 5;
+         String signature = TemporaryUrlSigner.checkApiEvery(api.getAccountApi(regionId), 5)
+               .sign("GET", object.getUri().getPath(), expires);
+
+         URI signed = URI.create(format("%s?temp_url_sig=%s&temp_url_expires=%s", object.getUri(), signature, expires));
+
+         InputStream publicStream = signed.toURL().openStream();
+         assertEquals(Strings2.toStringAndClose(publicStream), "swifty");
+
+         // let it expire
+         Thread.sleep(5000);
+         try {
+            signed.toURL().openStream();
+            fail("should have expired!");
+         } catch (IOException e) {
+         }
+      }
+   }
+
+   @Override
+   @BeforeClass(groups = "live")
+   public void setup() {
+      super.setup();
+      String key = UUID.randomUUID().toString();
+      for (String regionId : api.getConfiguredRegions()) {
+         api.getAccountApi(regionId).updateTemporaryUrlKey(key);
+         api.getContainerApi(regionId).create(containerName);
+         api.getObjectApi(regionId, containerName)
+               .put(name, newByteSourcePayload(ByteSource.wrap("swifty".getBytes())));
+      }
+   }
+
+   @AfterMethod
+   @AfterClass(groups = "live")
+   public void tearDown() {
+      for (String regionId : api.getConfiguredRegions()) {
+         api.getObjectApi(regionId, containerName).delete(name);
+         api.getContainerApi(regionId).deleteIfEmpty(containerName);
+      }
+      super.tearDown();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java
new file mode 100644
index 0000000..8f42cf3
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/TemporaryUrlSignerMockTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import static org.jclouds.openstack.swift.v1.features.AccountApiMockTest.accountResponse;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_TEMPORARY_URL_KEY;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
+import org.testng.annotations.Test;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+
+@Test(groups = "unit", testName = "TemporaryUrlSignerMockTest")
+public class TemporaryUrlSignerMockTest extends BaseOpenStackMockTest<SwiftApi> {
+
+   @Test(expectedExceptions = NullPointerException.class, expectedExceptionsMessageRegExp = "accountApi")
+   public void whenAccountApiIsNull() {
+      TemporaryUrlSigner.checkApiEvery(null, 10000);
+   }
+
+   public void whenAccountApiHasKey() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(accountResponse().addHeader(ACCOUNT_TEMPORARY_URL_KEY, "mykey")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         String signature = TemporaryUrlSigner.checkApiEvery(api.getAccountApi("DFW"), 10000)
+               .sign("GET", "/v1/AUTH_account/container/object", 1323479485l);
+
+         assertEquals(signature, "d9fc2067e52b06598421664cf6610bfc8fc431f6");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         assertEquals(server.takeRequest().getRequestLine(),
+               "HEAD /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = ".*returned a null temporaryUrlKey!")
+   public void whenAccountApiDoesntHaveKey() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(accountResponse()));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         TemporaryUrlSigner.checkApiEvery(api.getAccountApi("DFW"), 10000)
+            .sign("GET", "/v1/AUTH_account/container/object", 1323479485l);
+      } finally {
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         assertEquals(server.takeRequest().getRequestLine(),
+               "HEAD /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9 HTTP/1.1");
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java
new file mode 100644
index 0000000..97f5448
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.blobstore;
+
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+
+import org.jclouds.blobstore.BlobRequestSigner;
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.domain.PageSet;
+import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
+import org.jclouds.domain.Location;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.io.ByteStreams2;
+import org.jclouds.io.MutableContentMetadata;
+import org.jclouds.io.Payload;
+import org.jclouds.io.Payloads;
+import org.jclouds.rest.HttpClient;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Iterables;
+import com.google.common.io.ByteSource;
+import com.google.common.hash.Hashing;
+import com.google.common.net.MediaType;
+import com.google.common.net.HttpHeaders;
+
+@Test(groups = "live")
+public class RegionScopedBlobStoreContextLiveTest extends BaseBlobStoreIntegrationTest {
+
+   public RegionScopedBlobStoreContextLiveTest() {
+      provider = "openstack-swift";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
+      return props;
+   }
+
+   @Test
+   public void testRegionsAreNotEmpty() {
+      assertFalse(RegionScopedBlobStoreContext.class.cast(view).getConfiguredRegions().isEmpty());
+   }
+
+   @Test
+   public void testLocationsMatch() {
+      RegionScopedBlobStoreContext ctx = RegionScopedBlobStoreContext.class.cast(view);
+      for (String regionId : ctx.getConfiguredRegions()) {
+         Set<? extends Location> locations = ctx.getBlobStore(regionId).listAssignableLocations();
+         assertEquals(locations.size(), 1, "expected one region " + regionId + " " + locations);
+         Location location = locations.iterator().next();
+         assertEquals(location.getId(), regionId, "region id " + regionId + " didn't match getId(): " + location);
+      }
+   }
+
+   @Test
+   public void testListBlobs() throws InterruptedException, ExecutionException {
+      RegionScopedBlobStoreContext ctx = RegionScopedBlobStoreContext.class.cast(view);
+      for (String regionId : ctx.getConfiguredRegions()) {
+         ctx.getBlobStore(regionId).list();
+      }
+   }
+
+   @Test
+   public void testSign() throws InterruptedException, ExecutionException,
+         IOException {
+      RegionScopedBlobStoreContext ctx = RegionScopedBlobStoreContext.class.cast(view);
+      for (String regionId : ctx.getConfiguredRegions()) {
+         BlobStore region = ctx.getBlobStore(regionId);
+         PageSet<? extends StorageMetadata> containers = region.list();
+         if (containers.isEmpty()) {
+            continue;
+         }
+         String containerName = Iterables.getLast(containers).getName();
+
+         final ByteSource input = ByteSource.wrap("str".getBytes());
+         final HttpClient client = ctx.utils().http();
+
+         // test signed put
+         String blobName = "test-" + UUID.randomUUID();
+         Blob blob2 = region.blobBuilder(blobName).forSigning()
+               .contentLength(input.size())
+               .contentMD5(input.hash(Hashing.md5()).asBytes())
+               .contentType(MediaType.OCTET_STREAM.toString()).build();
+         BlobRequestSigner signer = ctx.getSigner(regionId);
+         HttpResponse response;
+         try {
+            HttpRequest putRequest;
+            putRequest = signer.signPutBlob(containerName, blob2, 600);
+            MutableContentMetadata metadata = blob2.getMetadata()
+                  .getContentMetadata();
+            HttpRequest.Builder<?> putRequestBuilder = putRequest.toBuilder()
+                  .addHeader(HttpHeaders.CONTENT_TYPE,
+                        metadata.getContentType());
+            putRequestBuilder.addHeader(HttpHeaders.CONTENT_LENGTH,
+                  String.valueOf(input.size()));
+            putRequestBuilder.payload(input);
+            putRequest = putRequestBuilder.build();
+            Payload payload = Payloads.newPayload(input.read());
+            putRequest.setPayload(payload);
+            assertNotNull(putRequest, "regionId=" + regionId + ", container="
+                  + containerName + ", blob=" + blobName);
+            response = client.invoke(putRequest);
+            if (response.getStatusCode() != 200
+                  && response.getStatusCode() != 201) {
+               fail("Signed PUT expected to return 200 or 201 but returned "
+                     + response.getStatusCode());
+            }
+         } catch (Exception e) {
+            fail("Failed signed put test: " + e);
+         }
+
+         // test signed get
+         try {
+            HttpRequest getRequest = signer.signGetBlob(containerName,
+                  blobName);
+            assertNotNull(getRequest, "regionId=" + regionId + ", container="
+                  + containerName + ", blob=" + blobName);
+            response = client.invoke(getRequest);
+            if (response.getStatusCode() != 200) {
+               fail("Signed GET expected to return 200 but returned "
+                     + response.getStatusCode());
+            }
+            Payload payload = response.getPayload();
+            assertEquals(ByteStreams2.toByteArrayAndClose(payload.openStream()), input.read(),
+                  "Data with signed GET not identical to what was put");
+         } catch (Exception e) {
+            fail("Failed signed GET test: " + e);
+         }
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
new file mode 100644
index 0000000..6d26100
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobIntegrationLiveTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.blobstore.integration;
+
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+
+import java.util.Properties;
+
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.integration.internal.BaseBlobIntegrationTest;
+import org.testng.SkipException;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "SwiftBlobIntegrationLiveTest")
+public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
+   
+   public SwiftBlobIntegrationLiveTest() {
+      provider = "openstack-swift";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
+      return props;
+   }
+
+   // Object/Container name contains forbidden chars from "<>
+   @Override
+   @DataProvider(name = "delete")
+   public Object[][] createData() {
+      return new Object[][] { { "normal" }, { "sp ace" }, { "qu?stion" }, { "unic₪de" }, { "path/foo" }, { "colon:" },
+            { "asteri*k" }, { "p|pe" } };
+   }
+
+   @Override
+   public void testGetTwoRanges() {
+      throw new SkipException("unsupported in swift");
+   }
+
+   @Override
+   public void testCreateBlobWithExpiry() throws InterruptedException {
+      throw new SkipException("unsupported in swift");
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void testGetIfUnmodifiedSince() throws InterruptedException {
+      throw new SkipException("unsupported in swift");
+   }
+
+   @Override
+   protected int getIncorrectContentMD5StatusCode() {
+      return 422;
+   }
+
+   @Override
+   protected void checkContentLanguage(Blob blob, String contentLanguage) {
+      assert blob.getPayload().getContentMetadata().getContentLanguage() == null;
+      assert blob.getMetadata().getContentMetadata().getContentLanguage() == null;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobLiveTest.java
new file mode 100644
index 0000000..d9996bf
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobLiveTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.blobstore.integration;
+
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+
+import java.util.Properties;
+
+import org.jclouds.blobstore.integration.internal.BaseBlobLiveTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "SwiftBlobLiveTest")
+public class SwiftBlobLiveTest extends BaseBlobLiveTest {
+
+   public SwiftBlobLiveTest() {
+      provider = "openstack-swift";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
+      return props;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobSignerLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobSignerLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobSignerLiveTest.java
new file mode 100644
index 0000000..9dd603f
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftBlobSignerLiveTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.blobstore.integration;
+
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+
+import java.util.Properties;
+
+import org.jclouds.blobstore.integration.internal.BaseBlobSignerLiveTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "SwiftBlobSignerLiveTest")
+public class SwiftBlobSignerLiveTest extends BaseBlobSignerLiveTest {
+
+   public SwiftBlobSignerLiveTest() {
+      provider = "openstack-swift";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
+      return props;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerIntegrationLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerIntegrationLiveTest.java
new file mode 100644
index 0000000..d954867
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerIntegrationLiveTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.blobstore.integration;
+
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Properties;
+
+import org.jclouds.blobstore.integration.internal.BaseContainerIntegrationTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "SwiftContainerIntegrationLiveTest")
+public class SwiftContainerIntegrationLiveTest extends BaseContainerIntegrationTest {
+
+   public SwiftContainerIntegrationLiveTest() {
+      provider = "openstack-swift";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
+      return props;
+   }
+
+   @Override
+   public void testListRootUsesDelimiter() throws InterruptedException {
+      try {
+         super.testListRootUsesDelimiter();
+      } catch (AssertionError e) {
+         // swift doesn't have the "common prefixes" in the response that s3
+         // does. If we wanted this to pass, we'd need to create
+         // pseudo-directories implicitly, which is costly and troublesome. It
+         // is better to fail this assertion.
+         assertTrue(e.getMessage().matches(".*16.* but .*15.*"), e.getMessage());
+         // ^^ squishy regex to deal with various formats of testng messages.
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerLiveTest.java
new file mode 100644
index 0000000..9bd85d6
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftContainerLiveTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.blobstore.integration;
+
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+
+import java.util.Properties;
+
+import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "SwiftContainerLiveTest")
+public class SwiftContainerLiveTest extends BaseContainerLiveTest {
+
+   public SwiftContainerLiveTest() {
+      provider = "openstack-swift";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
+      return props;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftServiceIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftServiceIntegrationLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftServiceIntegrationLiveTest.java
new file mode 100644
index 0000000..1da1a68
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/integration/SwiftServiceIntegrationLiveTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.blobstore.integration;
+
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+
+import java.util.Properties;
+
+import org.jclouds.blobstore.integration.internal.BaseServiceIntegrationTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "SwiftServiceIntegrationLiveTest")
+public class SwiftServiceIntegrationLiveTest extends BaseServiceIntegrationTest {
+
+   public SwiftServiceIntegrationLiveTest() {
+      provider = "openstack-swift";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
+      return props;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdaptersTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdaptersTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdaptersTest.java
new file mode 100644
index 0000000..e4225fc
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdaptersTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.config;
+
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.openstack.swift.v1.config.SwiftTypeAdapters.BulkDeleteResponseAdapter;
+import org.jclouds.openstack.swift.v1.config.SwiftTypeAdapters.ExtractArchiveResponseAdapter;
+import org.jclouds.openstack.swift.v1.domain.BulkDeleteResponse;
+import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+@Test
+public class SwiftTypeAdaptersTest {
+   Gson gson = new GsonBuilder()
+         .registerTypeAdapter(ExtractArchiveResponse.class, new ExtractArchiveResponseAdapter())
+         .registerTypeAdapter(BulkDeleteResponse.class, new BulkDeleteResponseAdapter())
+         .create();
+
+   public void extractArchiveWithoutErrors() {
+      assertEquals(gson.fromJson(""
+            + "{\n"
+            + "  \"Response Status\": \"201 Created\",\n"
+            + "  \"Response Body\": \"\",\n"
+            + "  \"Errors\": [],\n"
+            + "  \"Number Files Created\": 10\n"
+            + "}", ExtractArchiveResponse.class), ExtractArchiveResponse.create(10, ImmutableMap.<String, String> of()));
+   }
+
+   public void extractArchiveWithErrorsAndDecodesPaths() {
+      assertEquals(
+            gson.fromJson(""
+                  + "{\n"
+                  + "  \"Response Status\": \"201 Created\",\n"
+                  + "  \"Response Body\": \"\",\n"
+                  + "  \"Errors\": [\n"
+                  + "    [\"/v1/12345678912345/mycontainer/home/xx%3Cyy\", \"400 Bad Request\"],\n"
+                  + "    [\"/v1/12345678912345/mycontainer/../image.gif\", \"400 Bad Request\"]\n"
+                  + "  ],\n"
+                  + "  \"Number Files Created\": 8\n"
+                  + "}", ExtractArchiveResponse.class),
+            ExtractArchiveResponse.create(
+                  8,
+                  ImmutableMap.<String, String> builder()
+                        .put("/v1/12345678912345/mycontainer/home/xx<yy", "400 Bad Request")
+                        .put("/v1/12345678912345/mycontainer/../image.gif", "400 Bad Request").build()));
+   }
+
+   public void bulkDeleteWithoutErrors() {
+      assertEquals(gson.fromJson(""
+            + "{\n"
+            + "  \"Response Status\": \"200 OK\",\n"
+            + "  \"Response Body\": \"\",\n"
+            + "  \"Errors\": [],\n"
+            + "  \"Number Not Found\": 1,\n"
+            + "  \"Number Deleted\": 9\n"
+            + "}", BulkDeleteResponse.class), BulkDeleteResponse.create(9, 1, ImmutableMap.<String, String> of()));
+   }
+
+   public void bulkDeleteWithErrorsAndDecodesPaths() {
+      assertEquals(gson.fromJson(""
+            + "{\n"
+            + "  \"Response Status\": \"400 Bad Request\",\n"
+            + "  \"Response Body\": \"\",\n"
+            + "  \"Errors\": [\n"
+            + "    [\"/v1/12345678912345/Not%20Empty\", \"409 Conflict\"]"
+            + "  ],\n"
+            + "  \"Number Deleted\": 0\n"
+            + "}", BulkDeleteResponse.class),
+            BulkDeleteResponse.create(0, 0, ImmutableMap.of("/v1/12345678912345/Not Empty", "409 Conflict")));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiLiveTest.java
new file mode 100644
index 0000000..3841b33
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiLiveTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.features;
+
+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 java.util.Map;
+import java.util.Map.Entry;
+
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.domain.Account;
+import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+@Test(groups = "live", testName = "AccountApiLiveTest")
+public class AccountApiLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
+
+   public void testGet() throws Exception {
+      for (String regionId : regions) {
+         AccountApi accountApi = api.getAccountApi(regionId);
+         Account account = accountApi.get();
+
+         assertNotNull(account);
+         assertTrue(account.getContainerCount() >= 0);
+         assertTrue(account.getObjectCount() >= 0);
+         assertTrue(account.getBytesUsed() >= 0);
+      }
+   }
+
+   public void testUpdateMetadata() throws Exception {
+      for (String regionId : regions) {
+         AccountApi accountApi = api.getAccountApi(regionId);
+
+         Map<String, String> meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar");
+
+         assertTrue(accountApi.updateMetadata(meta));
+
+         accountHasMetadata(accountApi, meta);
+      }
+   }
+
+   public void testDeleteMetadata() throws Exception {
+      for (String regionId : regions) {
+         AccountApi accountApi = api.getAccountApi(regionId);
+
+         Map<String, String> meta = ImmutableMap.of("MyDelete1", "foo", "MyDelete2", "bar");
+
+         assertTrue(accountApi.updateMetadata(meta));
+         accountHasMetadata(accountApi, meta);
+
+         assertTrue(accountApi.deleteMetadata(meta));
+         Account account = accountApi.get();
+         for (Entry<String, String> entry : meta.entrySet()) {
+            // note keys are returned in lower-case!
+            assertFalse(account.getMetadata().containsKey(entry.getKey().toLowerCase()));
+         }
+      }
+   }
+
+   static void accountHasMetadata(AccountApi accountApi, Map<String, String> meta) {
+      Account account = accountApi.get();
+      for (Entry<String, String> entry : meta.entrySet()) {
+         // note keys are returned in lower-case!
+         assertEquals(account.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue(),
+               account + " didn't have metadata: " + entry);
+      }
+   }
+}


[2/5] Import openstack-swift from labs.

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java
new file mode 100644
index 0000000..84d08fe
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/AccountApiMockTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.features;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_BYTES_USED;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_CONTAINER_COUNT;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_OBJECT_COUNT;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_REMOVE_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_TEMPORARY_URL_KEY;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.domain.Account;
+import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+@Test(groups = "unit", testName = "AccountApiMockTest")
+public class AccountApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
+
+   /** upper-cases first char, and lower-cases rest!! **/
+   public void getKnowingServerMessesWithMetadataKeyCaseFormat() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(accountResponse()
+            // note silly casing
+            .addHeader(ACCOUNT_METADATA_PREFIX + "Apiname", "swift")
+            .addHeader(ACCOUNT_METADATA_PREFIX + "Apiversion", "v1.1")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         Account account = api.getAccountApi("DFW").get();
+         assertEquals(account.getContainerCount(), 3l);
+         assertEquals(account.getObjectCount(), 42l);
+         assertEquals(account.getBytesUsed(), 323479l);
+         for (Entry<String, String> entry : metadata.entrySet()) {
+            assertEquals(account.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue());
+         }
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "HEAD", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void updateMetadata() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(accountResponse()
+            .addHeader(ACCOUNT_METADATA_PREFIX + "ApiName", "swift")
+            .addHeader(ACCOUNT_METADATA_PREFIX + "ApiVersion", "v1.1")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getAccountApi("DFW").updateMetadata(metadata));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+
+         RecordedRequest replaceRequest = server.takeRequest();
+         assertRequest(replaceRequest, "POST", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9");
+         for (Entry<String, String> entry : metadata.entrySet()) {
+            assertEquals(replaceRequest.getHeader(ACCOUNT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue());
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void updateTemporaryUrlKey() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(accountResponse()));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getAccountApi("DFW").updateTemporaryUrlKey("foobar"));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+
+         RecordedRequest replaceRequest = server.takeRequest();
+         assertRequest(replaceRequest, "POST", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9");
+         assertEquals(replaceRequest.getHeader(ACCOUNT_TEMPORARY_URL_KEY), "foobar");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteMetadata() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(accountResponse()));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getAccountApi("DFW").deleteMetadata(metadata));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9 HTTP/1.1");
+         for (String key : metadata.keySet()) {
+            assertEquals(deleteRequest.getHeader(ACCOUNT_REMOVE_METADATA_PREFIX + key.toLowerCase()), "ignored");
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   private static final Map<String, String> metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1");
+
+   public static MockResponse accountResponse() {
+      return new MockResponse()
+            .addHeader(ACCOUNT_CONTAINER_COUNT, "3")
+            .addHeader(ACCOUNT_OBJECT_COUNT, "42")
+            .addHeader(ACCOUNT_BYTES_USED, "323479");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiLiveTest.java
new file mode 100644
index 0000000..6901ac9
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiLiveTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+import org.jboss.shrinkwrap.api.GenericArchive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.exporter.TarGzExporter;
+import org.jclouds.io.ByteStreams2;
+import org.jclouds.io.Payload;
+import org.jclouds.io.payloads.ByteSourcePayload;
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.domain.BulkDeleteResponse;
+import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse;
+import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteSource;
+
+@Test(groups = "live", testName = "BulkApiLiveTest")
+public class BulkApiLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
+
+   private static final int OBJECT_COUNT = 10;
+   private String containerName = getClass().getSimpleName();
+   List<String> paths = Lists.newArrayList();
+   byte[] tarGz;
+
+   public void testNotPresentWhenDeleting() throws Exception {
+      for (String regionId : regions) {
+         BulkDeleteResponse deleteResponse = api.getBulkApi(regionId).bulkDelete(
+               ImmutableList.of(UUID.randomUUID().toString()));
+         assertEquals(deleteResponse.getDeleted(), 0);
+         assertEquals(deleteResponse.getNotFound(), 1);
+         assertTrue(deleteResponse.getErrors().isEmpty());
+      }
+   }
+
+   public void testExtractArchive() throws Exception {
+      for (String regionId : regions) {
+         Payload payload = new ByteSourcePayload(ByteSource.wrap(tarGz));
+
+         ExtractArchiveResponse extractResponse = api.getBulkApi(regionId)
+                                                     .extractArchive(containerName, payload, "tar.gz");
+         assertEquals(extractResponse.getCreated(), OBJECT_COUNT);
+         assertTrue(extractResponse.getErrors().isEmpty());
+         assertEquals(api.getContainerApi(regionId).get(containerName).getObjectCount(), OBJECT_COUNT);
+
+         // repeat the command
+         extractResponse = api.getBulkApi(regionId).extractArchive(containerName, payload, "tar.gz");
+         assertEquals(extractResponse.getCreated(), OBJECT_COUNT);
+         assertTrue(extractResponse.getErrors().isEmpty());
+      }
+   }
+
+   @Test(dependsOnMethods = "testExtractArchive")
+   public void testBulkDelete() throws Exception {
+      for (String regionId : regions) {
+         BulkDeleteResponse deleteResponse = api.getBulkApi(regionId).bulkDelete(paths);
+         assertEquals(deleteResponse.getDeleted(), OBJECT_COUNT);
+         assertEquals(deleteResponse.getNotFound(), 0);
+         assertTrue(deleteResponse.getErrors().isEmpty());
+         assertEquals(api.getContainerApi(regionId).get(containerName).getObjectCount(), 0);
+      }
+   }
+
+   @Override
+   @BeforeClass(groups = "live")
+   public void setup() {
+      super.setup();
+      for (String regionId : regions) {
+         boolean created = api.getContainerApi(regionId).create(containerName);
+         if (!created) {
+            deleteAllObjectsInContainer(regionId, containerName);
+         }
+      }
+      GenericArchive files = ShrinkWrap.create(GenericArchive.class, "files.tar.gz");
+      StringAsset content = new StringAsset("foo");
+      for (int i = 0; i < OBJECT_COUNT; i++) {
+         paths.add(containerName + "/file" + i);
+         files.add(content, "/file" + i);
+      }
+
+      try {
+         tarGz = ByteStreams2.toByteArrayAndClose(files.as(TarGzExporter.class).exportAsInputStream());
+      } catch (IOException e) {
+         throw Throwables.propagate(e);
+      }
+   }
+
+   @Override
+   @AfterClass(groups = "live")
+   public void tearDown() {
+      for (String regionId : regions) {
+         deleteAllObjectsInContainer(regionId, containerName);
+         api.getContainerApi(regionId).deleteIfEmpty(containerName);
+      }
+      super.tearDown();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiMockTest.java
new file mode 100644
index 0000000..3136b52
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/BulkApiMockTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.jboss.shrinkwrap.api.GenericArchive;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.exporter.TarGzExporter;
+import org.jclouds.io.ByteStreams2;
+import org.jclouds.io.Payload;
+import org.jclouds.io.Payloads;
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse;
+import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.io.ByteSource;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+@Test(groups = "unit", testName = "BulkApiMockTest")
+public class BulkApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
+
+   public void testExtractArchive() throws Exception {
+      GenericArchive files = ShrinkWrap.create(GenericArchive.class, "files.tar.gz");
+      StringAsset content = new StringAsset("foo");
+      for (int i = 0; i < 10; i++) {
+         files.add(content, "/file" + i);
+      }
+
+      byte[] tarGz = ByteStreams2.toByteArrayAndClose(files.as(TarGzExporter.class).exportAsInputStream());
+
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201).setBody("{\"Number Files Created\": 10, \"Errors\": []}")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         Payload payload = Payloads.newByteSourcePayload(ByteSource.wrap(tarGz));
+         ExtractArchiveResponse response = api.getBulkApi("DFW").extractArchive("myContainer", payload, "tar.gz");
+
+         assertEquals(response.getCreated(), 10);
+         assertTrue(response.getErrors().isEmpty());
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         RecordedRequest extractRequest = server.takeRequest();
+         assertRequest(extractRequest, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer?extract-archive=tar.gz");
+         assertEquals(extractRequest.getBody(), tarGz);
+      } finally {
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java
new file mode 100644
index 0000000..bf2ba98
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.features;
+
+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 java.util.Map;
+import java.util.Map.Entry;
+
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.domain.Container;
+import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest;
+import org.jclouds.openstack.swift.v1.options.CreateContainerOptions;
+import org.jclouds.openstack.swift.v1.options.ListContainerOptions;
+import org.jclouds.openstack.swift.v1.options.UpdateContainerOptions;
+import org.jclouds.openstack.swift.v1.reference.SwiftHeaders;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+
+/**
+ * Provides live tests for the {@link ContainerApi}.
+ */
+@Test(groups = "live", testName = "ContainerApiLiveTest")
+public class ContainerApiLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
+
+   private String name = getClass().getSimpleName();
+
+   public void testCreateWithOptions() throws Exception {
+      for (String regionId : regions) {
+         ImmutableMultimap<String, String> headers =
+               ImmutableMultimap.of(SwiftHeaders.STATIC_WEB_INDEX, "__index.html",
+                                    SwiftHeaders.STATIC_WEB_ERROR, "__error.html");
+         CreateContainerOptions opts = new CreateContainerOptions().headers(headers);
+
+         assertNotNull(api.getContainerApi(regionId).create(name, opts));
+
+         Container container = api.getContainerApi(regionId).get(name);
+         assertNotNull(container);
+         assertEquals(container.getName(), name);
+         assertEquals(container.getMetadata().size(), 2);
+         assertEquals(container.getMetadata().get("web-index"), "__index.html");
+         assertEquals(container.getMetadata().get("web-error"), "__error.html");
+
+         assertTrue(api.getContainerApi(regionId).deleteIfEmpty(name));
+      }
+   }
+
+   public void testCreateWithSpacesAndSpecialCharacters() throws Exception {
+      final String nameWithSpaces = "container # ! special";
+
+      for (String regionId : regions) {
+         assertTrue(api.getContainerApi(regionId).create(nameWithSpaces));
+         Container container = api.getContainerApi(regionId).get(nameWithSpaces);
+         assertNotNull(container);
+         assertEquals(container.getName(), nameWithSpaces);
+
+         assertTrue(api.getContainerApi(regionId).deleteIfEmpty(nameWithSpaces));
+      }
+   }
+
+   public void testList() throws Exception {
+      for (String regionId : regions) {
+         ContainerApi containerApi = api.getContainerApi(regionId);
+         FluentIterable<Container> response = containerApi.list();
+         assertNotNull(response);
+         for (Container container : response) {
+            assertNotNull(container.getName());
+            assertTrue(container.getObjectCount() >= 0);
+            assertTrue(container.getBytesUsed() >= 0);
+         }
+      }
+   }
+
+   public void testListWithOptions() throws Exception {
+      String lexicographicallyBeforeName = name.substring(0, name.length() - 1);
+      for (String regionId : regions) {
+         ListContainerOptions options = ListContainerOptions.Builder.marker(lexicographicallyBeforeName);
+         Container container = api.getContainerApi(regionId).list(options).get(0);
+         assertEquals(container.getName(), name);
+         assertTrue(container.getObjectCount() == 0);
+         assertTrue(container.getBytesUsed() == 0);
+      }
+   }
+
+   public void testUpdate() throws Exception {
+      for (String regionId : regions) {
+         ImmutableMultimap<String, String> headers =
+               ImmutableMultimap.of(SwiftHeaders.STATIC_WEB_INDEX, "__index.html",
+                                    SwiftHeaders.STATIC_WEB_ERROR, "__error.html");
+         UpdateContainerOptions opts = new UpdateContainerOptions().headers(headers);
+
+         assertNotNull(api.getContainerApi(regionId).create(name));
+
+         Container container = api.getContainerApi(regionId).get(name);
+         assertNotNull(container);
+         assertEquals(container.getName(), name);
+         assertTrue(container.getMetadata().isEmpty());
+
+         assertNotNull(api.getContainerApi(regionId).update(name, opts));
+
+         Container updatedContainer = api.getContainerApi(regionId).get(name);
+         assertNotNull(updatedContainer);
+         assertEquals(updatedContainer.getName(), name);
+         assertEquals(updatedContainer.getMetadata().size(), 2);
+         assertEquals(updatedContainer.getMetadata().get("web-index"), "__index.html");
+         assertEquals(updatedContainer.getMetadata().get("web-error"), "__error.html");
+
+         assertTrue(api.getContainerApi(regionId).deleteIfEmpty(name));
+      }
+   }
+
+   public void testGet() throws Exception {
+      for (String regionId : regions) {
+         Container container = api.getContainerApi(regionId).get(name);
+         assertEquals(container.getName(), name);
+         assertTrue(container.getObjectCount() == 0);
+         assertTrue(container.getBytesUsed() == 0);
+      }
+   }
+
+   public void testUpdateMetadata() throws Exception {
+      Map<String, String> meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar");
+
+      for (String regionId : regions) {
+         ContainerApi containerApi = api.getContainerApi(regionId);
+         assertTrue(containerApi.updateMetadata(name, meta));
+         containerHasMetadata(containerApi, name, meta);
+      }
+   }
+
+   public void testDeleteMetadata() throws Exception {
+      Map<String, String> meta = ImmutableMap.of("MyDelete1", "foo", "MyDelete2", "bar");
+
+      for (String regionId : regions) {
+         ContainerApi containerApi = api.getContainerApi(regionId);
+         // update
+         assertTrue(containerApi.updateMetadata(name, meta));
+         containerHasMetadata(containerApi, name, meta);
+         // delete
+         assertTrue(containerApi.deleteMetadata(name, meta));
+         Container container = containerApi.get(name);
+         for (Entry<String, String> entry : meta.entrySet()) {
+            // note keys are returned in lower-case!
+            assertFalse(container.getMetadata().containsKey(entry.getKey().toLowerCase()));
+         }
+      }
+   }
+
+   static void containerHasMetadata(ContainerApi containerApi, String name, Map<String, String> meta) {
+      Container container = containerApi.get(name);
+      for (Entry<String, String> entry : meta.entrySet()) {
+         // note keys are returned in lower-case!
+         assertEquals(container.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue(),
+               container + " didn't have metadata: " + entry);
+      }
+   }
+
+   @Override
+   @BeforeClass(groups = "live")
+   public void setup() {
+      super.setup();
+      for (String regionId : regions) {
+         api.getContainerApi(regionId).create(name);
+      }
+   }
+
+   @Override
+   @AfterClass(groups = "live")
+   public void tearDown() {
+      for (String regionId : regions) {
+         api.getContainerApi(regionId).deleteIfEmpty(name);
+      }
+      super.tearDown();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java
new file mode 100644
index 0000000..6b3ef66
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java
@@ -0,0 +1,363 @@
+/*
+ * 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.features;
+
+import static org.jclouds.openstack.swift.v1.options.CreateContainerOptions.Builder.anybodyRead;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_BYTES_USED;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_OBJECT_COUNT;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_REMOVE_METADATA_PREFIX;
+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 java.util.Map;
+import java.util.Map.Entry;
+
+import org.jclouds.blobstore.ContainerNotFoundException;
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.domain.Container;
+import org.jclouds.openstack.swift.v1.options.ListContainerOptions;
+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.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+@Test(groups = "unit", testName = "ContainerApiMockTest")
+public class ContainerApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
+
+   public void testList() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/container_list.json"))));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         ImmutableList<Container> containers = api.getContainerApi("DFW").list().toList();
+         assertEquals(containers, ImmutableList.of(
+               Container.builder()
+                     .name("test_container_1")
+                     .objectCount(2)
+                     .bytesUsed(78).build(),
+               Container.builder()
+                     .name("test_container_2")
+                     .objectCount(1)
+                     .bytesUsed(17).build()));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testListWithOptions() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/container_list.json"))));
+
+      ListContainerOptions options = ListContainerOptions.Builder.marker("test");
+      assertNotNull(options);
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         ImmutableList<Container> containers = api.getContainerApi("DFW").list(options).toList();
+         assertEquals(containers, ImmutableList.of(
+               Container.builder()
+                     .name("test_container_1")
+                     .objectCount(2)
+                     .bytesUsed(78).build(),
+               Container.builder()
+                     .name("test_container_2")
+                     .objectCount(1)
+                     .bytesUsed(17).build()));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9?marker=test");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testContainerExists() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201)));
+      server.enqueue(addCommonHeaders(containerResponse()
+            .addHeader(CONTAINER_METADATA_PREFIX + "ApiName", "swift")
+            .addHeader(CONTAINER_METADATA_PREFIX + "ApiVersion", "v1.1")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getContainerApi("DFW").create("myContainer", anybodyRead().metadata(metadata)));
+
+         Container container = api.getContainerApi("DFW").get("myContainer");
+         assertEquals(container.getName(), "myContainer");
+         assertEquals(container.getObjectCount(), 42l);
+         assertEquals(container.getBytesUsed(), 323479l);
+         for (Entry<String, String> entry : container.getMetadata().entrySet()) {
+            assertEquals(container.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue());
+         }
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer");
+         assertRequest(server.takeRequest(), "HEAD", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   @Test(expectedExceptions = ContainerNotFoundException.class)
+   public void testContainerDoesNotExist() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getContainerApi("DFW").create("myContainer", anybodyRead().metadata(metadata)));
+
+         // the head call will throw the ContainerNotFoundException
+         api.getContainerApi("DFW").get("myContainer");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testCreate() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getContainerApi("DFW").create("myContainer"));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testCreateWithOptions() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getContainerApi("DFW").create("myContainer", anybodyRead().metadata(metadata)));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+
+         RecordedRequest createRequest = server.takeRequest();
+         assertRequest(createRequest, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer");
+
+         assertEquals(createRequest.getHeader(CONTAINER_READ), CONTAINER_ACL_ANYBODY_READ);
+
+         for (Entry<String, String> entry : metadata.entrySet()) {
+            assertEquals(createRequest.getHeader(CONTAINER_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue());
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testCreateWithSpacesAndSpecialCharacters() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getContainerApi("DFW").create("container # ! special"));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/container%20%23%20%21%20special");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testAlreadyCreated() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(202)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertFalse(api.getContainerApi("DFW").create("myContainer"));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   /** upper-cases first char, and lower-cases rest!! **/
+   public void testGetKnowingServerMessesWithMetadataKeyCaseFormat() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(containerResponse()
+            // note silly casing
+            .addHeader(CONTAINER_METADATA_PREFIX + "Apiname", "swift")
+            .addHeader(CONTAINER_METADATA_PREFIX + "Apiversion", "v1.1")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         Container container = api.getContainerApi("DFW").get("myContainer");
+         assertEquals(container.getName(), "myContainer");
+         assertEquals(container.getObjectCount(), 42l);
+         assertEquals(container.getBytesUsed(), 323479l);
+         for (Entry<String, String> entry : container.getMetadata().entrySet()) {
+            assertEquals(container.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue());
+         }
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "HEAD", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testUpdateMetadata() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(containerResponse()
+            .addHeader(CONTAINER_METADATA_PREFIX + "ApiName", "swift")
+            .addHeader(CONTAINER_METADATA_PREFIX + "ApiVersion", "v1.1")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getContainerApi("DFW").updateMetadata("myContainer", metadata));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest replaceRequest = server.takeRequest();
+         assertEquals(replaceRequest.getRequestLine(),
+               "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1");
+         for (Entry<String, String> entry : metadata.entrySet()) {
+            assertEquals(replaceRequest.getHeader(CONTAINER_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"))));
+      server.enqueue(addCommonHeaders(containerResponse()));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getContainerApi("DFW").deleteMetadata("myContainer", metadata));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1");
+         for (String key : metadata.keySet()) {
+            assertEquals(deleteRequest.getHeader(CONTAINER_REMOVE_METADATA_PREFIX + key.toLowerCase()), "ignored");
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testDeleteIfEmpty() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getContainerApi("DFW").deleteIfEmpty("myContainer"));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "DELETE /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testAlreadyDeleted() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getContainerApi("DFW").deleteIfEmpty("myContainer"));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "DELETE /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testDeleteWhenNotEmpty() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(409)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertFalse(api.getContainerApi("DFW").deleteIfEmpty("myContainer"));
+
+      } finally {
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "DELETE /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1");
+         server.shutdown();
+      }
+   }
+
+   private static final Map<String, String> metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1");
+
+   static MockResponse containerResponse() {
+      return new MockResponse()
+            .addHeader(CONTAINER_OBJECT_COUNT, "42")
+            .addHeader(CONTAINER_BYTES_USED, "323479");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/CreatePublicContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/CreatePublicContainerLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/CreatePublicContainerLiveTest.java
new file mode 100644
index 0000000..09e34b5
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/CreatePublicContainerLiveTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.features;
+
+import static org.jclouds.openstack.swift.v1.options.CreateContainerOptions.Builder.anybodyRead;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.internal.BaseSwiftApiLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "CreatePublicContainerLiveTest")
+public class CreatePublicContainerLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
+
+   private String name = getClass().getSimpleName();
+
+   public void testAnybodyReadUpdatesMetadata() throws Exception {
+      for (String regionId : api.getConfiguredRegions()) {
+         api.getContainerApi(regionId).create(name, anybodyRead());
+         assertTrue(api.getContainerApi(regionId).get(name).getAnybodyRead().get());
+      }
+   }
+
+   @Override
+   @AfterClass(groups = "live")
+   public void tearDown() {
+      for (String regionId : api.getConfiguredRegions()) {
+         api.getContainerApi(regionId).deleteIfEmpty(name);
+      }
+      super.tearDown();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
new file mode 100644
index 0000000..5a472ff
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiLiveTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.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.Date;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.http.options.GetOptions;
+import org.jclouds.io.Payload;
+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.internal.BaseSwiftApiLiveTest;
+import org.jclouds.openstack.swift.v1.options.ListContainerOptions;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.ByteSource;
+
+/**
+ * Provides live tests for the {@link ObjectApi}.
+ */
+@Test(groups = "live", testName = "ObjectApiLiveTest", singleThreaded = true)
+public class ObjectApiLiveTest extends BaseSwiftApiLiveTest<SwiftApi> {
+
+   private String name = getClass().getSimpleName();
+   private String containerName = getClass().getSimpleName() + "Container";
+   static final Payload PAYLOAD = newByteSourcePayload(ByteSource.wrap("swifty".getBytes()));
+
+   public void testCreateWithSpacesAndSpecialCharacters() throws Exception {
+      final String containerName = "container # ! special";
+      final String objectName = "object # ! special";
+
+      for (String regionId : regions) {
+         assertNotNull(api.getContainerApi(regionId).create(containerName));
+         assertNotNull(api.getObjectApi(regionId, containerName).put(objectName, PAYLOAD));
+
+         SwiftObject object = api.getObjectApi(regionId, containerName).get(objectName);
+         assertEquals(object.getName(), objectName);
+         checkObject(object);
+         assertEquals(toStringAndClose(object.getPayload().openStream()), "swifty");
+
+         api.getObjectApi(regionId, containerName).delete(objectName);
+         api.getContainerApi(regionId).deleteIfEmpty(containerName);
+      }
+   }
+
+   public void testPutWithExpiration() throws Exception {
+      String objectName = "test-expiration";
+
+      long expireMillis = new Date().getTime() + 1000 * 60 * 60 * 24;
+      Date expireAt = new Date(expireMillis);
+
+      Payload payload = newByteSourcePayload(ByteSource.wrap("swifty".getBytes()));
+      payload.getContentMetadata().setExpires(expireAt);
+
+      for (String regionId : regions) {
+         String etag = api.getObjectApi(regionId, containerName).put(objectName, payload);
+         assertNotNull(etag);
+
+         SwiftObject object = api.getObjectApi(regionId, containerName).get(objectName);
+         assertEquals(object.getName(), objectName);
+         checkObject(object);
+         assertEquals(toStringAndClose(object.getPayload().openStream()), "swifty");
+
+         api.getObjectApi(regionId, containerName).delete(objectName);
+      }
+   }
+   public void testCopyObject() throws Exception {
+      for (String regionId : regions) {
+         // source
+         String sourceContainer = "src" + containerName;
+         String sourceObjectName = "original.txt";
+         String badSource = "badSource";
+
+         // destination
+         String destinationContainer = "dest" + containerName;
+         String destinationObject = "copy.txt";
+         String destinationPath = "/" + destinationContainer + "/" + destinationObject;
+
+         ContainerApi containerApi = api.getContainerApi(regionId);
+
+         // create source and destination dirs
+         containerApi.create(sourceContainer);
+         containerApi.create(destinationContainer);
+
+         // get the api for this region and container
+         ObjectApi srcApi = api.getObjectApi(regionId, sourceContainer);
+         ObjectApi destApi = api.getObjectApi(regionId, destinationContainer);
+
+         // Create source object
+         assertNotNull(srcApi.put(sourceObjectName, PAYLOAD));
+         SwiftObject sourceObject = srcApi.get(sourceObjectName);
+         checkObject(sourceObject);
+
+         // Create the destination object
+         assertNotNull(destApi.put(destinationObject, PAYLOAD));
+         SwiftObject object = destApi.get(destinationObject);
+         checkObject(object);
+
+         // check the copy operation
+         assertTrue(destApi.copy(destinationObject, sourceContainer, sourceObjectName));
+         assertNotNull(destApi.get(destinationObject));
+
+         // now get a real SwiftObject
+         SwiftObject destSwiftObject = destApi.get(destinationObject);
+         assertEquals(toStringAndClose(destSwiftObject.getPayload().openStream()), "swifty");
+
+         // test exception thrown on bad source name
+         try {
+            destApi.copy(destinationObject, badSource, sourceObjectName);
+            fail("Expected CopyObjectException");
+         } catch (CopyObjectException e) {
+            assertEquals(e.getSourcePath(), "/" + badSource + "/" + sourceObjectName);
+            assertEquals(e.getDestinationPath(), destinationPath);
+         }
+
+         deleteAllObjectsInContainer(regionId, sourceContainer);
+         containerApi.deleteIfEmpty(sourceContainer);
+
+         deleteAllObjectsInContainer(regionId, destinationContainer);
+         containerApi.deleteIfEmpty(destinationContainer);
+      }
+   }
+
+   public void testList() throws Exception {
+      for (String regionId : regions) {
+         ObjectApi objectApi = api.getObjectApi(regionId, containerName);
+         ObjectList response = objectApi.list();
+         assertEquals(response.getContainer(), api.getContainerApi(regionId).get(containerName));
+         for (SwiftObject object : response) {
+            checkObject(object);
+         }
+      }
+   }
+
+   public void testListWithOptions() throws Exception {
+      for (String regionId : regions) {
+         ObjectApi objectApi = api.getObjectApi(regionId, containerName);
+         ObjectList response = objectApi.list(ListContainerOptions.NONE);
+         assertEquals(response.getContainer(), api.getContainerApi(regionId).get(containerName));
+         for (SwiftObject object : response) {
+            checkObject(object);
+         }
+      }
+   }
+
+   public void testMetadata() throws Exception {
+      for (String regionId : regions) {
+         SwiftObject object = api.getObjectApi(regionId, containerName).get(name);
+         assertEquals(object.getName(), name);
+         checkObject(object);
+         assertEquals(toStringAndClose(object.getPayload().openStream()), "swifty");
+      }
+   }
+
+   public void testUpdateMetadata() throws Exception {
+      for (String regionId : regions) {
+         ObjectApi objectApi = api.getObjectApi(regionId, containerName);
+
+         Map<String, String> meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", "bar");
+         assertTrue(objectApi.updateMetadata(name, meta));
+
+         SwiftObject object = objectApi.get(name);
+         for (Entry<String, String> entry : meta.entrySet()) {
+            // note keys are returned in lower-case!
+            assertEquals(object.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue(),
+                  object + " didn't have metadata: " + entry);
+         }
+      }
+   }
+
+   public void testGet() throws Exception {
+      for (String regionId : regions) {
+         SwiftObject object = api.getObjectApi(regionId, containerName).get(name, GetOptions.NONE);
+         assertEquals(object.getName(), name);
+         checkObject(object);
+         assertEquals(toStringAndClose(object.getPayload().openStream()), "swifty");
+      }
+   }
+
+   public void testPrivateByDefault() throws Exception {
+      for (String regionId : regions) {
+         SwiftObject object = api.getObjectApi(regionId, containerName).get(name);
+         try {
+            object.getUri().toURL().openStream();
+            fail("shouldn't be able to access " + object);
+         } catch (IOException expected) {
+         }
+      }
+   }
+
+   public void testGetOptions() throws Exception {
+      for (String regionId : regions) {
+         SwiftObject object = api.getObjectApi(regionId, containerName).get(name, tail(1));
+         assertEquals(object.getName(), name);
+         checkObject(object);
+         assertEquals(toStringAndClose(object.getPayload().openStream()), "y");
+      }
+   }
+
+   public void testListOptions() throws Exception {
+      String lexicographicallyBeforeName = name.substring(0, name.length() - 1);
+      for (String regionId : regions) {
+         SwiftObject object = api.getObjectApi(regionId, containerName)
+               .list(marker(lexicographicallyBeforeName)).get(0);
+         assertEquals(object.getName(), name);
+         checkObject(object);
+      }
+   }
+
+   public void testDeleteMetadata() throws Exception {
+      for (String regionId : regions) {
+         ObjectApi objectApi = api.getObjectApi(regionId, containerName);
+
+         Map<String, String> meta = ImmutableMap.of("MyDelete1", "foo", "MyDelete2", "bar");
+
+         assertTrue(objectApi.updateMetadata(name, meta));
+         assertFalse(objectApi.get(name).getMetadata().isEmpty());
+
+         assertTrue(objectApi.deleteMetadata(name, meta));
+         assertTrue(objectApi.get(name).getMetadata().isEmpty());
+      }
+   }
+
+   @Override
+   @BeforeClass(groups = "live")
+   public void setup() {
+      super.setup();
+      for (String regionId : regions) {
+         api.getContainerApi(regionId).create(containerName);
+         api.getObjectApi(regionId, containerName).put(name, PAYLOAD);
+      }
+   }
+
+   @Override
+   @AfterClass(groups = "live")
+   public void tearDown() {
+      for (String regionId : regions) {
+         deleteAllObjectsInContainer(regionId, containerName);
+         api.getObjectApi(regionId, containerName).delete(name);
+         api.getContainerApi(regionId).deleteIfEmpty(containerName);
+      }
+
+      super.tearDown();
+   }
+
+   static void checkObject(SwiftObject object) {
+      assertNotNull(object.getName());
+      assertNotNull(object.getUri());
+      assertNotNull(object.getETag());
+      assertTrue(object.getLastModified().getTime() <= System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5));
+      assertNotNull(object.getPayload().getContentMetadata().getContentLength());
+      assertNotNull(object.getPayload().getContentMetadata().getContentType());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
new file mode 100644
index 0000000..8c0e4d1
--- /dev/null
+++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java
@@ -0,0 +1,515 @@
+/*
+ * 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.features;
+
+import static com.google.common.base.Charsets.US_ASCII;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.net.HttpHeaders.EXPIRES;
+import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
+import static org.jclouds.Constants.PROPERTY_RETRY_DELAY_START;
+import static org.jclouds.Constants.PROPERTY_SO_TIMEOUT;
+import static org.jclouds.http.options.GetOptions.Builder.tail;
+import static org.jclouds.io.Payloads.newByteSourcePayload;
+import static org.jclouds.openstack.swift.v1.features.ContainerApiMockTest.containerResponse;
+import static org.jclouds.openstack.swift.v1.options.ListContainerOptions.Builder.marker;
+import static org.jclouds.openstack.swift.v1.options.PutOptions.Builder.metadata;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_REMOVE_METADATA_PREFIX;
+import static org.jclouds.util.Strings2.toStringAndClose;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Date;
+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}.
+ */
+@Test(groups = "unit", testName = "ObjectApiMockTest")
+public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> {
+   SimpleDateFormatDateService dates = new SimpleDateFormatDateService();
+
+   static final Payload PAYLOAD = newByteSourcePayload(ByteSource.wrap("swifty".getBytes()));
+
+   protected ImmutableList<SwiftObject> parsedObjectsForUrl(String baseUri) {
+      baseUri += "v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer";
+      return ImmutableList.of(
+            SwiftObject.builder()
+                  .name("test_obj_1")
+                  .uri(URI.create(baseUri + "/test_obj_1"))
+                  .etag("4281c348eaf83e70ddce0e07221c3d28")
+                  .payload(payload(14, "application/octet-stream", new Date(1406243553)))
+                  .lastModified(dates.iso8601DateParse("2009-02-03T05:26:32.612278")).build(),
+            SwiftObject.builder()
+                  .name("test_obj_2")
+                  .uri(URI.create(baseUri + "/test_obj_2"))
+                  .etag("b039efe731ad111bc1b0ef221c3849d0")
+                  .payload(payload(64l, "application/octet-stream", null))
+                  .lastModified(dates.iso8601DateParse("2009-02-03T05:26:32.612278")).build(),
+            SwiftObject.builder()
+                  .name("test obj 3")
+                  .uri(URI.create(baseUri + "/test%20obj%203"))
+                  .etag("0b2e80bd0744d9ebb20484149a57c82e")
+                  .payload(payload(14, "application/octet-stream", new Date()))
+                  .lastModified(dates.iso8601DateParse("2014-05-20T05:26:32.612278")).build());
+   }
+
+   public void testList() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(containerResponse()
+            .addHeader(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ)
+            .setBody(stringFromResource("/object_list.json"))));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         ObjectList objects = api.getObjectApi("DFW", "myContainer").list();
+         assertEquals(objects, parsedObjectsForUrl(server.getUrl("/").toString()));
+         assertEquals(objects.getContainer().getName(), "myContainer");
+         assertTrue(objects.getContainer().getAnybodyRead().get());
+
+         // Check MD5 is parsed from the ETag header.
+         SwiftObject object1 = objects.get(1);
+         assertEquals(base16().lowerCase().decode(object1.getETag()),
+               object1.getPayload().getContentMetadata().getContentMD5AsHashCode().asBytes());
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testListWithOptions() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(containerResponse()
+            .addHeader(CONTAINER_READ, CONTAINER_ACL_ANYBODY_READ)
+            .setBody(stringFromResource("/object_list.json"))));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         ObjectList objects = api.getObjectApi("DFW", "myContainer").list(new ListContainerOptions());
+         assertEquals(objects, parsedObjectsForUrl(server.getUrl("/").toString()));
+         assertEquals(objects.getContainer().getName(), "myContainer");
+         assertTrue(objects.getContainer().getAnybodyRead().get());
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testListOptions() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(containerResponse().setBody(stringFromResource("/object_list.json"))));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         ObjectList objects = api.getObjectApi("DFW", "myContainer").list(marker("test"));
+         assertEquals(objects, parsedObjectsForUrl(server.getUrl("/").toString()));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "GET", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer?marker=test");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testCreate() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse()
+            .setResponseCode(201)
+            .addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b"))
+            .addHeader("Expires", "1406243553"));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertEquals(
+               api.getObjectApi("DFW", "myContainer").put("myObject", PAYLOAD,
+                     metadata(metadata)), "d9f5eb4bba4e2f2f046e54611bc8196b");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         RecordedRequest replace = server.takeRequest();
+         assertRequest(replace, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject");
+
+         assertEquals(new String(replace.getBody()), "swifty");
+         for (Entry<String, String> entry : metadata.entrySet()) {
+            assertEquals(replace.getHeader(OBJECT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue());
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testCreateWithSpacesAndSpecialCharacters() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201).addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b")));
+
+      final String containerName = "container # ! special";
+      final String objectName = "object # ! special";
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertEquals(
+               api.getObjectApi("DFW", containerName).put(objectName, PAYLOAD,
+                     metadata(metadata)), "d9f5eb4bba4e2f2f046e54611bc8196b");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         RecordedRequest replace = server.takeRequest();
+         assertRequest(replace, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/container%20%23%20%21%20special/object%20%23%20%21%20special");
+
+         assertEquals(new String(replace.getBody()), "swifty");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testCreateWith408Retry() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(408))); // 1
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(408))); // 2
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(408))); // 3
+
+      // Finally success
+      server.enqueue(addCommonHeaders(new MockResponse()
+            .setResponseCode(201)
+            .addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b")));
+
+      try {
+         Properties overrides = new Properties();
+         overrides.setProperty(PROPERTY_MAX_RETRIES, 5 + "");
+
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift", overrides);
+         assertEquals(
+               api.getObjectApi("DFW", "myContainer").put("myObject", PAYLOAD,
+                     metadata(metadata)), "d9f5eb4bba4e2f2f046e54611bc8196b");
+
+         assertEquals(server.getRequestCount(), 5);
+         assertAuthentication(server);
+         RecordedRequest replace = server.takeRequest();
+         // This should take a while.
+         assertRequest(replace, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject");
+
+         assertEquals(new String(replace.getBody()), "swifty");
+         for (Entry<String, String> entry : metadata.entrySet()) {
+            assertEquals(replace.getHeader(OBJECT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue());
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   /** upper-cases first char, and lower-cases rest!! **/
+   public void testGetWithoutKnowingServerMessesWithMetadataKeyCaseFormat() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(objectResponse()
+            // note silly casing
+            .addHeader(OBJECT_METADATA_PREFIX + "Apiname", "swift")
+            .addHeader(OBJECT_METADATA_PREFIX + "Apiversion", "v1.1")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         SwiftObject object = api.getObjectApi("DFW", "myContainer").getWithoutBody("myObject");
+         assertEquals(object.getName(), "myObject");
+         assertEquals(object.getETag(), "8a964ee2a5e88be344f36c22562a6486");
+
+         // Check MD5 is parsed from the ETag header.
+         assertEquals(base16().lowerCase().decode(object.getETag()),
+               object.getPayload().getContentMetadata().getContentMD5AsHashCode().asBytes());
+
+         assertEquals(object.getLastModified(), dates.rfc822DateParse("Fri, 12 Jun 2010 13:40:18 GMT"));
+         for (Entry<String, String> entry : object.getMetadata().entrySet()) {
+            assertEquals(object.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue());
+         }
+         assertEquals(object.getPayload().getContentMetadata().getContentType(), "text/plain; charset=UTF-8");
+         assertEquals(toStringAndClose(object.getPayload().openStream()), "");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "HEAD", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testGet() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(objectResponse()
+            // note silly casing
+            .addHeader(OBJECT_METADATA_PREFIX + "Apiname", "swift")
+            .addHeader(OBJECT_METADATA_PREFIX + "Apiversion", "v1.1")));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         SwiftObject object = api.getObjectApi("DFW", "myContainer").get("myObject", tail(1));
+         assertEquals(object.getName(), "myObject");
+         assertEquals(object.getETag(), "8a964ee2a5e88be344f36c22562a6486");
+         assertEquals(object.getLastModified(), dates.rfc822DateParse("Fri, 12 Jun 2010 13:40:18 GMT"));
+         for (Entry<String, String> entry : object.getMetadata().entrySet()) {
+            assertEquals(object.getMetadata().get(entry.getKey().toLowerCase()), entry.getValue());
+         }
+
+         Payload payload = object.getPayload();
+         assertEquals(payload.getContentMetadata().getContentLength(), Long.valueOf(4));
+         assertEquals(payload.getContentMetadata().getContentType(), "text/plain; charset=UTF-8");
+         assertEquals(payload.getContentMetadata().getExpires(), dates.rfc822DateParse("Wed, 23 Jul 2014 14:00:00 GMT"));
+
+         assertEquals(toStringAndClose(payload.openStream()), "ABCD");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest get = server.takeRequest();
+         assertEquals(get.getRequestLine(),
+               "GET /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   @Test(expectedExceptions = HttpResponseException.class, timeOut = 20000)
+   public void testCreateWithTimeout() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      // Typically we would enqueue a response for the put. However, in this case, test the timeout by not providing one.
+
+      try {
+         Properties overrides = new Properties();
+
+         overrides.setProperty(PROPERTY_SO_TIMEOUT, 5000 + ""); // This time-outs the connection
+         overrides.setProperty(PROPERTY_MAX_RETRIES, 0 + ""); // 0 retries == 1 try. Semantics.
+         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.getObjectApi("DFW", "myContainer").put("myObject", new ByteSourcePayload(ByteSource.wrap("swifty".getBytes())), metadata(metadata));
+
+         fail("testReplaceTimeout test should have failed with an HttpResponseException.");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testUpdateMetadata() 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");
+         assertTrue(api.getObjectApi("DFW", "myContainer").updateMetadata("myObject", metadata));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest replaceRequest = server.takeRequest();
+         assertEquals(replaceRequest.getRequestLine(),
+               "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject HTTP/1.1");
+         for (Entry<String, String> entry : metadata.entrySet()) {
+            assertEquals(replaceRequest.getHeader(OBJECT_METADATA_PREFIX + entry.getKey().toLowerCase()), entry.getValue());
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testUpdateMetadataContentType() 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");
+         assertTrue(api.getObjectApi("DFW", "myContainer").updateMetadata("myObject", metadata));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest replaceRequest = server.takeRequest();
+         assertEquals(replaceRequest.getHeaders("Content-Type").get(0), "", "updateMetadata should send an empty 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<String, String> 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"))));
+      server.enqueue(addCommonHeaders(objectResponse()));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getObjectApi("DFW", "myContainer").deleteMetadata("myObject", metadata));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "POST /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject HTTP/1.1");
+         for (String key : metadata.keySet()) {
+            assertEquals(deleteRequest.getHeader(OBJECT_REMOVE_METADATA_PREFIX + key.toLowerCase()), "ignored");
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testDelete() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         api.getObjectApi("DFW", "myContainer").delete("myObject");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "DELETE /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testAlreadyDeleted() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         api.getObjectApi("DFW", "myContainer").delete("myObject");
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "DELETE /v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testCopyObject() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201)
+            .addHeader(SwiftHeaders.OBJECT_COPY_FROM, "/bar/foo.txt")));
+      try {
+         SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift");
+         assertTrue(api.getObjectApi("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");
+      } finally {
+         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.getObjectApi("DFW", "foo").copy("bar.txt", "bogus", "foo.txt");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   private static final Map<String, String> metadata = ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1");
+
+   static MockResponse objectResponse() {
+      return new MockResponse()
+            .addHeader("Last-Modified", "Fri, 12 Jun 2010 13:40:18 GMT")
+            .addHeader("ETag", "8a964ee2a5e88be344f36c22562a6486")
+            // TODO: MWS doesn't allow you to return content length w/o content
+            // on HEAD!
+            .setBody("ABCD".getBytes(US_ASCII))
+            .addHeader("Content-Length", "4")
+            .addHeader("Content-Type", "text/plain; charset=UTF-8")
+            .addHeader(EXPIRES, "Wed, 23 Jul 2014 14:00:00 GMT");
+   }
+
+   static Payload payload(long bytes, String contentType, Date expires) {
+      Payload payload = newByteSourcePayload(ByteSource.empty());
+      payload.getContentMetadata().setContentLength(bytes);
+      payload.getContentMetadata().setContentType(contentType);
+      payload.getContentMetadata().setExpires(expires);
+      return payload;
+   }
+}


[5/5] git commit: Import openstack-swift from labs.

Posted by an...@apache.org.
Import openstack-swift from labs.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/34663f3c
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/34663f3c
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/34663f3c

Branch: refs/heads/master
Commit: 34663f3c286c83ace8fbf3b02f703d26f8cd499a
Parents: 8432c66
Author: Adrian Cole <ad...@gmail.com>
Authored: Wed Oct 8 08:18:47 2014 -0700
Committer: Andrew Phillips <an...@apache.org>
Committed: Wed Oct 8 18:07:35 2014 -0400

----------------------------------------------------------------------
 allblobstore/pom.xml                            |   5 +
 apis/openstack-swift/README.md                  |  10 +
 apis/openstack-swift/pom.xml                    | 141 +++++
 .../openstack/swift/v1/CopyObjectException.java |  48 ++
 .../jclouds/openstack/swift/v1/SwiftApi.java    |  70 +++
 .../openstack/swift/v1/SwiftApiMetadata.java    |  99 ++++
 .../openstack/swift/v1/SwiftFallbacks.java      |  43 ++
 .../openstack/swift/v1/TemporaryUrlSigner.java  |  89 ++++
 .../swift/v1/binders/BindMetadataToHeaders.java | 141 +++++
 .../openstack/swift/v1/binders/SetPayload.java  |  72 +++
 .../blobstore/RegionScopedBlobStoreContext.java | 154 ++++++
 .../blobstore/RegionScopedSwiftBlobStore.java   | 312 +++++++++++
 .../RegionScopedTemporaryUrlBlobSigner.java     | 109 ++++
 .../config/SignUsingTemporaryUrls.java          |  67 +++
 .../config/SwiftBlobStoreContextModule.java     |  65 +++
 .../v1/blobstore/functions/ToBlobMetadata.java  |  71 +++
 .../functions/ToListContainerOptions.java       |  52 ++
 .../blobstore/functions/ToResourceMetadata.java |  44 ++
 .../swift/v1/config/BaseSwiftHttpApiModule.java |  46 ++
 .../swift/v1/config/SwiftHttpApiModule.java     |  32 ++
 .../swift/v1/config/SwiftTypeAdapters.java      | 119 +++++
 .../openstack/swift/v1/domain/Account.java      | 219 ++++++++
 .../swift/v1/domain/BulkDeleteResponse.java     | 101 ++++
 .../openstack/swift/v1/domain/Container.java    | 237 +++++++++
 .../swift/v1/domain/ExtractArchiveResponse.java |  90 ++++
 .../openstack/swift/v1/domain/ObjectList.java   |  57 ++
 .../openstack/swift/v1/domain/Segment.java      | 136 +++++
 .../openstack/swift/v1/domain/SwiftObject.java  | 267 ++++++++++
 .../openstack/swift/v1/features/AccountApi.java | 111 ++++
 .../openstack/swift/v1/features/BulkApi.java    | 105 ++++
 .../swift/v1/features/ContainerApi.java         | 215 ++++++++
 .../openstack/swift/v1/features/ObjectApi.java  | 265 ++++++++++
 .../swift/v1/features/StaticLargeObjectApi.java |  90 ++++
 .../swift/v1/functions/ETagHeader.java          |  32 ++
 .../v1/functions/EntriesWithoutMetaPrefix.java  |  48 ++
 .../swift/v1/functions/FalseOnAccepted.java     |  30 ++
 .../swift/v1/functions/MetadataFromHeaders.java |  31 ++
 .../v1/functions/ParseAccountFromHeaders.java   |  38 ++
 .../v1/functions/ParseContainerFromHeaders.java |  54 ++
 .../v1/functions/ParseObjectFromResponse.java   |  87 ++++
 .../functions/ParseObjectListFromResponse.java  | 113 ++++
 .../swift/v1/handlers/SwiftErrorHandler.java    |  91 ++++
 .../v1/options/CreateContainerOptions.java      | 108 ++++
 .../swift/v1/options/ListContainerOptions.java  | 132 +++++
 .../openstack/swift/v1/options/PutOptions.java  |  71 +++
 .../v1/options/UpdateContainerOptions.java      | 107 ++++
 .../swift/v1/reference/SwiftHeaders.java        |  92 ++++
 .../services/org.jclouds.apis.ApiMetadata       |  18 +
 .../swift/v1/AuthenticationMockTest.java        |  71 +++
 .../swift/v1/SwiftApiMetadataTest.java          |  32 ++
 .../swift/v1/SwiftErrorHandlerTest.java         | 117 +++++
 .../swift/v1/TemporaryUrlSignerLiveTest.java    |  90 ++++
 .../swift/v1/TemporaryUrlSignerMockTest.java    |  76 +++
 .../RegionScopedBlobStoreContextLiveTest.java   | 159 ++++++
 .../SwiftBlobIntegrationLiveTest.java           |  76 +++
 .../integration/SwiftBlobLiveTest.java          |  39 ++
 .../integration/SwiftBlobSignerLiveTest.java    |  39 ++
 .../SwiftContainerIntegrationLiveTest.java      |  54 ++
 .../integration/SwiftContainerLiveTest.java     |  39 ++
 .../SwiftServiceIntegrationLiveTest.java        |  39 ++
 .../swift/v1/config/SwiftTypeAdaptersTest.java  |  90 ++++
 .../swift/v1/features/AccountApiLiveTest.java   |  87 ++++
 .../swift/v1/features/AccountApiMockTest.java   | 145 ++++++
 .../swift/v1/features/BulkApiLiveTest.java      | 125 +++++
 .../swift/v1/features/BulkApiMockTest.java      |  72 +++
 .../swift/v1/features/ContainerApiLiveTest.java | 197 +++++++
 .../swift/v1/features/ContainerApiMockTest.java | 363 +++++++++++++
 .../features/CreatePublicContainerLiveTest.java |  47 ++
 .../swift/v1/features/ObjectApiLiveTest.java    | 286 ++++++++++
 .../swift/v1/features/ObjectApiMockTest.java    | 515 +++++++++++++++++++
 .../features/StaticLargeObjectApiLiveTest.java  | 129 +++++
 .../features/StaticLargeObjectApiMockTest.java  | 107 ++++
 .../features/UrlEncodeAndJoinOnNewlineTest.java |  44 ++
 .../swift/v1/internal/BaseSwiftApiLiveTest.java |  88 ++++
 .../v1/options/CreateContainerOptionsTest.java  |  69 +++
 .../swift/v1/options/PutOptionsTest.java        |  48 ++
 .../v1/options/UpdateContainerOptionsTest.java  | 122 +++++
 .../src/test/resources/access.json              | 249 +++++++++
 .../src/test/resources/container_list.json      |  12 +
 .../src/test/resources/logback.xml              |  71 +++
 .../src/test/resources/object_list.json         |  23 +
 apis/pom.xml                                    |   1 +
 82 files changed, 8455 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/allblobstore/pom.xml
----------------------------------------------------------------------
diff --git a/allblobstore/pom.xml b/allblobstore/pom.xml
index a1c5c3a..47a45c3 100644
--- a/allblobstore/pom.xml
+++ b/allblobstore/pom.xml
@@ -55,6 +55,11 @@
     </dependency>
     <dependency>
       <groupId>org.apache.jclouds.api</groupId>
+      <artifactId>openstack-swift</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds.api</groupId>
       <artifactId>cloudfiles</artifactId>
       <version>${project.version}</version>
     </dependency>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/README.md
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/README.md b/apis/openstack-swift/README.md
new file mode 100644
index 0000000..2d319da
--- /dev/null
+++ b/apis/openstack-swift/README.md
@@ -0,0 +1,10 @@
+OpenStack Swift
+===============
+
+Swift is a distributed object storage system designed to scale from a single machine to thousands of servers.
+
+Production ready?
+Beta
+
+This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based on that feedback, minor changes to the interfaces may happen. This code will replace org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later.
+

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/pom.xml
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/pom.xml b/apis/openstack-swift/pom.xml
new file mode 100644
index 0000000..23333c1
--- /dev/null
+++ b/apis/openstack-swift/pom.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.jclouds</groupId>
+    <artifactId>jclouds-project</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+    <relativePath>../../project/pom.xml</relativePath>
+  </parent>
+
+  <groupId>org.apache.jclouds.api</groupId>
+  <artifactId>openstack-swift</artifactId>
+  <name>jclouds openstack-swift api</name>
+  <description>jclouds components to access an implementation of OpenStack Swift</description>
+  <packaging>bundle</packaging>
+
+  <properties>
+    <!-- keystone endpoint -->
+    <test.openstack-swift.endpoint>http://localhost:5000/v2.0/</test.openstack-swift.endpoint>
+    <test.openstack-swift.api-version>1.0</test.openstack-swift.api-version>
+    <test.openstack-swift.build-version />
+    <test.openstack-swift.identity>FIXME_IDENTITY</test.openstack-swift.identity>
+    <test.openstack-swift.credential>FIXME_CREDENTIALS</test.openstack-swift.credential>
+    <test.jclouds.keystone.credential-type>passwordCredentials</test.jclouds.keystone.credential-type>
+    <jclouds.blobstore.httpstream.url>http://archive.apache.org/dist/commons/logging/binaries/commons-logging-1.1.1-bin.tar.gz</jclouds.blobstore.httpstream.url>
+    <jclouds.blobstore.httpstream.md5>e5de09672af9b386c30a311654d8541a</jclouds.blobstore.httpstream.md5>
+    <jclouds.osgi.export>org.jclouds.openstack.swift.v1*;version="${project.version}"</jclouds.osgi.export>
+    <jclouds.osgi.import>org.jclouds*;version="${project.version}",*</jclouds.osgi.import>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.jclouds.api</groupId>
+      <artifactId>openstack-keystone</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-blobstore</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-core</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds.api</groupId>
+      <artifactId>openstack-keystone</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-blobstore</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.shrinkwrap</groupId>
+      <artifactId>shrinkwrap-depchain</artifactId>
+      <version>1.2.0</version>
+      <type>pom</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds.driver</groupId>
+      <artifactId>jclouds-slf4j</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <profiles>
+    <profile>
+      <id>live</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>integration</id>
+                <phase>integration-test</phase>
+                <goals>
+                  <goal>test</goal>
+                </goals>
+                <configuration>
+                  <systemPropertyVariables>
+                    <test.openstack-swift.endpoint>${test.openstack-swift.endpoint}</test.openstack-swift.endpoint>
+                    <test.openstack-swift.api-version>${test.openstack-swift.api-version}</test.openstack-swift.api-version>
+                    <test.openstack-swift.build-version>${test.openstack-swift.build-version}</test.openstack-swift.build-version>
+                    <test.openstack-swift.identity>${test.openstack-swift.identity}</test.openstack-swift.identity>
+                    <test.openstack-swift.credential>${test.openstack-swift.credential}</test.openstack-swift.credential>
+                    <test.jclouds.keystone.credential-type>${test.jclouds.keystone.credential-type}</test.jclouds.keystone.credential-type>
+                    <jclouds.blobstore.httpstream.url>${jclouds.blobstore.httpstream.url}</jclouds.blobstore.httpstream.url>
+                    <jclouds.blobstore.httpstream.md5>${jclouds.blobstore.httpstream.md5}</jclouds.blobstore.httpstream.md5>
+                  </systemPropertyVariables>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
+</project>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/CopyObjectException.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/CopyObjectException.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/CopyObjectException.java
new file mode 100644
index 0000000..010efb1
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/CopyObjectException.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import org.jclouds.rest.ResourceNotFoundException;
+
+/**
+ * Thrown when an object cannot be copied.
+ *
+ *
+ * @see {@link SwiftErrorHandler#handleError(HttpCommand, HttpResponse)}
+ */
+@SuppressWarnings("serial")
+public class CopyObjectException extends ResourceNotFoundException {
+
+   private String sourcePath;
+   private String destinationPath;
+
+   public CopyObjectException(String sourcePath, String destinationPath, String message) {
+      super(String.format("Either the source path '%s' or the destination path '%s' was not found. " +
+         "(message: %s)", sourcePath, destinationPath, message));
+      this.sourcePath = sourcePath;
+      this.destinationPath = destinationPath;
+   }
+
+   public String getSourcePath() {
+      return sourcePath;
+   }
+
+   public String getDestinationPath() {
+      return destinationPath;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApi.java
new file mode 100644
index 0000000..1d4d1d0
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApi.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import java.io.Closeable;
+import java.util.Set;
+
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+import org.jclouds.location.Region;
+import org.jclouds.location.functions.RegionToEndpoint;
+import org.jclouds.openstack.swift.v1.features.AccountApi;
+import org.jclouds.openstack.swift.v1.features.BulkApi;
+import org.jclouds.openstack.swift.v1.features.ContainerApi;
+import org.jclouds.openstack.swift.v1.features.ObjectApi;
+import org.jclouds.openstack.swift.v1.features.StaticLargeObjectApi;
+import org.jclouds.rest.annotations.Delegate;
+import org.jclouds.rest.annotations.EndpointParam;
+
+import com.google.common.annotations.Beta;
+import com.google.inject.Provides;
+
+/**
+ * Provides access to the OpenStack Object Storage (Swift) API.
+ * <p/>
+ * OpenStack Object Storage is an object-based storage system that stores content and metadata
+ * as objects. You create, modify, and get objects and metadata using this API.
+ * <p/>
+ */
+@Beta
+public interface SwiftApi extends Closeable {
+
+   @Provides
+   @Region
+   Set<String> getConfiguredRegions();
+
+   @Delegate
+   AccountApi getAccountApi(@EndpointParam(parser = RegionToEndpoint.class) String region);
+
+   @Delegate
+   BulkApi getBulkApi(@EndpointParam(parser = RegionToEndpoint.class) String region);
+
+   @Delegate
+   ContainerApi getContainerApi(@EndpointParam(parser = RegionToEndpoint.class) String region);
+
+   @Delegate
+   @Path("/{containerName}")
+   ObjectApi getObjectApi(@EndpointParam(parser = RegionToEndpoint.class) String region,
+         @PathParam("containerName") String containerName);
+
+   @Delegate
+   @Path("/{containerName}")
+   StaticLargeObjectApi getStaticLargeObjectApi(@EndpointParam(parser = RegionToEndpoint.class) String region,
+         @PathParam("containerName") String containerName);
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
new file mode 100644
index 0000000..8d77bb0
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.SERVICE_TYPE;
+import static org.jclouds.reflect.Reflection2.typeToken;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.openstack.keystone.v2_0.config.AuthenticationApiModule;
+import org.jclouds.openstack.keystone.v2_0.config.CredentialTypes;
+import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule;
+import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule.RegionModule;
+import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext;
+import org.jclouds.openstack.swift.v1.blobstore.config.SignUsingTemporaryUrls;
+import org.jclouds.openstack.swift.v1.blobstore.config.SwiftBlobStoreContextModule;
+import org.jclouds.openstack.swift.v1.config.SwiftHttpApiModule;
+import org.jclouds.openstack.swift.v1.config.SwiftTypeAdapters;
+import org.jclouds.openstack.v2_0.ServiceType;
+import org.jclouds.rest.internal.BaseHttpApiMetadata;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+/**
+ * Implementation of {@link ApiMetadata} for the Swift API.
+ */
+public class SwiftApiMetadata extends BaseHttpApiMetadata<SwiftApi> {
+
+   @Override
+   public Builder toBuilder() {
+      return new Builder().fromApiMetadata(this);
+   }
+
+   public SwiftApiMetadata() {
+      this(new Builder());
+   }
+
+   protected SwiftApiMetadata(Builder builder) {
+      super(builder);
+   }
+
+   public static Properties defaultProperties() {
+      Properties properties = BaseHttpApiMetadata.defaultProperties();
+      properties.setProperty(SERVICE_TYPE, ServiceType.OBJECT_STORE);
+      properties.setProperty(CREDENTIAL_TYPE, CredentialTypes.PASSWORD_CREDENTIALS);
+      return properties;
+   }
+
+   public static class Builder extends BaseHttpApiMetadata.Builder<SwiftApi, Builder> {
+
+      protected Builder() {
+          id("openstack-swift")
+         .name("OpenStack Swift API")
+         .identityName("${tenantName}:${userName} or ${userName}, if your keystone supports a default tenant")
+         .credentialName("${password}")
+         .documentation(URI.create("http://docs.openstack.org/api/openstack-object-storage/1.0/content/ch_object-storage-dev-overview.html"))
+         .version("1")
+         .endpointName("Keystone base url ending in /v2.0/")
+         .defaultEndpoint("http://localhost:5000/v2.0/")
+         .defaultProperties(SwiftApiMetadata.defaultProperties())
+         .view(typeToken(RegionScopedBlobStoreContext.class))
+         .defaultModules(ImmutableSet.<Class<? extends Module>>builder()
+                                     .add(AuthenticationApiModule.class)
+                                     .add(KeystoneAuthenticationModule.class)
+                                     .add(RegionModule.class)
+                                     .add(SwiftTypeAdapters.class)
+                                     .add(SwiftHttpApiModule.class)
+                                     .add(SwiftBlobStoreContextModule.class)
+                                     .add(SignUsingTemporaryUrls.class).build());
+      }
+
+      @Override
+      public SwiftApiMetadata build() {
+         return new SwiftApiMetadata(this);
+      }
+
+      @Override
+      protected Builder self() {
+         return this;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftFallbacks.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftFallbacks.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftFallbacks.java
new file mode 100644
index 0000000..c875707
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftFallbacks.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.equalTo;
+import static com.google.common.base.Throwables.propagate;
+import static org.jclouds.http.HttpUtils.contains404;
+import static org.jclouds.http.HttpUtils.returnValueOnCodeOrNull;
+
+import org.jclouds.Fallback;
+
+public final class SwiftFallbacks {
+
+   public static final class TrueOn404FalseOn409 implements Fallback<Boolean> {
+      @Override
+      public Boolean createOrPropagate(Throwable t) throws Exception {
+         if (contains404(checkNotNull(t, "throwable")))
+            return true;
+         if (returnValueOnCodeOrNull(t, false, equalTo(409)) != null)
+            return false;
+         throw propagate(t);
+      }
+   }
+
+   private SwiftFallbacks() {
+      throw new AssertionError("intentionally unimplemented");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java
new file mode 100644
index 0000000..c6d6030
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/TemporaryUrlSigner.java
@@ -0,0 +1,89 @@
+/*
+ * 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;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Suppliers.memoizeWithExpiration;
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.io.BaseEncoding.base16;
+import static java.lang.String.format;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.jclouds.openstack.swift.v1.features.AccountApi;
+
+import com.google.common.base.Supplier;
+
+/**
+ * Use this utility to create temporary urls.
+ */
+public class TemporaryUrlSigner {
+
+   public static TemporaryUrlSigner checkApiEvery(final AccountApi api, long seconds) {
+      Supplier<String> keySupplier = memoizeWithExpiration(new TemporaryUrlKeyFromAccount(api), seconds, SECONDS);
+      return new TemporaryUrlSigner(keySupplier);
+   }
+
+   private final Supplier<String> keySupplier;
+
+   TemporaryUrlSigner(Supplier<String> keySupplier) {
+      this.keySupplier = keySupplier;
+   }
+
+   public String sign(String method, String path, long expirationTimestampSeconds) {
+      checkNotNull(method, "method");
+      checkNotNull(path, "path");
+      checkArgument(expirationTimestampSeconds > 0, "expirationTimestamp must be a unix epoch timestamp");
+      String hmacBody = format("%s\n%s\n%s", method, expirationTimestampSeconds, path);
+      return base16().lowerCase().encode(hmacSHA1(hmacBody));
+   }
+
+   byte[] hmacSHA1(String data) {
+      try {
+         String key = keySupplier.get();
+         checkState(key != null, "%s returned a null temporaryUrlKey!", keySupplier);
+         Mac mac = Mac.getInstance("HmacSHA1");
+         mac.init(new SecretKeySpec(key.getBytes(UTF_8), "HmacSHA1"));
+         return mac.doFinal(data.getBytes(UTF_8));
+      } catch (Exception e) {
+         throw propagate(e);
+      }
+   }
+
+   static class TemporaryUrlKeyFromAccount implements Supplier<String> {
+      private final AccountApi api;
+
+      private TemporaryUrlKeyFromAccount(AccountApi api) {
+         this.api = checkNotNull(api, "accountApi");
+      }
+
+      @Override
+      public String get() {
+         return api.get().getTemporaryUrlKey().orNull();
+      }
+
+      @Override
+      public String toString() {
+         return format("get().getTemporaryUrlKey() using %s", api);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
new file mode 100644
index 0000000..9451934
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
@@ -0,0 +1,141 @@
+/*
+ * 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.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 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;
+
+/**
+ * Will lower-case header keys due to a swift implementation to return headers
+ * in a different case than sent. ex.
+ *
+ * <pre>
+ * >> X-Account-Meta-MyDelete1: foo
+ * >> X-Account-Meta-MyDelete2: bar
+ * </pre>
+ *
+ * results in:
+ *
+ * <pre>
+ * << X-Account-Meta-Mydelete1: foo
+ * << X-Account-Meta-Mydelete2: bar
+ * </pre>
+ *
+ * <h4>Note</h4> <br/>
+ * 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
+ * Turkish from working.
+ */
+public class BindMetadataToHeaders implements Binder {
+
+   public static class BindAccountMetadataToHeaders extends BindMetadataToHeaders {
+      BindAccountMetadataToHeaders() {
+         super(ACCOUNT_METADATA_PREFIX);
+      }
+   }
+
+   public static class BindRemoveAccountMetadataToHeaders extends BindMetadataToHeaders.ForRemoval {
+      BindRemoveAccountMetadataToHeaders() {
+         super(ACCOUNT_METADATA_PREFIX);
+      }
+   }
+
+   public static class BindContainerMetadataToHeaders extends BindMetadataToHeaders {
+      BindContainerMetadataToHeaders() {
+         super(CONTAINER_METADATA_PREFIX);
+      }
+   }
+
+   public static class BindRemoveContainerMetadataToHeaders extends BindMetadataToHeaders.ForRemoval {
+      BindRemoveContainerMetadataToHeaders() {
+         super(CONTAINER_METADATA_PREFIX);
+      }
+   }
+
+   public static class BindObjectMetadataToHeaders extends BindMetadataToHeaders {
+      BindObjectMetadataToHeaders() {
+         super(OBJECT_METADATA_PREFIX);
+      }
+   }
+
+   public static class BindRemoveObjectMetadataToHeaders extends BindMetadataToHeaders.ForRemoval {
+      BindRemoveObjectMetadataToHeaders() {
+         super(OBJECT_METADATA_PREFIX);
+      }
+   }
+
+   /**
+    * @see <a
+    *      href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/delete-account-metadata.html">documentation</a>
+    */
+   public abstract static class ForRemoval extends BindMetadataToHeaders {
+      ForRemoval(String metadataPrefix) {
+         super(metadataPrefix);
+      }
+
+      @Override
+      protected void putMetadata(Builder<String, String> headers, String key, String value) {
+         headers.put(String.format("x-remove%s", key.substring(1)), "ignored");
+      }
+   }
+
+   private final String metadataPrefix;
+
+   public BindMetadataToHeaders(String metadataPrefix) {
+      this.metadataPrefix = checkNotNull(metadataPrefix, "metadataPrefix");
+   }
+
+   @SuppressWarnings("unchecked")
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      checkNotNull(request, "request");
+      checkArgument(input instanceof Map<?, ?>, "input must be a non-null java.util.Map!");
+      Map<String, String> metadata = Map.class.cast(input);
+      ImmutableMultimap<String, String> headers = toHeaders(metadata);
+      return (R) request.toBuilder().replaceHeaders(headers).build();
+   }
+
+   protected void putMetadata(Builder<String, String> headers, String key, String value) {
+      headers.put(key, value);
+   }
+
+   public ImmutableMultimap<String, String> toHeaders(Map<String, String> metadata) {
+      Builder<String, String> builder = ImmutableMultimap.<String, String> builder();
+      for (Entry<String, String> keyVal : metadata.entrySet()) {
+         String keyInLowercase = keyVal.getKey().toLowerCase();
+         if (keyVal.getKey().startsWith(metadataPrefix)) {
+            putMetadata(builder, keyInLowercase, keyVal.getValue());
+         } else {
+            putMetadata(builder, String.format("%s%s", metadataPrefix, keyInLowercase), keyVal.getValue());
+         }
+      }
+      return builder.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java
new file mode 100644
index 0000000..f24a3ad
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java
@@ -0,0 +1,72 @@
+/*
+ * 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.binders;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.net.HttpHeaders.ETAG;
+import static com.google.common.net.HttpHeaders.TRANSFER_ENCODING;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_DELETE_AT;
+
+import java.util.Date;
+
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpRequest.Builder;
+import org.jclouds.io.Payload;
+import org.jclouds.rest.Binder;
+
+import com.google.common.hash.HashCode;
+
+public class SetPayload implements Binder {
+
+   @SuppressWarnings("unchecked")
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      Builder<?> builder = request.toBuilder();
+      Payload payload = Payload.class.cast(input);
+
+      if (payload.getContentMetadata().getContentType() == null) {
+         // TODO: use `X-Detect-Content-Type` here. Should be configurable via a property.
+         payload.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
+      }
+
+      Long contentLength = payload.getContentMetadata().getContentLength();
+      if (contentLength != null && contentLength >= 0) {
+         checkArgument(contentLength <= 5l * 1024 * 1024 * 1024, "maximum size for put object is 5GB, %s",
+               contentLength);
+      } else {
+         builder.replaceHeader(TRANSFER_ENCODING, "chunked").build();
+      }
+
+      HashCode md5 = payload.getContentMetadata().getContentMD5AsHashCode();
+      if (md5 != null) {
+         // Swift will validate the md5, if placed as an ETag header
+         builder.replaceHeader(ETAG, base16().lowerCase().encode(md5.asBytes()));
+      }
+
+      Date expires = payload.getContentMetadata().getExpires();
+      if (expires != null) {
+         builder.addHeader(OBJECT_DELETE_AT,
+               String.valueOf(MILLISECONDS.toSeconds(expires.getTime()))).build();
+      }
+
+      return (R) builder.payload(payload).build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java
new file mode 100644
index 0000000..e58df89
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java
@@ -0,0 +1,154 @@
+/*
+ * 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.blobstore;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.jclouds.Context;
+import org.jclouds.blobstore.BlobRequestSigner;
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.attr.ConsistencyModel;
+import org.jclouds.internal.BaseView;
+import org.jclouds.location.Provider;
+import org.jclouds.location.Region;
+import org.jclouds.rest.Utils;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.reflect.TypeToken;
+
+/**
+ * Implementation of {@link BlobStoreContext} which allows you to employ
+ * multiple regions.
+ *
+ * Example.
+ *
+ * <pre>
+ * ctx = contextBuilder.buildView(RegionScopedBlobStoreContext.class);
+ *
+ * Set&lt;String&gt; regionIds = ctx.getConfiguredRegions();
+ *
+ * // isolated to a specific region
+ * BlobStore texasBlobStore = ctx.getBlobStore(&quot;US-TX&quot;);
+ * BlobStore virginiaBlobStore = ctx.getBlobStore(&quot;US-VA&quot;);
+ * </pre>
+ */
+public class RegionScopedBlobStoreContext extends BaseView implements BlobStoreContext {
+
+   /**
+    * @return regions supported in this context.
+    */
+   public Set<String> getConfiguredRegions() {
+      return regionIds.get();
+   }
+
+   /**
+    * @param regionId
+    *           valid region id from {@link #getConfiguredRegions()}
+    * @throws IllegalArgumentException
+    *            if {@code regionId} was invalid.
+    */
+   public BlobStore getBlobStore(String regionId) {
+      checkRegionId(regionId);
+      return blobStore.apply(regionId);
+   }
+
+   /**
+    * @param regionId
+    *           valid region id from {@link #getConfiguredRegions()}
+    * @throws IllegalArgumentException
+    *            if {@code regionId} was invalid.
+    */
+   public BlobRequestSigner getSigner(String regionId) {
+      checkRegionId(regionId);
+      return blobRequestSigner.apply(regionId);
+   }
+
+   protected void checkRegionId(String regionId) {
+      checkArgument(getConfiguredRegions().contains(checkNotNull(regionId, "regionId was null")), "region %s not in %s",
+            regionId, getConfiguredRegions());
+   }
+
+   private final Supplier<Set<String>> regionIds;
+   private final Supplier<String> implicitRegionId;
+   // factory functions are decoupled so that you can exchange how requests are
+   // signed or decorate without a class hierarchy dependency
+   private final Function<String, BlobStore> blobStore;
+   private final Function<String, BlobRequestSigner> blobRequestSigner;
+   private final Utils utils;
+   private final ConsistencyModel consistencyModel;
+
+   @Inject
+   public RegionScopedBlobStoreContext(@Provider Context backend, @Provider TypeToken<? extends Context> backendType,
+         @Region Supplier<Set<String>> regionIds, @Region Supplier<String> implicitRegionId,
+         Function<String, BlobStore> blobStore, Function<String, BlobRequestSigner> blobRequestSigner, Utils utils,
+         ConsistencyModel consistencyModel) {
+      super(backend, backendType);
+      this.regionIds = checkNotNull(regionIds, "regionIds");
+      this.implicitRegionId = checkNotNull(implicitRegionId, "implicitRegionId");
+      this.blobStore = checkNotNull(blobStore, "blobStore");
+      this.blobRequestSigner = checkNotNull(blobRequestSigner, "blobRequestSigner");
+      this.utils = checkNotNull(utils, "utils");
+      this.consistencyModel = checkNotNull(consistencyModel, "consistencyModel");
+   }
+
+   @Override
+   public ConsistencyModel getConsistencyModel() {
+      return consistencyModel;
+   }
+
+   @Override
+   public BlobStore getBlobStore() {
+      return getBlobStore(implicitRegionId.get());
+   }
+
+   @Override
+   public BlobRequestSigner getSigner() {
+      return getSigner(implicitRegionId.get());
+   }
+
+   @Override
+   public Utils utils() {
+      return utils;
+   }
+
+   @Override
+   public void close() {
+      delegate().close();
+   }
+
+   public int hashCode() {
+      return delegate().hashCode();
+   }
+
+   @Override
+   public String toString() {
+      return delegate().toString();
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      return delegate().equals(obj);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
new file mode 100644
index 0000000..c0f65c5
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
@@ -0,0 +1,312 @@
+/*
+ * 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.blobstore;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.tryFind;
+import static com.google.common.collect.Lists.transform;
+import static org.jclouds.blobstore.options.ListContainerOptions.Builder.recursive;
+import static org.jclouds.location.predicates.LocationPredicates.idEquals;
+import static org.jclouds.openstack.swift.v1.options.PutOptions.Builder.metadata;
+
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.domain.BlobBuilder;
+import org.jclouds.blobstore.domain.BlobMetadata;
+import org.jclouds.blobstore.domain.MutableBlobMetadata;
+import org.jclouds.blobstore.domain.PageSet;
+import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.blobstore.domain.StorageType;
+import org.jclouds.blobstore.domain.internal.BlobBuilderImpl;
+import org.jclouds.blobstore.domain.internal.BlobImpl;
+import org.jclouds.blobstore.domain.internal.PageSetImpl;
+import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
+import org.jclouds.blobstore.options.CreateContainerOptions;
+import org.jclouds.blobstore.options.GetOptions;
+import org.jclouds.blobstore.options.ListContainerOptions;
+import org.jclouds.blobstore.options.PutOptions;
+import org.jclouds.blobstore.strategy.ClearListStrategy;
+import org.jclouds.collect.Memoized;
+import org.jclouds.domain.Location;
+import org.jclouds.io.Payload;
+import org.jclouds.io.payloads.ByteSourcePayload;
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.blobstore.functions.ToBlobMetadata;
+import org.jclouds.openstack.swift.v1.blobstore.functions.ToListContainerOptions;
+import org.jclouds.openstack.swift.v1.blobstore.functions.ToResourceMetadata;
+import org.jclouds.openstack.swift.v1.domain.Container;
+import org.jclouds.openstack.swift.v1.domain.ObjectList;
+import org.jclouds.openstack.swift.v1.domain.SwiftObject;
+import org.jclouds.openstack.swift.v1.features.ObjectApi;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteSource;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.assistedinject.Assisted;
+
+public class RegionScopedSwiftBlobStore implements BlobStore {
+
+   @Inject
+   protected RegionScopedSwiftBlobStore(Injector baseGraph, BlobStoreContext context, SwiftApi api,
+         @Memoized Supplier<Set<? extends Location>> locations, @Assisted String regionId) {
+      checkNotNull(regionId, "regionId");
+      Optional<? extends Location> found = tryFind(locations.get(), idEquals(regionId));
+      checkArgument(found.isPresent(), "region %s not in %s", regionId, locations.get());
+      this.region = found.get();
+      this.regionId = regionId;
+      this.toResourceMetadata = new ToResourceMetadata(found.get());
+      this.context = context;
+      this.api = api;
+      // until we parameterize ClearListStrategy with a factory
+      this.clearList = baseGraph.createChildInjector(new AbstractModule() {
+         @Override
+         protected void configure() {
+            bind(BlobStore.class).toInstance(RegionScopedSwiftBlobStore.this);
+         }
+      }).getInstance(ClearListStrategy.class);
+   }
+
+   private final BlobStoreContext context;
+   private final ClearListStrategy clearList;
+   private final SwiftApi api;
+   private final Location region;
+   private final String regionId;
+   private final BlobToHttpGetOptions toGetOptions = new BlobToHttpGetOptions();
+   private final ToListContainerOptions toListContainerOptions = new ToListContainerOptions();
+   private final ToResourceMetadata toResourceMetadata;
+
+   @Override
+   public Set<? extends Location> listAssignableLocations() {
+      return ImmutableSet.of(region);
+   }
+
+   @Override
+   public PageSet<? extends StorageMetadata> list() {
+      // TODO: there may eventually be >10k containers..
+      FluentIterable<StorageMetadata> containers = api.getContainerApi(regionId).list()
+            .transform(toResourceMetadata);
+      return new PageSetImpl<StorageMetadata>(containers, null);
+   }
+
+   @Override
+   public boolean containerExists(String container) {
+      Container val = api.getContainerApi(regionId).get(container);
+      containerCache.put(container, Optional.fromNullable(val));
+      return val != null;
+   }
+
+   @Override
+   public boolean createContainerInLocation(Location location, String container) {
+      return createContainerInLocation(location, container, CreateContainerOptions.NONE);
+   }
+
+   @Override
+   public boolean createContainerInLocation(Location location, String container, CreateContainerOptions options) {
+      checkArgument(location == null || location.equals(region), "location must be null or %s", region);
+      if (options.isPublicRead()) {
+         return api.getContainerApi(regionId).create(container, ANYBODY_READ);
+      }
+      return api.getContainerApi(regionId).create(container, BASIC_CONTAINER);
+   }
+
+   private static final org.jclouds.openstack.swift.v1.options.CreateContainerOptions BASIC_CONTAINER = new org.jclouds.openstack.swift.v1.options.CreateContainerOptions();
+   private static final org.jclouds.openstack.swift.v1.options.CreateContainerOptions ANYBODY_READ = new org.jclouds.openstack.swift.v1.options.CreateContainerOptions()
+         .anybodyRead();
+
+   @Override
+   public PageSet<? extends StorageMetadata> list(String container) {
+      return list(container, ListContainerOptions.NONE);
+   }
+
+   @Override
+   public PageSet<? extends StorageMetadata> list(final String container, ListContainerOptions options) {
+      ObjectApi objectApi = api.getObjectApi(regionId, container);
+      ObjectList objects = objectApi.list(toListContainerOptions.apply(options));
+      if (objects == null) {
+         containerCache.put(container, Optional.<Container> absent());
+         return new PageSetImpl<StorageMetadata>(ImmutableList.<StorageMetadata> of(), null);
+      } else {
+         containerCache.put(container, Optional.of(objects.getContainer()));
+         List<? extends StorageMetadata> list = transform(objects, toBlobMetadata(container));
+         int limit = Optional.fromNullable(options.getMaxResults()).or(10000);
+         String marker = list.size() == limit ? list.get(limit - 1).getName() : null;
+         // TODO: we should probably deprecate this option
+         if (options.isDetailed()) {
+            list = transform(list, new Function<StorageMetadata, StorageMetadata>() {
+               @Override
+               public StorageMetadata apply(StorageMetadata input) {
+                  if (input.getType() != StorageType.BLOB) {
+                     return input;
+                  }
+                  return blobMetadata(container, input.getName());
+               }
+            });
+         }
+         return new PageSetImpl<StorageMetadata>(list, marker);
+      }
+   }
+
+   @Override
+   public boolean blobExists(String container, String name) {
+      return blobMetadata(container, name) != null;
+   }
+
+   @Override
+   public String putBlob(String container, Blob blob) {
+      return putBlob(container, blob, PutOptions.NONE);
+   }
+
+   @Override
+   public String putBlob(String container, Blob blob, PutOptions options) {
+      if (options.isMultipart()) {
+         throw new UnsupportedOperationException();
+      }
+      ObjectApi objectApi = api.getObjectApi(regionId, container);
+      return objectApi.put(blob.getMetadata().getName(), blob.getPayload(), metadata(blob.getMetadata().getUserMetadata()));
+   }
+
+   @Override
+   public BlobMetadata blobMetadata(String container, String name) {
+      SwiftObject object = api.getObjectApi(regionId, container).get(name);
+      if (object == null) {
+         return null;
+      }
+      return toBlobMetadata(container).apply(object);
+   }
+
+   @Override
+   public Blob getBlob(String container, String key) {
+      return getBlob(container, key, GetOptions.NONE);
+   }
+
+   @Override
+   public Blob getBlob(String container, String name, GetOptions options) {
+      ObjectApi objectApi = api.getObjectApi(regionId, container);
+      SwiftObject object = objectApi.get(name, toGetOptions.apply(options));
+      if (object == null) {
+         return null;
+      }
+      Blob blob = new BlobImpl(toBlobMetadata(container).apply(object));
+      blob.setPayload(object.getPayload());
+      blob.setAllHeaders(object.getHeaders());
+      return blob;
+   }
+
+   @Override
+   public void removeBlob(String container, String name) {
+      api.getObjectApi(regionId, container).delete(name);
+   }
+
+   @Override
+   public BlobStoreContext getContext() {
+      return context;
+   }
+
+   @Override
+   public BlobBuilder blobBuilder(String name) {
+      return new BlobBuilderImpl().name(name);
+   }
+
+   @Override
+   public boolean directoryExists(String containerName, String directory) {
+      return api.getObjectApi(regionId, containerName)
+            .get(directory) != null;
+   }
+
+   @Override
+   public void createDirectory(String containerName, String directory) {
+      api.getObjectApi(regionId, containerName)
+            .put(directory, directoryPayload);
+   }
+
+   private final Payload directoryPayload = new ByteSourcePayload(ByteSource.wrap(new byte[] {})) {
+      {
+         getContentMetadata().setContentType("application/directory");
+      }
+   };
+
+   @Override
+   public void deleteDirectory(String containerName, String directory) {
+      api.getObjectApi(regionId, containerName).delete(directory);
+   }
+
+   @Override
+   public long countBlobs(String containerName) {
+      Container container = api.getContainerApi(regionId).get(containerName);
+      // undefined if container doesn't exist, so default to zero
+      return container != null ? container.getObjectCount() : 0;
+   }
+
+   @Override
+   public void clearContainer(String containerName) {
+      clearContainer(containerName, recursive());
+   }
+
+   @Override
+   public void clearContainer(String containerName, ListContainerOptions options) {
+      // this could be implemented to use bulk delete
+      clearList.execute(containerName, options);
+   }
+
+   @Override
+   public void deleteContainer(String container) {
+      clearContainer(container, recursive());
+      api.getContainerApi(regionId).deleteIfEmpty(container);
+      containerCache.invalidate(container);
+   }
+
+   @Override
+   public boolean deleteContainerIfEmpty(String container) {
+      boolean deleted = api.getContainerApi(regionId).deleteIfEmpty(container);
+      if (deleted) {
+         containerCache.invalidate(container);
+      }
+      return deleted;
+   }
+
+   protected final LoadingCache<String, Optional<Container>> containerCache = CacheBuilder.newBuilder().build(
+         new CacheLoader<String, Optional<Container>>() {
+            public Optional<Container> load(String container) {
+               return Optional.fromNullable(api.getContainerApi(regionId).get(container));
+            }
+         });
+
+   protected Function<SwiftObject, MutableBlobMetadata> toBlobMetadata(String container) {
+      return new ToBlobMetadata(containerCache.getUnchecked(container).get());
+   }
+
+   @Override
+   public long countBlobs(String containerName, ListContainerOptions options) {
+      throw new UnsupportedOperationException();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
new file mode 100644
index 0000000..5134efa
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
@@ -0,0 +1,109 @@
+/*
+ * 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.blobstore;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Provider;
+
+import org.jclouds.blobstore.BlobRequestSigner;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.Uris;
+import org.jclouds.http.options.GetOptions;
+import org.jclouds.location.Region;
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.TemporaryUrlSigner;
+
+import com.google.common.base.Supplier;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.name.Named;
+
+/**
+ * Uses {@link TemporaryUrlSigner} to sign requests for access to blobs. If no
+ * interval is supplied, it defaults to a year.
+ */
+public class RegionScopedTemporaryUrlBlobSigner implements BlobRequestSigner {
+
+   @Inject
+   protected RegionScopedTemporaryUrlBlobSigner(@Region Supplier<Map<String, Supplier<URI>>> regionToUris,
+         @Named(PROPERTY_SESSION_INTERVAL) long seconds, @TimeStamp Provider<Long> timestamp, SwiftApi api,
+         @Assisted String regionId) {
+      checkNotNull(regionId, "regionId");
+      this.timestamp = timestamp;
+      this.signer = TemporaryUrlSigner.checkApiEvery(api.getAccountApi(regionId), seconds);
+      this.storageUrl = regionToUris.get().get(regionId).get();
+   }
+
+   private static final long YEAR = TimeUnit.DAYS.toSeconds(365);
+   private final BlobToHttpGetOptions toGetOptions = new BlobToHttpGetOptions();
+   private final Provider<Long> timestamp;
+   private final TemporaryUrlSigner signer;
+   private final URI storageUrl;
+
+   @Override
+   public HttpRequest signGetBlob(String container, String name) {
+      return signGetBlob(container, name, YEAR);
+   }
+
+   @Override
+   public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
+      return sign("GET", container, name, GetOptions.NONE, timestamp.get() + timeInSeconds);
+   }
+
+   @Override
+   public HttpRequest signGetBlob(String container, String name, org.jclouds.blobstore.options.GetOptions options) {
+      return sign("GET", container, name, toGetOptions.apply(options), timestamp.get() + YEAR);
+   }
+
+   @Override
+   public HttpRequest signPutBlob(String container, Blob blob) {
+      return signPutBlob(container, blob, YEAR);
+   }
+
+   @Override
+   public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) {
+      return sign("PUT", container, blob.getMetadata().getName(), GetOptions.NONE, timestamp.get() + timeInSeconds);
+   }
+
+   @Override
+   public HttpRequest signRemoveBlob(String container, String name) {
+      return sign("DELETE", container, name, GetOptions.NONE, timestamp.get() + YEAR);
+   }
+
+   private HttpRequest sign(String method, String container, String name, GetOptions options, long expires) {
+      checkNotNull(container, "container");
+      checkNotNull(name, "name");
+      URI url = Uris.uriBuilder(storageUrl).appendPath(container).appendPath(name).build();
+      String signature = signer.sign(method, url.getPath(), expires);
+      return HttpRequest.builder()
+                        .method(method)
+                        .endpoint(url)
+                        .addQueryParams(options.buildQueryParameters())
+                        .addQueryParam("temp_url_sig", signature)
+                        .addQueryParam("temp_url_expires", String.valueOf(expires))
+                        .headers(options.buildRequestHeaders()).build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java
new file mode 100644
index 0000000..43a876d
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java
@@ -0,0 +1,67 @@
+/*
+ * 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.blobstore.config;
+
+import javax.inject.Inject;
+
+import org.jclouds.blobstore.BlobRequestSigner;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.openstack.swift.v1.blobstore.RegionScopedTemporaryUrlBlobSigner;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ForwardingObject;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+
+public class SignUsingTemporaryUrls extends AbstractModule {
+
+   @Override
+   protected void configure() {
+      install(new FactoryModuleBuilder().build(Factory.class));
+   }
+
+   interface Factory {
+      RegionScopedTemporaryUrlBlobSigner create(String in);
+   }
+
+   @Provides
+   Function<String, BlobRequestSigner> blobRequestSigner(FactoryFunction in) {
+      return in;
+   }
+
+   static class FactoryFunction extends ForwardingObject implements Function<String, BlobRequestSigner> {
+      @Inject
+      Factory delegate;
+
+      @Override
+      protected Factory delegate() {
+         return delegate;
+      }
+
+      @Override
+      public BlobRequestSigner apply(String in) {
+         return delegate.create(in);
+      }
+   }
+
+   @Provides
+   @TimeStamp
+   protected Long unixEpochTimestamp() {
+      return System.currentTimeMillis() / 1000;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java
new file mode 100644
index 0000000..c3b0a82
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java
@@ -0,0 +1,65 @@
+/*
+ * 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.blobstore.config;
+
+import javax.inject.Inject;
+
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.attr.ConsistencyModel;
+import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext;
+import org.jclouds.openstack.swift.v1.blobstore.RegionScopedSwiftBlobStore;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ForwardingObject;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
+
+public class SwiftBlobStoreContextModule extends AbstractModule {
+
+   @Override
+   protected void configure() {
+      bind(ConsistencyModel.class).toInstance(ConsistencyModel.EVENTUAL);
+      bind(BlobStoreContext.class).to(RegionScopedBlobStoreContext.class);
+      install(new FactoryModuleBuilder().build(Factory.class));
+   }
+
+   interface Factory {
+      RegionScopedSwiftBlobStore create(String in);
+   }
+
+   @Provides
+   Function<String, BlobStore> blobStore(FactoryFunction in) {
+      return in;
+   }
+
+   static class FactoryFunction extends ForwardingObject implements Function<String, BlobStore> {
+      @Inject
+      Factory delegate;
+
+      @Override
+      protected Factory delegate() {
+         return delegate;
+      }
+
+      @Override
+      public BlobStore apply(String in) {
+         return delegate.create(in);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java
new file mode 100644
index 0000000..6a1b8c5
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToBlobMetadata.java
@@ -0,0 +1,71 @@
+/*
+ * 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.blobstore.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.blobstore.domain.MutableBlobMetadata;
+import org.jclouds.blobstore.domain.StorageType;
+import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl;
+import org.jclouds.blobstore.strategy.IfDirectoryReturnNameStrategy;
+import org.jclouds.blobstore.strategy.internal.MarkersIfDirectoryReturnNameStrategy;
+import org.jclouds.openstack.swift.v1.domain.Container;
+import org.jclouds.openstack.swift.v1.domain.SwiftObject;
+
+import com.google.common.base.Function;
+
+public class ToBlobMetadata implements Function<SwiftObject, MutableBlobMetadata> {
+
+   private final IfDirectoryReturnNameStrategy ifDirectoryReturnName = new MarkersIfDirectoryReturnNameStrategy();
+   private final Container container;
+
+   public ToBlobMetadata(Container container) {
+      this.container = checkNotNull(container, "container");
+   }
+
+   @Override
+   public MutableBlobMetadata apply(SwiftObject from) {
+      if (from == null)
+         return null;
+      MutableBlobMetadata to = new MutableBlobMetadataImpl();
+      to.setContainer(container.getName());
+      if (container.getAnybodyRead().isPresent()) {
+         to.setPublicUri(from.getUri());
+      }
+      to.setUri(from.getUri());
+      to.setETag(from.getETag());
+      to.setName(from.getName());
+      to.setLastModified(from.getLastModified());
+      to.setContentMetadata(from.getPayload().getContentMetadata());
+      to.getContentMetadata().setContentMD5(from.getPayload().getContentMetadata().getContentMD5AsHashCode());
+      to.getContentMetadata().setExpires(from.getPayload().getContentMetadata().getExpires());
+      to.setUserMetadata(from.getMetadata());
+      String directoryName = ifDirectoryReturnName.execute(to);
+      if (directoryName != null) {
+         to.setName(directoryName);
+         to.setType(StorageType.RELATIVE_PATH);
+      } else {
+         to.setType(StorageType.BLOB);
+      }
+      return to;
+   }
+
+   @Override
+   public String toString() {
+      return "ObjectToBlobMetadata(" + container + ")";
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToListContainerOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToListContainerOptions.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToListContainerOptions.java
new file mode 100644
index 0000000..2650f5b
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToListContainerOptions.java
@@ -0,0 +1,52 @@
+/*
+ * 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.blobstore.functions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.blobstore.options.ListContainerOptions;
+
+import com.google.common.base.Function;
+
+public class ToListContainerOptions implements
+      Function<ListContainerOptions, org.jclouds.openstack.swift.v1.options.ListContainerOptions> {
+
+   @Override
+   public org.jclouds.openstack.swift.v1.options.ListContainerOptions apply(ListContainerOptions from) {
+      checkNotNull(from, "set options to instance NONE instead of passing null");
+      org.jclouds.openstack.swift.v1.options.ListContainerOptions options = new org.jclouds.openstack.swift.v1.options.ListContainerOptions();
+      if ((from.getDir() == null) && (from.isRecursive())) {
+         options.prefix("");
+      }
+      if ((from.getDir() == null) && (!from.isRecursive())) {
+         options.path("");
+      }
+      if ((from.getDir() != null) && (from.isRecursive())) {
+         options.prefix(from.getDir().endsWith("/") ? from.getDir() : from.getDir() + "/");
+      }
+      if ((from.getDir() != null) && (!from.isRecursive())) {
+         options.path(from.getDir());
+      }
+      if (from.getMarker() != null) {
+         options.marker(from.getMarker());
+      }
+      if (from.getMaxResults() != null) {
+         options.limit(from.getMaxResults());
+      }
+      return options;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java
new file mode 100644
index 0000000..bc5e186
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java
@@ -0,0 +1,44 @@
+/*
+ * 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.blobstore.functions;
+
+import org.jclouds.blobstore.domain.MutableStorageMetadata;
+import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.blobstore.domain.StorageType;
+import org.jclouds.blobstore.domain.internal.MutableStorageMetadataImpl;
+import org.jclouds.domain.Location;
+import org.jclouds.openstack.swift.v1.domain.Container;
+
+import com.google.common.base.Function;
+
+public class ToResourceMetadata implements Function<Container, StorageMetadata> {
+   private Location region;
+
+   public ToResourceMetadata(Location region) {
+      this.region = region;
+   }
+
+   @Override
+   public StorageMetadata apply(Container from) {
+      MutableStorageMetadata to = new MutableStorageMetadataImpl();
+      to.setName(from.getName());
+      to.setLocation(region);
+      to.setType(StorageType.CONTAINER);
+      to.setUserMetadata(from.getMetadata());
+      return to;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/BaseSwiftHttpApiModule.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/BaseSwiftHttpApiModule.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/BaseSwiftHttpApiModule.java
new file mode 100644
index 0000000..d6d8b6a
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/BaseSwiftHttpApiModule.java
@@ -0,0 +1,46 @@
+/*
+ * 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.config;
+
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.annotation.ClientError;
+import org.jclouds.http.annotation.Redirection;
+import org.jclouds.http.annotation.ServerError;
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.openstack.swift.v1.handlers.SwiftErrorHandler;
+import org.jclouds.rest.ConfiguresHttpApi;
+import org.jclouds.rest.config.HttpApiModule;
+
+@ConfiguresHttpApi
+public abstract class BaseSwiftHttpApiModule<A extends SwiftApi> extends HttpApiModule<A> {
+
+   protected BaseSwiftHttpApiModule(Class<A> api) {
+      super(api);
+   }
+
+   @Override
+   protected void configure() {
+      super.configure();
+   }
+
+   @Override
+   protected void bindErrorHandlers() {
+      bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(SwiftErrorHandler.class);
+      bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(SwiftErrorHandler.class);
+      bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(SwiftErrorHandler.class);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftHttpApiModule.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftHttpApiModule.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftHttpApiModule.java
new file mode 100644
index 0000000..cb93760
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftHttpApiModule.java
@@ -0,0 +1,32 @@
+/*
+ * 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.config;
+import org.jclouds.openstack.swift.v1.SwiftApi;
+import org.jclouds.rest.ConfiguresHttpApi;
+
+@ConfiguresHttpApi
+public class SwiftHttpApiModule extends BaseSwiftHttpApiModule<SwiftApi> {
+
+   public SwiftHttpApiModule() {
+      super(SwiftApi.class);
+   }
+
+   @Override
+   protected void configure() {
+      super.configure();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdapters.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdapters.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdapters.java
new file mode 100644
index 0000000..4eafdb6
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/config/SwiftTypeAdapters.java
@@ -0,0 +1,119 @@
+/*
+ * 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.config;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.util.Map;
+
+import org.jclouds.json.config.GsonModule.DateAdapter;
+import org.jclouds.json.config.GsonModule.Iso8601DateAdapter;
+import org.jclouds.openstack.swift.v1.domain.BulkDeleteResponse;
+import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+
+public class SwiftTypeAdapters extends AbstractModule {
+
+   @Override
+   protected void configure() {
+      bind(DateAdapter.class).to(Iso8601DateAdapter.class);
+   }
+
+   @Provides
+   public Map<Type, Object> provideCustomAdapterBindings() {
+      return ImmutableMap.<Type, Object> builder()
+            .put(ExtractArchiveResponse.class, new ExtractArchiveResponseAdapter())
+            .put(BulkDeleteResponse.class, new BulkDeleteResponseAdapter()).build();
+   }
+
+   static class ExtractArchiveResponseAdapter extends TypeAdapter<ExtractArchiveResponse> {
+
+      @Override
+      public ExtractArchiveResponse read(JsonReader reader) throws IOException {
+         int created = 0;
+         Builder<String, String> errors = ImmutableMap.<String, String> builder();
+         reader.beginObject();
+         while (reader.hasNext()) {
+            String key = reader.nextName();
+            if (key.equals("Number Files Created")) {
+               created = reader.nextInt();
+            } else if (key.equals("Errors")) {
+               readErrors(reader, errors);
+            } else {
+               reader.skipValue();
+            }
+         }
+         reader.endObject();
+         return ExtractArchiveResponse.create(created, errors.build());
+      }
+
+      @Override
+      public void write(JsonWriter arg0, ExtractArchiveResponse arg1) throws IOException {
+         throw new UnsupportedOperationException();
+      }
+   }
+
+   static class BulkDeleteResponseAdapter extends TypeAdapter<BulkDeleteResponse> {
+
+      @Override
+      public BulkDeleteResponse read(JsonReader reader) throws IOException {
+         int deleted = 0;
+         int notFound = 0;
+         Builder<String, String> errors = ImmutableMap.<String, String> builder();
+         reader.beginObject();
+         while (reader.hasNext()) {
+            String key = reader.nextName();
+            if (key.equals("Number Deleted")) {
+               deleted = reader.nextInt();
+            } else if (key.equals("Number Not Found")) {
+               notFound = reader.nextInt();
+            } else if (key.equals("Errors")) {
+               readErrors(reader, errors);
+            } else {
+               reader.skipValue();
+            }
+         }
+         reader.endObject();
+         return BulkDeleteResponse.create(deleted, notFound, errors.build());
+      }
+
+      @Override
+      public void write(JsonWriter arg0, BulkDeleteResponse arg1) throws IOException {
+         throw new UnsupportedOperationException();
+      }
+   }
+
+   static void readErrors(JsonReader reader, Builder<String, String> errors) throws IOException {
+      reader.beginArray();
+      while (reader.hasNext()) {
+         reader.beginArray();
+         String decodedPath = URI.create(reader.nextString()).getPath();
+         errors.put(decodedPath, reader.nextString());
+         reader.endArray();
+      }
+      reader.endArray();
+   }
+
+}


[4/5] Import openstack-swift from labs.

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java
new file mode 100644
index 0000000..932c134
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Account.java
@@ -0,0 +1,219 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Represents an Account in OpenStack Object Storage.
+ * 
+ * @see org.jclouds.openstack.swift.v1.features.AccountApi
+ */
+public class Account {
+
+   private final long containerCount;
+   private final long objectCount;
+   private final long bytesUsed;
+   private final Map<String, String> metadata;
+   private final Multimap<String, String> headers;
+
+   // parsed from headers, so ConstructorProperties here would be misleading
+   protected Account(long containerCount, long objectCount, long bytesUsed, Map<String, String> metadata,
+         Multimap<String, String> headers) {
+      this.containerCount = containerCount;
+      this.objectCount = objectCount;
+      this.bytesUsed = bytesUsed;
+      this.metadata = metadata == null ? ImmutableMap.<String, String> of() : metadata;
+      this.headers = headers == null ? ImmutableMultimap.<String, String> of() : headers;
+   }
+
+   /**
+    * @return The count of containers for this account.
+    */
+   public long getContainerCount() {
+      return containerCount;
+   }
+
+   /**
+    * @return The count of objects for this account.
+    */
+   public long getObjectCount() {
+      return objectCount;
+   }
+
+   /**
+    * @return The number of bytes used by this account.
+    */
+   public long getBytesUsed() {
+      return bytesUsed;
+   }
+
+   /**
+    * @return The {@link Optional&lt;String&gt;} temporary URL key for this account.
+    */
+   public Optional<String> getTemporaryUrlKey() {
+      return Optional.fromNullable(metadata.get("temp-url-key"));
+   }
+
+   /**
+    * <h3>NOTE</h3>
+    * In current swift implementations, headers keys are lower-cased. This means
+    * characters such as turkish will probably not work out well.
+    *
+    * @return a {@code Map<String, String>} containing the account metadata.
+    */
+   public Map<String, String> getMetadata() {
+      return metadata;
+   }
+
+   /**
+    * @return The HTTP headers for this account.
+    */
+   public Multimap<String, String> getHeaders() {
+      return headers;
+   }
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof Account) {
+         Account that = Account.class.cast(object);
+         return equal(getContainerCount(), that.getContainerCount())
+               && equal(getObjectCount(), that.getObjectCount())
+               && equal(getBytesUsed(), that.getBytesUsed())
+               && equal(getMetadata(), that.getMetadata());
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(getContainerCount(), getObjectCount(), getBytesUsed(), getMetadata());
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   protected ToStringHelper string() {
+      return toStringHelper(this)
+            .add("containerCount", getContainerCount())
+            .add("objectCount", getObjectCount())
+            .add("bytesUsed", getBytesUsed())
+            .add("metadata", getMetadata());
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().fromAccount(this);
+   }
+
+   public static class Builder {
+      protected long containerCount;
+      protected long objectCount;
+      protected long bytesUsed;
+      protected Multimap<String, String> headers = ImmutableMultimap.of();
+      protected Map<String, String> metadata = ImmutableMap.of();
+
+      /**
+       * @param containerCount  the count of containers for this account.
+       *
+       * @see Account#getContainerCount()
+       */
+      public Builder containerCount(long containerCount) {
+         this.containerCount = containerCount;
+         return this;
+      }
+
+      /**
+       * @param objectCount  the count of objects for this account.
+       *
+       * @see Account#getObjectCount()
+       */
+      public Builder objectCount(long objectCount) {
+         this.objectCount = objectCount;
+         return this;
+      }
+
+      /**
+       * @param bytesUsed  the number of bytes used by this account.
+       *
+       * @see Account#getBytesUsed()
+       */
+      public Builder bytesUsed(long bytesUsed) {
+         this.bytesUsed = bytesUsed;
+         return this;
+      }
+
+      /**
+       * <h3>NOTE</h3>
+       * This method will lower-case all metadata keys due to a Swift implementation
+       * decision.
+       *
+       * @param metadata  the metadata for this account. 
+       *
+       * @see Account#getMetadata()
+       */
+      public Builder metadata(Map<String, String> metadata) {
+         ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String> builder();
+         for (Entry<String, String> entry : checkNotNull(metadata, "metadata").entrySet()) {
+            builder.put(entry.getKey().toLowerCase(), entry.getValue());
+         }
+         this.metadata = builder.build();
+         return this;
+      }
+
+      /**
+       * @see Account#getHeaders()
+       */
+      public Builder headers(Multimap<String, String> headers) {
+        this.headers = headers;
+        return this;
+      }
+
+      public Account build() {
+         return new Account(containerCount, objectCount, bytesUsed, metadata, headers);
+      }
+
+      public Builder fromAccount(Account from) {
+         return containerCount(from.getContainerCount())
+               .objectCount(from.getObjectCount())
+               .bytesUsed(from.getBytesUsed())
+               .metadata(from.getMetadata())
+               .headers(from.getHeaders());
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/BulkDeleteResponse.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/BulkDeleteResponse.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/BulkDeleteResponse.java
new file mode 100644
index 0000000..4f79044
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/BulkDeleteResponse.java
@@ -0,0 +1,101 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Objects;
+
+/**
+ * Represents a response from a Bulk Delete request.
+ * 
+ * @see org.jclouds.openstack.swift.v1.features.BulkApi
+ */
+public class BulkDeleteResponse {
+   public static BulkDeleteResponse create(int deleted, int notFound, Map<String, String> errors) {
+      return new BulkDeleteResponse(deleted, notFound, errors);
+   }
+
+   private final int deleted;
+   private final int notFound;
+   private final Map<String, String> errors;
+
+   private BulkDeleteResponse(int deleted, int notFound, Map<String, String> errors) {
+      this.deleted = deleted;
+      this.notFound = notFound;
+      this.errors = checkNotNull(errors, "errors");
+   }
+
+   /** 
+    * @return The number of files deleted.
+    * */
+   public int getDeleted() {
+      return deleted;
+   }
+
+   /** 
+    * @return The number of files not found.
+    */
+   public int getNotFound() {
+      return notFound;
+   }
+
+   /** 
+    * @return a {@code Map<String, String>} containing each path that failed 
+    *         to be deleted and its corresponding error response.
+    */
+   public Map<String, String> getErrors() {
+      return errors;
+   }
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof BulkDeleteResponse) {
+         BulkDeleteResponse that = BulkDeleteResponse.class.cast(object);
+         return equal(getDeleted(), that.getDeleted())
+               && equal(getNotFound(), that.getNotFound())
+               && equal(getErrors(), that.getErrors());
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(getDeleted(), getNotFound(), getErrors());
+   }
+
+   protected ToStringHelper string() {
+      return toStringHelper(this)
+            .add("deleted", getDeleted())
+            .add("notFound", getNotFound())
+            .add("errors", getErrors());
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java
new file mode 100644
index 0000000..4a31ab7
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java
@@ -0,0 +1,237 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Represents a Container in OpenStack Object Storage.
+ * 
+ * @see org.jclouds.openstack.swift.v1.features.ContainerApi
+ */
+public class Container implements Comparable<Container> {
+
+   private final String name;
+   private final long objectCount;
+   private final long bytesUsed;
+   private final Optional<Boolean> anybodyRead;
+   private final Map<String, String> metadata;
+   private final Multimap<String, String> headers;
+
+   @ConstructorProperties({ "name", "count", "bytes", "anybodyRead", "metadata", "headers"})
+   protected Container(String name, long objectCount, long bytesUsed, Optional<Boolean> anybodyRead,
+         Map<String, String> metadata, Multimap<String, String> headers) {
+      this.name = checkNotNull(name, "name");
+      this.objectCount = objectCount;
+      this.bytesUsed = bytesUsed;
+      this.anybodyRead = anybodyRead == null ? Optional.<Boolean> absent() : anybodyRead;
+      this.metadata = metadata == null ? ImmutableMap.<String, String> of() : metadata;
+      this.headers = headers == null ? ImmutableMultimap.<String, String> of() : headers;
+   }
+
+   /**
+    * @return The name of this container.
+    */
+   public String getName() {
+      return name;
+   }
+
+   /**
+    * @return The count of objects for this container.
+    */
+   public long getObjectCount() {
+      return objectCount;
+   }
+
+   /**
+    * @return The number of bytes used by this container.
+    */
+   public long getBytesUsed() {
+      return bytesUsed;
+   }
+
+   /**
+    * Absent except in {@link ContainerApi#get(String) Get Container} commands.
+    * 
+    * @return true  if this container is publicly readable, false otherwise.
+    * 
+    * @see org.jclouds.openstack.swift.v1.options.CreateContainerOptions#anybodyRead()
+    */
+   public Optional<Boolean> getAnybodyRead() {
+      return anybodyRead;
+   }
+
+   /**
+    * <h3>NOTE</h3>
+    * In current swift implementations, headers keys are lower-cased. This means
+    * characters such as turkish will probably not work out well.
+    * 
+    * @return a {@code Map<String, String>} containing this container's metadata.
+    */
+   public Map<String, String> getMetadata() {
+      return metadata;
+   }
+
+   /**
+    * @return The HTTP headers for this account.
+    */
+   public Multimap<String, String> getHeaders() {
+      return headers;
+   }
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof Container) {
+         final Container that = Container.class.cast(object);
+         return equal(getName(), that.getName())
+               && equal(getObjectCount(), that.getObjectCount())
+               && equal(getBytesUsed(), that.getBytesUsed())
+               && equal(getMetadata(), that.getMetadata());
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(getName(), getObjectCount(), getBytesUsed(), getAnybodyRead(), getMetadata());
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   protected ToStringHelper string() {
+      return toStringHelper(this).omitNullValues()
+            .add("name", getName())
+            .add("objectCount", getObjectCount())
+            .add("bytesUsed", getBytesUsed())
+            .add("anybodyRead", getAnybodyRead().orNull())
+            .add("metadata", getMetadata());
+   }
+
+   @Override
+   public int compareTo(Container that) {
+      if (that == null)
+         return 1;
+      if (this == that)
+         return 0;
+      return this.getName().compareTo(that.getName());
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().fromContainer(this);
+   }
+
+   public static class Builder {
+      protected String name;
+      protected long objectCount;
+      protected long bytesUsed;
+      protected Optional<Boolean> anybodyRead = Optional.absent();
+      protected Map<String, String> metadata = ImmutableMap.of();
+      protected Multimap<String, String> headers = ImmutableMultimap.of();
+
+      /**
+       * @see Container#getName()
+       */
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      /**
+       * @see Container#getObjectCount()
+       */
+      public Builder objectCount(long objectCount) {
+         this.objectCount = objectCount;
+         return this;
+      }
+
+      /**
+       * @see Container#getBytesUsed()
+       */
+      public Builder bytesUsed(long bytesUsed) {
+         this.bytesUsed = bytesUsed;
+         return this;
+      }
+
+      /**
+       * @see Container#getAnybodyRead()
+       */
+      public Builder anybodyRead(Boolean anybodyRead) {
+         this.anybodyRead = Optional.fromNullable(anybodyRead);
+         return this;
+      }
+
+      /**
+       * <h3>NOTE</h3>
+       * This method will lower-case all metadata keys.
+       * 
+       * @see Container#getMetadata()
+       */
+      public Builder metadata(Map<String, String> metadata) {
+         ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String> builder();
+         for (Entry<String, String> entry : checkNotNull(metadata, "metadata").entrySet()) {
+            builder.put(entry.getKey().toLowerCase(), entry.getValue());
+         }
+         this.metadata = builder.build();
+         return this;
+      }
+
+      /**
+       * @see Container#getHeaders()
+       */
+      public Builder headers(Multimap<String, String> headers) {
+         this.headers = headers;
+         return this;
+      }
+
+      public Container build() {
+         return new Container(name, objectCount, bytesUsed, anybodyRead, metadata, headers);
+      }
+
+      public Builder fromContainer(Container from) {
+         return name(from.getName())
+               .objectCount(from.getObjectCount())
+               .bytesUsed(from.getBytesUsed())
+               .anybodyRead(from.getAnybodyRead().orNull())
+               .metadata(from.getMetadata())
+               .headers(from.getHeaders());
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ExtractArchiveResponse.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ExtractArchiveResponse.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ExtractArchiveResponse.java
new file mode 100644
index 0000000..7e06e0e
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ExtractArchiveResponse.java
@@ -0,0 +1,90 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Objects;
+
+/**
+ * Represents a response from an Extract Archive request.
+ *
+ * @see org.jclouds.openstack.swift.v1.features.BulkApi
+ */
+public class ExtractArchiveResponse {
+   public static ExtractArchiveResponse create(int created, Map<String, String> errors) {
+      return new ExtractArchiveResponse(created, errors);
+   }
+
+   private final int created;
+   private final Map<String, String> errors;
+
+   private ExtractArchiveResponse(int created, Map<String, String> errors) {
+      this.created = created;
+      this.errors = checkNotNull(errors, "errors");
+   }
+
+   /** 
+    * @return The number of files created.
+    */
+   public int getCreated() {
+      return created;
+   }
+
+   /** 
+    * @return a {@code Map<String, String>} containing each path that failed 
+    *         to be created and its corresponding error response.
+    */
+   public Map<String, String> getErrors() {
+      return errors;
+   }
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof ExtractArchiveResponse) {
+         ExtractArchiveResponse that = ExtractArchiveResponse.class.cast(object);
+         return equal(getCreated(), that.getCreated())
+               && equal(getErrors(), that.getErrors());
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(getCreated(), getErrors());
+   }
+
+   protected ToStringHelper string() {
+      return toStringHelper(this)
+            .add("created", getCreated())
+            .add("errors", getErrors());
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ObjectList.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ObjectList.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ObjectList.java
new file mode 100644
index 0000000..5bc3136
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/ObjectList.java
@@ -0,0 +1,57 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+
+import com.google.common.collect.ForwardingList;
+
+/**
+ * Represents a list of objects in a container.
+ * 
+ * @see Container 
+ * @see SwiftObject
+ * @see org.jclouds.openstack.swift.v1.features.ObjectApi#list()
+ */
+public class ObjectList extends ForwardingList<SwiftObject> {
+
+   public static ObjectList create(List<SwiftObject> objects, Container container) {
+      return new ObjectList(objects, container);
+   }
+
+   private final List<SwiftObject> objects;
+   private final Container container;
+
+   protected ObjectList(List<SwiftObject> objects, Container container) {
+      this.objects = checkNotNull(objects, "objects");
+      this.container = checkNotNull(container, "container");
+   }
+
+   /**
+    * @return the parent {@link Container} the objects reside in.
+    */
+   public Container getContainer() {
+      return container;
+   }
+
+   @Override
+   protected List<SwiftObject> delegate() {
+      return objects;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Segment.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Segment.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Segment.java
new file mode 100644
index 0000000..a5f064f
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Segment.java
@@ -0,0 +1,136 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.inject.Named;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Objects;
+
+/**
+ * Represents a single segment of a multi-part upload.
+ * 
+ * @see org.jclouds.openstack.swift.v1.features.StaticLargeObjectApi
+ */
+public class Segment {
+
+   private final String path;
+   private final String etag;
+   @Named("size_bytes")
+   private final long sizeBytes;
+
+   private Segment(String path, String etag, long sizeBytes) {
+      this.path = checkNotNull(path, "path");
+      this.etag = checkNotNull(etag, "etag of %s", path);
+      this.sizeBytes = checkNotNull(sizeBytes, "sizeBytes of %s", path);
+   }
+
+   /**
+    * @return The container and object name in the format: {@code <container-name>/<object-name>}
+    */
+   public String getPath() {
+      return path;
+   }
+
+   /**
+    * @return The ETag of the content of the segment object.
+    */
+   public String getETag() {
+      return etag;
+   }
+
+   /**
+    * @return The size of the segment object.
+    */
+   public long getSizeBytes() {
+      return sizeBytes;
+   }
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof Segment) {
+         Segment that = Segment.class.cast(object);
+         return equal(getPath(), that.getPath())
+               && equal(getETag(), that.getETag())
+               && equal(getSizeBytes(), that.getSizeBytes());
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(getPath(), getETag(), getSizeBytes());
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   protected ToStringHelper string() {
+      return toStringHelper(this)
+            .add("path", getPath())
+            .add("etag", getETag())
+            .add("sizeBytes", getSizeBytes());
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public static class Builder {
+      protected String path;
+      protected String etag;
+      protected long sizeBytes;
+
+      /**
+       * @see Segment#getPath()
+       */
+      public Builder path(String path) {
+         this.path = path;
+         return this;
+      }
+
+      /**
+       * @see Segment#getEtag()
+       */
+      public Builder etag(String etag) {
+         this.etag = etag;
+         return this;
+      }
+
+      /**
+       * @see Segment#getSizeBytes()
+       */
+      public Builder sizeBytes(long sizeBytes) {
+         this.sizeBytes = sizeBytes;
+         return this;
+      }
+
+      public Segment build() {
+         return new Segment(path, etag, sizeBytes);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/SwiftObject.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/SwiftObject.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/SwiftObject.java
new file mode 100644
index 0000000..2184ff2
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/SwiftObject.java
@@ -0,0 +1,267 @@
+/*
+ * 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.domain;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+import java.util.Date;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.jclouds.io.Payload;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Represents an object in OpenStack Object Storage.
+ * 
+ * 
+ * @see ObjectApi
+ */
+public class SwiftObject implements Comparable<SwiftObject> {
+
+   private final String name;
+   private final URI uri;
+   private final String etag;
+   private final Date lastModified;
+   private final Multimap<String, String> headers;
+   private final Map<String, String> metadata;
+   private final Payload payload;
+
+   protected SwiftObject(String name, URI uri, String etag, Date lastModified,
+         Multimap<String, String> headers, Map<String, String> metadata, Payload payload) {
+      this.name = checkNotNull(name, "name");
+      this.uri = checkNotNull(uri, "uri of %s", uri);
+      this.etag = checkNotNull(etag, "etag of %s", name).replace("\"", "");
+      this.lastModified = checkNotNull(lastModified, "lastModified of %s", name);
+      this.headers = headers == null ? ImmutableMultimap.<String, String> of() : checkNotNull(headers, "headers of %s", name);
+      this.metadata = metadata == null ? ImmutableMap.<String, String> of() : metadata;
+      this.payload = checkNotNull(payload, "payload of %s", name);
+   }
+
+   /**
+    * @return The name of this object.
+    */
+   public String getName() {
+      return name;
+   }
+
+   /**
+    * @return The {@link URI} for this object.
+    */
+   public URI getUri() {
+      return uri;
+   }
+
+   /**
+    * @return The ETag of the content of this object.
+    * @deprecated Please use {@link #getETag()} as this method will be removed in jclouds 1.8.
+    */
+   public String getEtag() {
+      return etag;
+   }
+
+   /**
+    * @return The ETag of the content of this object.
+    */
+   public String getETag() {
+      return etag;
+   }
+
+   /**
+    * @return The {@link Date} that this object was last modified.
+    */
+   public Date getLastModified() {
+      return lastModified;
+   }
+
+   /**
+    * @return The HTTP headers for this object.
+    */
+   public Multimap<String, String> getHeaders() {
+      return headers;
+   }
+
+   /**
+    * <h3>NOTE</h3>
+    * In current swift implementations, headers keys are lower-cased. This means
+    * characters such as turkish will probably not work out well.
+    * 
+    * @return a {@code Map<String, String>} containing this object's metadata. The map is empty
+    *         except in {@link ObjectApi#head(String) GetObjectMetadata} or
+    *         {@link ObjectApi#get(String) GetObject} commands.
+    */
+   public Map<String, String> getMetadata() {
+      return metadata;
+   }
+
+   /**
+    * <h3>NOTE</h3>
+    * The object will only have a {@link Payload#getInput()} when retrieved via the
+    * {@link ObjectApi#get(String) GetObject} command.
+    * 
+    * @return The {@link Payload} for this object.
+    */
+   public Payload getPayload() {
+      return payload;
+   }
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof SwiftObject) {
+         final SwiftObject that = SwiftObject.class.cast(object);
+         return equal(getName(), that.getName())
+               && equal(getUri(), that.getUri())
+               && equal(getETag(), that.getETag());
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(getName(), getUri(), getETag());
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   protected ToStringHelper string() {
+      return toStringHelper(this)
+            .add("name", getName())
+            .add("uri", getUri())
+            .add("etag", getETag())
+            .add("lastModified", getLastModified())
+            .add("metadata", getMetadata());
+   }
+
+   @Override
+   public int compareTo(SwiftObject that) {
+      if (that == null)
+         return 1;
+      if (this == that)
+         return 0;
+      return this.getName().compareTo(that.getName());
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Builder toBuilder() {
+      return builder().fromObject(this);
+   }
+
+   public static class Builder {
+      protected String name;
+      protected URI uri;
+      protected String etag;
+      protected Date lastModified;
+      protected Payload payload;
+      protected Multimap<String, String> headers = ImmutableMultimap.of();
+      protected Map<String, String> metadata = ImmutableMap.of();
+
+      /**
+       * @see SwiftObject#getName()
+       */
+      public Builder name(String name) {
+         this.name = checkNotNull(name, "name");
+         return this;
+      }
+
+      /**
+       * @see SwiftObject#getUri()
+       */
+      public Builder uri(URI uri) {
+         this.uri = checkNotNull(uri, "uri");
+         return this;
+      }
+
+      /**
+       * @see SwiftObject#getETag()
+       */
+      public Builder etag(String etag) {
+         this.etag = etag;
+         return this;
+      }
+
+      /**
+       * @see SwiftObject#getLastModified()
+       */
+      public Builder lastModified(Date lastModified) {
+         this.lastModified = lastModified;
+         return this;
+      }
+
+      /**
+       * @see SwiftObject#getPayload()
+       */
+      public Builder payload(Payload payload) {
+         this.payload = payload;
+         return this;
+      }
+
+      /**
+       * @see SwiftObject#getHeaders()
+       */
+      public Builder headers(Multimap<String, String> headers) {
+         this.headers = headers;
+         return this;
+      }
+
+      /**
+       * Will lower-case all metadata keys due to a swift implementation
+       * decision.
+       * 
+       * @see SwiftObject#getMetadata()
+       */
+      public Builder metadata(Map<String, String> metadata) {
+         ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String> builder();
+         for (Entry<String, String> entry : checkNotNull(metadata, "metadata").entrySet()) {
+            builder.put(entry.getKey().toLowerCase(), entry.getValue());
+         }
+         this.metadata = builder.build();
+         return this;
+      }
+
+      public SwiftObject build() {
+         return new SwiftObject(name, uri, etag, lastModified, headers, metadata, payload);
+      }
+
+      public Builder fromObject(SwiftObject from) {
+         return name(from.getName())
+               .uri(from.getUri())
+               .etag(from.getETag())
+               .lastModified(from.getLastModified())
+               .headers(from.getHeaders())
+               .metadata(from.getMetadata())
+               .payload(from.getPayload());
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java
new file mode 100644
index 0000000..c9d5b9d
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/AccountApi.java
@@ -0,0 +1,111 @@
+/*
+ * 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.features;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_TEMPORARY_URL_KEY;
+
+import java.util.Map;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.HEAD;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+
+import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindAccountMetadataToHeaders;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindRemoveAccountMetadataToHeaders;
+import org.jclouds.openstack.swift.v1.domain.Account;
+import org.jclouds.openstack.swift.v1.functions.ParseAccountFromHeaders;
+import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Provides access to the OpenStack Object Storage (Swift) Account API features.
+ *
+ * <p/>
+ * Account metadata prefixed with {@code X-Account-Meta-} will be converted
+ * appropriately using a binder/parser.
+ * <p/>
+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based
+ * on that feedback, minor changes to the interfaces may happen. This code will replace
+ * org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later.
+ *
+ *
+ * @see {@link Account}
+ */
+@Beta
+@RequestFilters(AuthenticateRequest.class)
+@Consumes(APPLICATION_JSON)
+public interface AccountApi {
+
+   /**
+    * Gets the {@link Account}.
+    *
+    * @return The {@link Account} object.
+    */
+   @Named("account:get")
+   @HEAD
+   @ResponseParser(ParseAccountFromHeaders.class)
+   Account get();
+
+   /**
+    * Creates or updates the {@link Account} metadata.
+    *
+    * @param metadata  the metadata to create or update.
+    *
+    * @return {@code true} if the metadata was successfully created or updated,
+    *         {@code false} if not.
+    */
+   @Named("account:updateMetadata")
+   @POST
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean updateMetadata(@BinderParam(BindAccountMetadataToHeaders.class) Map<String, String> metadata);
+
+   /**
+    * Replaces the temporary URL key for the {@link Account}.
+    *
+    * @param temporaryUrlKey  the temporary URL key to update.
+    *
+    * @return {@code true} if the temporary URL key was successfully updated,
+    *         {@code false} if not.
+    */
+   @Named("account:updateTemporaryUrlKey")
+   @POST
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean updateTemporaryUrlKey(@HeaderParam(ACCOUNT_TEMPORARY_URL_KEY) String temporaryUrlKey);
+
+   /**
+    * Deletes metadata from the {@link Account}.
+    *
+    * @param metadata  the metadata to delete.
+    *
+    * @return {@code true} if the metadata was successfully deleted,
+    *         {@code false} if not.
+    */
+   @Named("account:deleteMetadata")
+   @POST
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean deleteMetadata(@BinderParam(BindRemoveAccountMetadataToHeaders.class) Map<String, String> metadata);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/BulkApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/BulkApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/BulkApi.java
new file mode 100644
index 0000000..b62322f
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/BulkApi.java
@@ -0,0 +1,105 @@
+/*
+ * 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.features;
+
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.net.UrlEscapers.urlFragmentEscaper;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.io.Payload;
+import org.jclouds.io.Payloads;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.swift.v1.binders.SetPayload;
+import org.jclouds.openstack.swift.v1.domain.BulkDeleteResponse;
+import org.jclouds.openstack.swift.v1.domain.ExtractArchiveResponse;
+import org.jclouds.rest.Binder;
+import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.QueryParams;
+import org.jclouds.rest.annotations.RequestFilters;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Joiner;
+
+/**
+ * Provides access to the OpenStack Object Storage (Swift) Bulk API features.
+ * <p/>
+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based
+ * on that feedback, minor changes to the interfaces may happen. This code will replace
+ * org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later.
+ */
+@Beta
+@RequestFilters(AuthenticateRequest.class)
+@Consumes(APPLICATION_JSON)
+public interface BulkApi {
+
+   /**
+    * Extracts a tar archive at the path specified as {@code path}.
+    *
+    * @param path
+    *           the path to extract under.
+    * @param payload
+    *           the {@link Payload payload} archive.
+    * @param format
+    *           one of {@code tar}, {@code tar.gz}, or {@code tar.bz2}
+    *
+    * @return {@link BulkDeleteResponse#getErrors()} are empty on success.
+    */
+   @Named("bulk:extractArchive")
+   @PUT
+   @Path("/{path}")
+   ExtractArchiveResponse extractArchive(@PathParam("path") String path,
+         @BinderParam(SetPayload.class) Payload payload, @QueryParam("extract-archive") String format);
+
+   /**
+    * Deletes multiple objects or containers, if present.
+    *
+    * @param paths
+    *           format of {@code container}, for an empty container, or
+    *           {@code container/object} for an object.
+    *
+    * @return {@link BulkDeleteResponse#getErrors()} are empty on success.
+    */
+   @Named("bulk:delete")
+   @DELETE
+   @QueryParams(keys = "bulk-delete")
+   BulkDeleteResponse bulkDelete(@BinderParam(UrlEncodeAndJoinOnNewline.class) Iterable<String> paths);
+
+   // NOTE: this cannot be tested on MWS and is also brittle, as it relies on
+   // sending a body on DELETE.
+   // https://bugs.launchpad.net/swift/+bug/1232787
+   static class UrlEncodeAndJoinOnNewline implements Binder {
+      @SuppressWarnings("unchecked")
+      @Override
+      public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+         String encodedAndNewlineDelimited = Joiner.on('\n').join(
+               transform(Iterable.class.cast(input), urlFragmentEscaper().asFunction()));
+         Payload payload = Payloads.newStringPayload(encodedAndNewlineDelimited);
+         payload.getContentMetadata().setContentType(TEXT_PLAIN);
+         return (R) request.toBuilder().payload(payload).build();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java
new file mode 100644
index 0000000..4cbac0d
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java
@@ -0,0 +1,215 @@
+/*
+ * 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.features;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.jclouds.openstack.swift.v1.SwiftFallbacks.TrueOn404FalseOn409;
+
+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 org.jclouds.Fallbacks.EmptyFluentIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindContainerMetadataToHeaders;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindRemoveContainerMetadataToHeaders;
+import org.jclouds.openstack.swift.v1.domain.Container;
+import org.jclouds.openstack.swift.v1.functions.FalseOnAccepted;
+import org.jclouds.openstack.swift.v1.functions.ParseContainerFromHeaders;
+import org.jclouds.openstack.swift.v1.options.CreateContainerOptions;
+import org.jclouds.openstack.swift.v1.options.ListContainerOptions;
+import org.jclouds.openstack.swift.v1.options.UpdateContainerOptions;
+import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.FluentIterable;
+
+/**
+ * Provides access to the OpenStack Object Storage (Swift) Container API features.
+ * <p/>
+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based
+ * on that feedback, minor changes to the interfaces may happen. This code will replace
+ * {@code org.jclouds.openstack.swift.SwiftClient} in jclouds 2.0 and it is recommended you adopt it sooner than later.
+ */
+@Beta
+@RequestFilters(AuthenticateRequest.class)
+@Consumes(APPLICATION_JSON)
+public interface ContainerApi {
+
+   /**
+    * Lists up to 10,000 containers.
+    *
+    * <h3>NOTE</h3>
+    * This method returns a list of {@link Container} objects <b>without</b> metadata. To retrieve
+    * the {@link Container} metadata, use the {@link #get(String)} method.
+    * <p/>
+    *
+    * @return a list of {@link Container containers} ordered by name.
+    */
+   @Named("container:list")
+   @GET
+   @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
+   FluentIterable<Container> list();
+
+   /**
+    * Lists containers with the supplied {@link ListContainerOptions}.
+    *
+    * <h3>NOTE</h3>
+    * This method returns a list of {@link Container} objects <b>without</b> metadata. To retrieve
+    * the {@link Container} metadata, use the {@link #get(String)} method.
+    * <p/>
+    *
+    * @param options
+    *          the options to control the output list.
+    *
+    * @return a list of {@link Container containers} ordered by name.
+    */
+   @Named("container:list")
+   @GET
+   @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
+   FluentIterable<Container> list(ListContainerOptions options);
+
+   /**
+    * Creates a container, if not already present.
+    *
+    * @param containerName
+    *           corresponds to {@link Container#getName()}.
+    *
+    * @return {@code true} if the container was created, {@code false} if the container already existed.
+    */
+   @Named("container:create")
+   @PUT
+   @Path("/{containerName}")
+   @ResponseParser(FalseOnAccepted.class)
+   boolean create(@PathParam("containerName") String containerName);
+
+   /**
+    * Creates a container, if not already present.
+    *
+    * @param containerName
+    *           corresponds to {@link Container#getName()}.
+    * @param options
+    *           the options to use when creating the container.
+    *
+    * @return {@code true} if the container was created, {@code false} if the container already existed.
+    */
+   @Named("container:create")
+   @PUT
+   @Path("/{containerName}")
+   @ResponseParser(FalseOnAccepted.class)
+   boolean create(@PathParam("containerName") String containerName, CreateContainerOptions options);
+
+   /**
+    * Gets the {@link Container}.
+    *
+    * @param containerName
+    *           corresponds to {@link Container#getName()}.
+    *
+    * @return the {@link Container}, or {@code null} if not found.
+    */
+   @Named("container:get")
+   @HEAD
+   @Path("/{containerName}")
+   @ResponseParser(ParseContainerFromHeaders.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   Container get(@PathParam("containerName") String containerName);
+
+   /**
+    * Updates the {@link Container}.
+    *
+    * @param containerName
+    *           the container name corresponding to {@link Container#getName()}.
+    * @param options
+    *           the container options to update.
+    *
+    * @return {@code true} if the container metadata was successfully created or updated,
+    *         {@code false} if not.
+    */
+   @Named("container:update")
+   @POST
+   @Path("/{containerName}")
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean update(@PathParam("containerName") String containerName, UpdateContainerOptions options);
+
+   /**
+    * Creates or updates the {@link Container} metadata.
+    *
+    * @param containerName
+    *           the container name corresponding to {@link Container#getName()}.
+    * @param metadata
+    *           the container metadata to create or update.
+    *
+    * @return {@code true} if the container metadata was successfully created or updated,
+    *         {@code false} if not.
+    */
+   @Named("container:updateMetadata")
+   @POST
+   @Path("/{containerName}")
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean updateMetadata(@PathParam("containerName") String containerName,
+         @BinderParam(BindContainerMetadataToHeaders.class) Map<String, String> metadata);
+
+   /**
+    * Deletes {@link Container} metadata.
+    *
+    * @param containerName
+    *           corresponds to {@link Container#getName()}.
+    * @param metadata
+    *           the container metadata to delete.
+    *
+    * @return {@code true} if the container metadata was successfully deleted,
+    *         {@code false} if not.
+    */
+   @Named("container:deleteMetadata")
+   @POST
+   @Path("/{containerName}")
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean deleteMetadata(@PathParam("containerName") String containerName,
+         @BinderParam(BindRemoveContainerMetadataToHeaders.class) Map<String, String> metadata);
+
+   /**
+    * Deletes a {@link Container}, if empty.
+    *
+    * @param containerName
+    *           corresponds to {@link Container#getName()}.
+    *
+    * @return {@code true} if the container was deleted or not present.
+    *
+    * @throws IllegalStateException if the container was not empty.
+    */
+   @Named("container:deleteIfEmpty")
+   @DELETE
+   @Path("/{containerName}")
+   @Fallback(TrueOn404FalseOn409.class)
+   boolean deleteIfEmpty(@PathParam("containerName") String containerName) throws IllegalStateException;
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
new file mode 100644
index 0000000..4d4d603
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ObjectApi.java
@@ -0,0 +1,265 @@
+/*
+ * 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.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 org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.blobstore.BlobStoreFallbacks.FalseOnContainerNotFound;
+import org.jclouds.http.options.GetOptions;
+import org.jclouds.io.Payload;
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindObjectMetadataToHeaders;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindRemoveObjectMetadataToHeaders;
+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.functions.ETagHeader;
+import org.jclouds.openstack.swift.v1.functions.ParseObjectFromResponse;
+import org.jclouds.openstack.swift.v1.functions.ParseObjectListFromResponse;
+import org.jclouds.openstack.swift.v1.options.ListContainerOptions;
+import org.jclouds.openstack.swift.v1.options.PutOptions;
+import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.Headers;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Provides access to the OpenStack Object Storage (Swift) Object API features.
+ * <p/>
+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based
+ * on that feedback, minor changes to the interfaces may happen. This code will replace
+ * org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later.
+ */
+@Beta
+@RequestFilters(AuthenticateRequest.class)
+@Consumes(APPLICATION_JSON)
+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")
+   @GET
+   @ResponseParser(ParseObjectListFromResponse.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   ObjectList list();
+
+   /**
+    * 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
+    *           the {@link ListContainerOptions} for controlling the returned list.
+    *
+    * @return an {@link ObjectList} of {@link SwiftObject} ordered by name or {@code null}.
+    */
+   @Named("object:list")
+   @GET
+   @ResponseParser(ParseObjectListFromResponse.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   ObjectList list(ListContainerOptions options);
+
+   /**
+    * Creates or updates a {@link SwiftObject}.
+    *
+    * @param objectName
+    *           corresponds to {@link SwiftObject#getName()}.
+    * @param payload
+    *           corresponds to {@link SwiftObject#getPayload()}.
+    *
+    * @return {@link SwiftObject#getETag()} of the object.
+    */
+   @Named("object:put")
+   @PUT
+   @Path("/{objectName}")
+   @Headers(keys = EXPECT, values = "100-continue")
+   @ResponseParser(ETagHeader.class)
+   String put(@PathParam("objectName") String objectName, @BinderParam(SetPayload.class) Payload payload);
+
+   /**
+    * 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")
+   @PUT
+   @Path("/{objectName}")
+   @Headers(keys = EXPECT, values = "100-continue")
+   @ResponseParser(ETagHeader.class)
+   String put(@PathParam("objectName") String objectName, @BinderParam(SetPayload.class) Payload payload,
+         PutOptions options);
+
+   /**
+    * 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")
+   @HEAD
+   @Path("/{objectName}")
+   @ResponseParser(ParseObjectFromResponse.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   SwiftObject getWithoutBody(@PathParam("objectName") String objectName);
+
+   /**
+    * 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")
+   @GET
+   @Path("/{objectName}")
+   @ResponseParser(ParseObjectFromResponse.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   SwiftObject get(@PathParam("objectName") String objectName);
+
+   /**
+    * 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")
+   @GET
+   @Path("/{objectName}")
+   @ResponseParser(ParseObjectFromResponse.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Nullable
+   SwiftObject get(@PathParam("objectName") String objectName, GetOptions options);
+
+   /**
+    * 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,
+    *         {@code false} if not.
+    */
+   @Named("object:updateMetadata")
+   @POST
+   @Path("/{objectName}")
+   @Produces("")
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean updateMetadata(@PathParam("objectName") String objectName,
+         @BinderParam(BindObjectMetadataToHeaders.class) Map<String, String> 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,
+    *         {@code false} if not.
+    */
+   @Named("object:deleteMetadata")
+   @POST
+   @Path("/{objectName}")
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean deleteMetadata(@PathParam("objectName") String objectName,
+         @BinderParam(BindRemoveObjectMetadataToHeaders.class) Map<String, String> metadata);
+
+   /**
+    * Deletes an object, if present.
+    *
+    * @param objectName
+    *           corresponds to {@link SwiftObject#getName()}.
+    */
+   @Named("object:delete")
+   @DELETE
+   @Path("/{objectName}")
+   @Fallback(VoidOnNotFoundOr404.class)
+   void delete(@PathParam("objectName") String objectName);
+
+   /**
+    * Copies an object from one container to another.
+    *
+    * <h3>NOTE</h3>
+    * 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")
+   @PUT
+   @Path("/{destinationObject}")
+   @Headers(keys = OBJECT_COPY_FROM, values = "/{sourceContainer}/{sourceObject}")
+   @Fallback(FalseOnContainerNotFound.class)
+   boolean copy(@PathParam("destinationObject") String destinationObject,
+                @PathParam("sourceContainer") String sourceContainer,
+                @PathParam("sourceObject") String sourceObject);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApi.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApi.java
new file mode 100644
index 0000000..51ba30d
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/StaticLargeObjectApi.java
@@ -0,0 +1,90 @@
+/*
+ * 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.features;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.swift.v1.binders.BindMetadataToHeaders.BindObjectMetadataToHeaders;
+import org.jclouds.openstack.swift.v1.domain.Segment;
+import org.jclouds.openstack.swift.v1.functions.ETagHeader;
+import org.jclouds.rest.annotations.BinderParam;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.QueryParams;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Provides access to the OpenStack Object Storage (Swift) Static Large Object API features.
+ * <p/>
+ * This API is new to jclouds and hence is in Beta. That means we need people to use it and give us feedback. Based
+ * on that feedback, minor changes to the interfaces may happen. This code will replace
+ * org.jclouds.openstack.swift.SwiftClient in jclouds 2.0 and it is recommended you adopt it sooner than later.
+ */
+@Beta
+@RequestFilters(AuthenticateRequest.class)
+@Consumes(APPLICATION_JSON)
+@Path("/{objectName}")
+public interface StaticLargeObjectApi {
+
+   /**
+    * Creates or updates a static large object's manifest.
+    *
+    * @param objectName
+    *           corresponds to {@link SwiftObject#getName()}.
+    * @param segments
+    *           ordered parts which will be concatenated upon download.
+    * @param metadata
+    *           corresponds to {@link SwiftObject#getMetadata()}.
+    *
+    * @return {@link SwiftObject#getEtag()} of the object, which is the MD5
+    *         checksum of the concatenated ETag values of the {@code segments}.
+    */
+   @Named("staticLargeObject:replaceManifest")
+   @PUT
+   @ResponseParser(ETagHeader.class)
+   @QueryParams(keys = "multipart-manifest", values = "put")
+   String replaceManifest(@PathParam("objectName") String objectName,
+         @BinderParam(BindToJsonPayload.class) List<Segment> segments,
+         @BinderParam(BindObjectMetadataToHeaders.class) Map<String, String> metadata);
+
+   /**
+    * Deletes a static large object, if present, including all of its segments.
+    *
+    * @param objectName
+    *           corresponds to {@link SwiftObject#getName()}.
+    */
+   @Named("staticLargeObject:delete")
+   @DELETE
+   @Fallback(VoidOnNotFoundOr404.class)
+   @QueryParams(keys = "multipart-manifest", values = "delete")
+   void delete(@PathParam("objectName") String objectName);
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ETagHeader.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ETagHeader.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ETagHeader.java
new file mode 100644
index 0000000..25a749b
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ETagHeader.java
@@ -0,0 +1,32 @@
+/*
+ * 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.functions;
+
+import static com.google.common.net.HttpHeaders.ETAG;
+
+import org.jclouds.http.HttpResponse;
+
+import com.google.common.base.Function;
+
+public class ETagHeader implements Function<HttpResponse, String> {
+
+   @Override
+   public String apply(HttpResponse from) {
+      String etag = from.getFirstHeaderOrNull(ETAG);
+      return etag != null ? etag.replace("\"", "") : null;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/EntriesWithoutMetaPrefix.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/EntriesWithoutMetaPrefix.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/EntriesWithoutMetaPrefix.java
new file mode 100644
index 0000000..473da3e
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/EntriesWithoutMetaPrefix.java
@@ -0,0 +1,48 @@
+/*
+ * 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.functions;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Extracts entries whose keys start with {@code .*-Meta-}.
+ * 
+ * @param from
+ *           a {@link Multimap} containing the prefixed headers.
+ * 
+ * @return the extracted metadata without the prefixed keys. 
+ */
+public enum EntriesWithoutMetaPrefix implements Function<Multimap<String, String>, Map<String, String>> {
+   INSTANCE;
+
+   @Override
+   public Map<String, String> apply(Multimap<String, String> arg0) {
+      ImmutableMap.Builder<String, String> metadata = ImmutableMap.builder();
+      for (Entry<String, String> header : arg0.entries()) {
+         int index = header.getKey().indexOf("-Meta-");
+         if (index != -1) {
+            metadata.put(header.getKey().substring(index + 6), header.getValue());
+         }
+      }
+      return metadata.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java
new file mode 100644
index 0000000..68da524
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java
@@ -0,0 +1,30 @@
+/*
+ * 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.functions;
+
+import org.jclouds.http.HttpResponse;
+
+import com.google.common.base.Function;
+
+/** Returns {@code false} on HTTP 202 {@code Accepted}. */
+public class FalseOnAccepted implements Function<HttpResponse, Boolean> {
+
+   @Override
+   public Boolean apply(HttpResponse from) {
+      return from.getStatusCode() == 202 ? false : true;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/MetadataFromHeaders.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/MetadataFromHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/MetadataFromHeaders.java
new file mode 100644
index 0000000..cdd49f1
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/MetadataFromHeaders.java
@@ -0,0 +1,31 @@
+/*
+ * 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.functions;
+
+import java.util.Map;
+
+import org.jclouds.http.HttpResponse;
+
+import com.google.common.base.Function;
+
+/** Extracts metadata entries from http response headers. */
+public class MetadataFromHeaders implements Function<HttpResponse, Map<String, String>> {
+   @Override
+   public Map<String, String> apply(HttpResponse from) {
+      return EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseAccountFromHeaders.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseAccountFromHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseAccountFromHeaders.java
new file mode 100644
index 0000000..9debe67
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseAccountFromHeaders.java
@@ -0,0 +1,38 @@
+/*
+ * 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.functions;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_BYTES_USED;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_CONTAINER_COUNT;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_OBJECT_COUNT;
+
+import org.jclouds.http.HttpResponse;
+import org.jclouds.openstack.swift.v1.domain.Account;
+
+import com.google.common.base.Function;
+
+public class ParseAccountFromHeaders implements Function<HttpResponse, Account> {
+
+   @Override
+   public Account apply(HttpResponse from) {
+      return Account.builder()
+            .bytesUsed(Long.parseLong(from.getFirstHeaderOrNull(ACCOUNT_BYTES_USED)))
+            .containerCount(Long.parseLong(from.getFirstHeaderOrNull(ACCOUNT_CONTAINER_COUNT)))
+            .objectCount(Long.parseLong(from.getFirstHeaderOrNull(ACCOUNT_OBJECT_COUNT)))
+            .metadata(EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders())).build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/34663f3c/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java
----------------------------------------------------------------------
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java
new file mode 100644
index 0000000..d616351
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java
@@ -0,0 +1,54 @@
+/*
+ * 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.functions;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_ACL_ANYBODY_READ;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_BYTES_USED;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_OBJECT_COUNT;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_READ;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.openstack.swift.v1.domain.Container;
+import org.jclouds.rest.InvocationContext;
+import org.jclouds.rest.internal.GeneratedHttpRequest;
+
+import com.google.common.base.Function;
+
+public class ParseContainerFromHeaders implements Function<HttpResponse, Container>,
+      InvocationContext<ParseContainerFromHeaders> {
+
+   String name;
+
+   @Override
+   public Container apply(HttpResponse from) {
+      Container c = 
+      Container.builder()
+            .name(name)
+            .bytesUsed(Long.parseLong(from.getFirstHeaderOrNull(CONTAINER_BYTES_USED)))
+            .objectCount(Long.parseLong(from.getFirstHeaderOrNull(CONTAINER_OBJECT_COUNT)))
+            .anybodyRead(CONTAINER_ACL_ANYBODY_READ.equals(from.getFirstHeaderOrNull(CONTAINER_READ)))
+            .metadata(EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders())).build();
+      return c;
+   }
+
+   @Override
+   public ParseContainerFromHeaders setContext(HttpRequest request) {
+      this.name = GeneratedHttpRequest.class.cast(request).getInvocation().getArgs().get(0).toString();
+      return this;
+   }
+}