Updated Branches:
  refs/heads/master f46c86a97 -> f5e4012f9

JCLOUDS-306. refactored and completed swift ContainerApi


Project: 
http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/commit/f5e4012f
Tree: 
http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/tree/f5e4012f
Diff: 
http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/diff/f5e4012f

Branch: refs/heads/master
Commit: f5e4012f9e88b6cedfda5b61c760ef363ad7e772
Parents: f867518
Author: Adrian Cole <[email protected]>
Authored: Fri Sep 27 21:38:00 2013 -0700
Committer: Adrian Cole <[email protected]>
Committed: Sat Sep 28 07:12:01 2013 -0700

----------------------------------------------------------------------
 .../openstack/swift/v1/domain/Container.java    |  52 +++-
 .../swift/v1/features/ContainerApi.java         | 142 +++++++++-
 .../swift/v1/functions/FalseOnAccepted.java     |  30 ++
 .../v1/functions/ParseContainerFromHeaders.java |  46 +++
 .../swift/v1/handlers/SwiftErrorHandler.java    |   3 +
 .../swift/v1/options/ListContainersOptions.java |  77 -----
 .../swift/v1/features/ContainerApiLiveTest.java |  90 +++++-
 .../swift/v1/features/ContainerApiMockTest.java | 280 +++++++++++++++++++
 .../v1/options/ListContainersOptionsTest.java   |  92 ------
 .../swift/v1/parse/ParseContainerListTest.java  |  56 ----
 .../src/test/resources/container_list.json      |   4 -
 11 files changed, 623 insertions(+), 249 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java
index 612aa80..cfee278 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/domain/Container.java
@@ -21,9 +21,14 @@ import static com.google.common.base.Objects.toStringHelper;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import java.beans.ConstructorProperties;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.jclouds.openstack.swift.v1.features.ContainerApi;
 
 import com.google.common.base.Objects;
 import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableMap;
 
 /**
  * @see <a
@@ -35,12 +40,14 @@ public class Container implements Comparable<Container> {
    private final String name;
    private final long objectCount;
    private final long bytesUsed;
+   private final Map<String, String> metadata;
 
-   @ConstructorProperties({ "name", "count", "bytes" })
-   protected Container(String name, long objectCount, long bytesUsed) {
+   @ConstructorProperties({ "name", "count", "bytes", "metadata" })
+   protected Container(String name, long objectCount, long bytesUsed, 
Map<String, String> metadata) {
       this.name = checkNotNull(name, "name");
       this.objectCount = objectCount;
       this.bytesUsed = bytesUsed;
+      this.metadata = metadata == null ? ImmutableMap.<String, String> of() : 
metadata;
    }
 
    public String name() {
@@ -55,6 +62,18 @@ public class Container implements Comparable<Container> {
       return bytesUsed;
    }
 
+   /**
+    * Empty except in {@link ContainerApi#get(String) GetContainer} commands.
+    * 
+    * <h3>Note</h3>
+    * 
+    * In current swift implementations, headers keys are lower-cased. This 
means
+    * characters such as turkish will probably not work out well.
+    */
+   public Map<String, String> metadata() {
+      return metadata;
+   }
+
    @Override
    public boolean equals(Object object) {
       if (this == object) {
@@ -64,7 +83,8 @@ public class Container implements Comparable<Container> {
          final Container that = Container.class.cast(object);
          return equal(name(), that.name()) //
                && equal(objectCount(), that.objectCount()) //
-               && equal(bytesUsed(), that.bytesUsed());
+               && equal(bytesUsed(), that.bytesUsed()) //
+               && equal(metadata(), that.metadata());
       } else {
          return false;
       }
@@ -72,7 +92,7 @@ public class Container implements Comparable<Container> {
 
    @Override
    public int hashCode() {
-      return Objects.hashCode(name(), objectCount(), bytesUsed());
+      return Objects.hashCode(name(), objectCount(), bytesUsed(), metadata());
    }
 
    @Override
@@ -84,7 +104,8 @@ public class Container implements Comparable<Container> {
       return toStringHelper("") //
             .add("name", name()) //
             .add("objectCount", objectCount()) //
-            .add("bytesUsed", bytesUsed());
+            .add("bytesUsed", bytesUsed()) //
+            .add("metadata", metadata());
    }
 
    @Override
@@ -108,6 +129,7 @@ public class Container implements Comparable<Container> {
       protected String name;
       protected long objectCount;
       protected long bytesUsed;
+      protected Map<String, String> metadata = ImmutableMap.of();
 
       /**
        * @see Container#name()
@@ -133,14 +155,30 @@ public class Container implements Comparable<Container> {
          return this;
       }
 
+      /**
+       * Will lower-case all metadata keys due to a swift implementation
+       * decision.
+       * 
+       * @see Container#metadata()
+       */
+      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 Container build() {
-         return new Container(name, objectCount, bytesUsed);
+         return new Container(name, objectCount, bytesUsed, metadata);
       }
 
       public Builder fromContainer(Container from) {
          return name(from.name()) //
                .objectCount(from.objectCount()) //
-               .bytesUsed(from.bytesUsed());
+               .bytesUsed(from.bytesUsed()) //
+               .metadata(from.metadata());
       }
    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java
index 5696d77..a1b7ae7 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/features/ContainerApi.java
@@ -16,17 +16,36 @@
  */
 package org.jclouds.openstack.swift.v1.features;
 
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+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.core.MediaType;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+
 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.options.ListContainersOptions;
+import org.jclouds.openstack.swift.v1.functions.FalseOnAccepted;
+import org.jclouds.openstack.swift.v1.functions.ParseContainerFromHeaders;
+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 com.google.common.collect.FluentIterable;
 
@@ -40,31 +59,132 @@ import com.google.common.collect.FluentIterable;
  *      >api doc</a>
  */
 @RequestFilters(AuthenticateRequest.class)
+@Consumes(APPLICATION_JSON)
 public interface ContainerApi {
 
    /**
-    * @see #list(ListContainersOptions)
+    * Lists up to 10,000 containers.
+    * 
+    * @return a list of existing storage containers ordered by name.
     */
+   @Named("ListContainers")
    @GET
-   @Consumes(MediaType.APPLICATION_JSON)
    @QueryParams(keys = "format", values = "json")
    @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
    @Path("/")
-   FluentIterable<? extends Container> list();
+   FluentIterable<Container> listFirstPage();
 
    /**
-    * retrieve a list of existing storage containers ordered by name. The sort 
order for the name is
-    * based on a binary comparison, a single built-in collating sequence that 
compares string data
-    * using SQLite's memcmp() function, regardless of text encoding.
+    * Lists up to 10,000 containers, starting at {@code marker}
+    * 
+    * @param marker
+    *           lexicographic position to start list.
     * 
-    * @param options
     * @return a list of existing storage containers ordered by name.
     */
+   @Named("ListContainers")
    @GET
-   @Consumes(MediaType.APPLICATION_JSON)
    @QueryParams(keys = "format", values = "json")
    @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
    @Path("/")
-   FluentIterable<? extends Container> list(ListContainersOptions options);
+   FluentIterable<Container> listAt(@QueryParam("marker") String marker);
+
+   /**
+    * Creates a container, if not already present.
+    * 
+    * @param containerName
+    *           corresponds to {@link Container#name()}.
+    * @see <a
+    *      
href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/create-container.html";>
+    *      Create Container API</a>
+    * 
+    * @return <code>false</code> if the container already existed.
+    */
+   @Named("CreateContainer")
+   @PUT
+   @ResponseParser(FalseOnAccepted.class)
+   @Path("/{containerName}")
+   boolean createIfAbsent(@PathParam("containerName") String containerName);
+
+   /**
+    * Gets the {@link Container}.
+    * 
+    * @param containerName
+    *           corresponds to {@link Container#name()}.
+    * @return the Container or null, if not found.
+    * 
+    * @see <a
+    *      
href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/retrieve-Container-metadata.html";>
+    *      Get Container Metadata API</a>
+    */
+   @Named("GetContainer")
+   @HEAD
+   @ResponseParser(ParseContainerFromHeaders.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   @Path("/{containerName}")
+   @Nullable
+   Container get(@PathParam("containerName") String containerName);
 
+   /**
+    * Creates or updates the Container metadata.
+    * 
+    * @param containerName
+    *           corresponds to {@link Container#name()}.
+    * @param metadata
+    *           the Container metadata to create or update.
+    * 
+    * @see <a
+    *      
href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/Update_Container_Metadata-d1e1900.html";>
+    *      Create or Update Container Metadata API</a>
+    * 
+    * @return <code>true</code> if the Container Metadata was successfully
+    *         created or updated, false if not.
+    */
+   @Named("UpdateContainerMetadata")
+   @POST
+   @Fallback(FalseOnNotFoundOr404.class)
+   @Path("/{containerName}")
+   boolean updateMetadata(@PathParam("containerName") String containerName,
+         @BinderParam(BindContainerMetadataToHeaders.class) Map<String, 
String> metadata);
+
+   /**
+    * Deletes Container metadata.
+    * 
+    * @param containerName
+    *           corresponds to {@link Container#name()}.
+    * @param metadata
+    *           the Container metadata to delete.
+    * 
+    * @return <code>true</code> if the Container Metadata was successfully
+    *         deleted, false if not.
+    * 
+    * @see <a
+    *      
href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/delete-container-metadata.html";>
+    *      Delete Container Metadata API</a>
+    */
+   @Named("DeleteContainerMetadata")
+   @POST
+   @Fallback(FalseOnNotFoundOr404.class)
+   @Path("/{containerName}")
+   boolean deleteMetadata(@PathParam("containerName") String containerName,
+         @BinderParam(BindRemoveContainerMetadataToHeaders.class) Map<String, 
String> metadata);
+
+   /**
+    * Deletes a container, if empty.
+    * 
+    * @param containerName
+    *           corresponds to {@link Container#name()}.
+    * @see <a
+    *      
href="http://docs.openstack.org/api/openstack-object-storage/1.0/content/delete-container.html";>
+    *      Delete Container API</a>
+    * 
+    * @return <code>false</code> if the container was not present.
+    * @throws IllegalStateException
+    *            when the container wasn't empty.
+    */
+   @Named("DeleteContainer")
+   @DELETE
+   @Fallback(FalseOnNotFoundOr404.class)
+   @Path("/{containerName}")
+   boolean deleteIfEmpty(@PathParam("containerName") String containerName) 
throws IllegalStateException;
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/FalseOnAccepted.java
new file mode 100644
index 0000000..68da524
--- /dev/null
+++ 
b/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/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.java
new file mode 100644
index 0000000..5e66202
--- /dev/null
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/functions/ParseContainerFromHeaders.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.functions;
+
+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> {
+
+   private String name;
+
+   @Override
+   public Container apply(HttpResponse from) {
+      return Container.builder() //
+            .name(name) //
+            
.bytesUsed(Long.parseLong(from.getFirstHeaderOrNull("X-Container-Bytes-Used"))) 
//
+            
.objectCount(Integer.parseInt(from.getFirstHeaderOrNull("X-Container-Object-Count")))
 //
+            
.metadata(EntriesWithoutMetaPrefix.INSTANCE.apply(from.getHeaders())).build();
+   }
+
+   @Override
+   public ParseContainerFromHeaders setContext(HttpRequest request) {
+      this.name = 
GeneratedHttpRequest.class.cast(request).getInvocation().getArgs().get(0).toString();
+      return this;
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java
index e71daa6..f94f4a8 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/handlers/SwiftErrorHandler.java
@@ -65,6 +65,9 @@ public class SwiftErrorHandler implements HttpErrorHandler {
                }
             }
             break;
+         case 409:
+            exception = new IllegalStateException(exception.getMessage(), 
exception);
+            break;
       }
       command.setException(exception);
    }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainersOptions.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainersOptions.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainersOptions.java
deleted file mode 100644
index 2efa90b..0000000
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/options/ListContainersOptions.java
+++ /dev/null
@@ -1,77 +0,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.
- */
-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;
-
-/**
- * Contains options supported in the REST API for the GET container operation. 
<h2>
- */
-public class ListContainersOptions extends BaseHttpRequestOptions {
-   public static final ListContainersOptions NONE = new 
ListContainersOptions();
-
-   /**
-    * Given a string value x, return object names greater in value than the 
specified marker.
-    */
-   public ListContainersOptions marker(String marker) {
-      queryParameters.put("marker", checkNotNull(marker, "marker"));
-      return this;
-   }
-
-   public String getMarker() {
-      return getFirstQueryOrNull("marker");
-   }
-
-   /**
-    * For an integer value n, limits the number of results to n values.
-    */
-   public ListContainersOptions 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;
-   }
-
-   public int getLimit() {
-      String val = getFirstQueryOrNull("limit");
-      return val != null ? Integer.valueOf(val) : 10000;
-   }
-
-
-   public static class Builder {
-
-      /**
-       * @see ListContainersOptions#marker(String)
-       */
-      public static ListContainersOptions marker(String marker) {
-         ListContainersOptions options = new ListContainersOptions();
-         return options.marker(marker);
-      }
-
-      /**
-       * @see ListContainersOptions#limit(int)
-       */
-      public static ListContainersOptions limit(int limit) {
-         ListContainersOptions options = new ListContainersOptions();
-         return options.limit(limit);
-      }
-
-   }
-}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java
 
b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java
index 93b248e..973d363 100644
--- 
a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java
+++ 
b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiLiveTest.java
@@ -16,14 +16,22 @@
  */
 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.domain.Container;
 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.FluentIterable;
+import com.google.common.collect.ImmutableMap;
 
 /**
  * @author Adrian Cole
@@ -31,11 +39,13 @@ import com.google.common.collect.FluentIterable;
 @Test(groups = "live", testName = "ContainerApiLiveTest")
 public class ContainerApiLiveTest extends BaseSwiftApiLiveTest {
 
+   private String name = getClass().getSimpleName();
+
    @Test
-   public void testListContainers() throws Exception {
+   public void list() throws Exception {
       for (String regionId : api.configuredRegions()) {
          ContainerApi containerApi = api.containerApiInRegion(regionId);
-         FluentIterable<? extends Container> response = containerApi.list();
+         FluentIterable<Container> response = containerApi.listFirstPage();
          assertNotNull(response);
          for (Container container : response) {
             assertNotNull(container.name());
@@ -44,4 +54,80 @@ public class ContainerApiLiveTest extends 
BaseSwiftApiLiveTest {
          }
       }
    }
+
+   public void get() throws Exception {
+      for (String regionId : api.configuredRegions()) {
+         Container container = api.containerApiInRegion(regionId).get(name);
+         assertEquals(container.name(), name);
+         assertTrue(container.objectCount() == 0);
+         assertTrue(container.bytesUsed() == 0);
+      }
+   }
+
+   public void listAt() throws Exception {
+      String lexicographicallyBeforeName = name.substring(0, name.length() - 
1);
+      for (String regionId : api.configuredRegions()) {
+         Container container = 
api.containerApiInRegion(regionId).listAt(lexicographicallyBeforeName).get(0);
+         assertEquals(container.name(), name);
+         assertTrue(container.objectCount() == 0);
+         assertTrue(container.bytesUsed() == 0);
+      }
+   }
+
+   public void updateMetadata() throws Exception {
+      for (String regionId : api.configuredRegions()) {
+         ContainerApi containerApi = api.containerApiInRegion(regionId);
+
+         Map<String, String> meta = ImmutableMap.of("MyAdd1", "foo", "MyAdd2", 
"bar");
+
+         assertTrue(containerApi.updateMetadata(name, meta));
+
+         containerHasMetadata(containerApi, name, meta);
+      }
+   }
+
+   public void deleteMetadata() throws Exception {
+      for (String regionId : api.configuredRegions()) {
+         ContainerApi containerApi = api.containerApiInRegion(regionId);
+
+         Map<String, String> meta = ImmutableMap.of("MyDelete1", "foo", 
"MyDelete2", "bar");
+
+         assertTrue(containerApi.updateMetadata(name, meta));
+         containerHasMetadata(containerApi, name, meta);
+
+         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.metadata().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.metadata().get(entry.getKey().toLowerCase()), 
entry.getValue(), //
+               container + " didn't have metadata: " + entry);
+      }
+   }
+
+   @Override
+   @BeforeClass(groups = "live")
+   public void setup() {
+      super.setup();
+      for (String regionId : api.configuredRegions()) {
+         api.containerApiInRegion(regionId).createIfAbsent(name);
+      }
+   }
+
+   @Override
+   @AfterClass(groups = "live")
+   public void tearDown() {
+      for (String regionId : api.configuredRegions()) {
+         api.containerApiInRegion(regionId).deleteIfEmpty(name);
+      }
+      super.tearDown();
+   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java
 
b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java
new file mode 100644
index 0000000..d54530e
--- /dev/null
+++ 
b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ContainerApiMockTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.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.BaseSwiftMockTest;
+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;
+
+/**
+ * @author Adrian Cole
+ */
+@Test
+public class ContainerApiMockTest extends BaseSwiftMockTest {
+
+   String containerList = "" //
+         + "[\n" //
+         + "  {\"name\":\"test_container_1\", \"count\":2, \"bytes\":78},\n" //
+         + "  {\"name\":\"test_container_2\", \"count\":1, \"bytes\":17}\n" //
+         + "]";
+
+   public void listFirstPage() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(new MockResponse().setBody(containerList));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         ImmutableList<Container> containers = 
api.containerApiInRegion("DFW").listFirstPage().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);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens 
HTTP/1.1");
+         assertEquals(server.takeRequest().getRequestLine(),
+               "GET 
/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/?format=json HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void listAt() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(new MockResponse().setBody(containerList));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         ImmutableList<Container> containers = 
api.containerApiInRegion("DFW").listAt("test").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);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens 
HTTP/1.1");
+         assertEquals(server.takeRequest().getRequestLine(),
+               "GET 
/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/?format=json&marker=test 
HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void createIfAbsent() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(new MockResponse().setResponseCode(201));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         
assertTrue(api.containerApiInRegion("DFW").createIfAbsent("myContainer"));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens 
HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "PUT 
/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void alreadyCreated() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(new MockResponse().setResponseCode(202));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         
assertFalse(api.containerApiInRegion("DFW").createIfAbsent("myContainer"));
+
+         assertEquals(server.getRequestCount(), 2);
+         assertEquals(server.takeRequest().getRequestLine(), "POST /tokens 
HTTP/1.1");
+         RecordedRequest deleteRequest = server.takeRequest();
+         assertEquals(deleteRequest.getRequestLine(),
+               "PUT 
/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   /** upper-cases first char, and lower-cases rest!! **/
+   public void getKnowingServerMessesWithMetadataKeyCaseFormat() throws 
Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(containerResponse() //
+            // note silly casing
+            .addHeader("X-Container-Meta-Apiname", "swift") //
+            .addHeader("X-Container-Meta-Apiversion", "v1.1"));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         Container container = 
api.containerApiInRegion("DFW").get("myContainer");
+         assertEquals(container.name(), "myContainer");
+         assertEquals(container.objectCount(), 42l);
+         assertEquals(container.bytesUsed(), 323479l);
+         for (Entry<String, String> entry : container.metadata().entrySet()) {
+            
assertEquals(container.metadata().get(entry.getKey().toLowerCase()), 
entry.getValue());
+         }
+
+         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/myContainer HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void updateMetadata() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(containerResponse() //
+            .addHeader("X-Container-Meta-ApiName", "swift") //
+            .addHeader("X-Container-Meta-ApiVersion", "v1.1"));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         
assertTrue(api.containerApiInRegion("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("x-container-meta-" + 
entry.getKey().toLowerCase()), entry.getValue());
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteMetadata() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(containerResponse());
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         
assertTrue(api.containerApiInRegion("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("x-remove-container-meta-" + 
key.toLowerCase()), "ignored");
+         }
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteIfEmpty() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(new MockResponse().setResponseCode(204));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         
assertTrue(api.containerApiInRegion("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 alreadyDeleted() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(new MockResponse().setResponseCode(404));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         
assertFalse(api.containerApiInRegion("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();
+      }
+   }
+
+   @Test(expectedExceptions = IllegalStateException.class)
+   public void deleteWhenNotEmpty() throws Exception {
+      MockWebServer server = mockSwiftServer();
+      server.enqueue(new MockResponse().setBody(access));
+      server.enqueue(new MockResponse().setResponseCode(409));
+
+      try {
+         SwiftApi api = swiftApi(server.getUrl("/").toString());
+         api.containerApiInRegion("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 final static Map<String, String> metadata = 
ImmutableMap.of("ApiName", "swift", "ApiVersion", "v1.1");
+
+   public static MockResponse containerResponse() {
+      return new MockResponse() //
+            .addHeader("X-Container-Object-Count", "42") //
+            .addHeader("X-Container-Bytes-Used", "323479");
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/ListContainersOptionsTest.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/ListContainersOptionsTest.java
 
b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/ListContainersOptionsTest.java
deleted file mode 100644
index df9d4d6..0000000
--- 
a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/options/ListContainersOptionsTest.java
+++ /dev/null
@@ -1,92 +0,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.
- */
-package org.jclouds.openstack.swift.v1.options;
-
-import static 
org.jclouds.openstack.swift.v1.options.ListContainersOptions.Builder.limit;
-import static 
org.jclouds.openstack.swift.v1.options.ListContainersOptions.Builder.marker;
-import static org.testng.Assert.assertEquals;
-
-import com.google.common.collect.ImmutableList;
-
-import org.jclouds.http.options.HttpRequestOptions;
-import org.testng.annotations.Test;
-
-/**
- * @author Adrian Cole
- */
-@Test(testName = "ListContainersOptionsTest")
-public class ListContainersOptionsTest {
-
-   @Test
-   public void testAssignability() {
-      assert 
HttpRequestOptions.class.isAssignableFrom(ListContainersOptions.class);
-      assert !String.class.isAssignableFrom(ListContainersOptions.class);
-   }
-   @Test
-   public void testNoOptionsQueryString() {
-      HttpRequestOptions options = new ListContainersOptions();
-      assertEquals(options.buildQueryParameters().size(), 0);
-   }
-
-   @Test
-   public void testMarker() {
-      ListContainersOptions options = new ListContainersOptions();
-      options.marker("test");
-      assertEquals(options.buildQueryParameters().get("marker"), 
ImmutableList.of("test"));
-   }
-
-   @Test
-   public void testNullMarker() {
-      ListContainersOptions options = new ListContainersOptions();
-      assertEquals(options.buildQueryParameters().get("marker"), 
ImmutableList.of());
-   }
-
-   @Test
-   public void testMarkerStatic() {
-      ListContainersOptions options = marker("test");
-      assertEquals(options.buildQueryParameters().get("marker"), 
ImmutableList.of("test"));
-   }
-
-   @Test(expectedExceptions = NullPointerException.class)
-   public void testMarkerNPE() {
-      marker(null);
-   }
-
-   @Test
-   public void testLimit() {
-      ListContainersOptions options = new ListContainersOptions();
-      options.limit(1000);
-      assertEquals(options.buildQueryParameters().get("limit"), 
ImmutableList.of("1000"));
-   }
-
-   @Test
-   public void testNullLimit() {
-      ListContainersOptions options = new ListContainersOptions();
-      assertEquals(options.buildQueryParameters().get("limit"), 
ImmutableList.of());
-   }
-
-   @Test
-   public void testLimitStatic() {
-      ListContainersOptions options = limit(1000);
-      assertEquals(options.buildQueryParameters().get("limit"), 
ImmutableList.of("1000"));
-   }
-
-   @Test(expectedExceptions = IllegalStateException.class)
-   public void testLimitNegative() {
-      limit(-1);
-   }
-}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/parse/ParseContainerListTest.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/parse/ParseContainerListTest.java
 
b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/parse/ParseContainerListTest.java
deleted file mode 100644
index 4101fb2..0000000
--- 
a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/parse/ParseContainerListTest.java
+++ /dev/null
@@ -1,56 +0,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.
- */
-package org.jclouds.openstack.swift.v1.parse;
-
-import java.util.Set;
-
-import javax.ws.rs.Consumes;
-import javax.ws.rs.core.MediaType;
-
-import org.jclouds.json.BaseSetParserTest;
-import org.jclouds.openstack.swift.v1.domain.Container;
-import org.testng.annotations.Test;
-
-import com.google.common.collect.ImmutableSet;
-
-/**
- * @author Adrian Cole
- */
-@Test(groups = "unit", testName = "ParseContainerListTest")
-public class ParseContainerListTest extends BaseSetParserTest<Container> {
-
-   @Override
-   public String resource() {
-      return "/container_list.json";
-   }
-
-   @Override
-   @Consumes(MediaType.APPLICATION_JSON)
-   public Set<Container> expected() {
-      return ImmutableSet
-            .of(Container.builder()
-                  .name("test_container_1")
-                  .objectCount(2)
-                  .bytesUsed(78)
-                  .build(),
-                Container.builder()
-                  .name("test_container_2")
-                  .objectCount(1)
-                  .bytesUsed(17)
-                  .build());
-   }
-}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/f5e4012f/openstack-swift/src/test/resources/container_list.json
----------------------------------------------------------------------
diff --git a/openstack-swift/src/test/resources/container_list.json 
b/openstack-swift/src/test/resources/container_list.json
deleted file mode 100644
index f7d9b9b..0000000
--- a/openstack-swift/src/test/resources/container_list.json
+++ /dev/null
@@ -1,4 +0,0 @@
-[
-  {"name":"test_container_1", "count":2, "bytes":78},
-  {"name":"test_container_2", "count":1, "bytes":17}
-]
\ No newline at end of file

Reply via email to