You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ga...@apache.org on 2015/06/30 04:28:17 UTC

[1/6] jclouds git commit: JCLOUDS-930: Implement prefix for LocalBlobStore.

Repository: jclouds
Updated Branches:
  refs/heads/master 61b06329d -> 1cb082297


JCLOUDS-930: Implement prefix for LocalBlobStore.

Implements prefix support for the local blob store. The patch allows
for correctly parsing prefixes that may not terminate with a delimiter
(i.e. foo with delimiter "/" and a key foobar/key, should return
foobar/ as the common path) and ones that do (i.e. foo/).

NOTE: there is a small change in behavior in this patch.
LocalBlobStore used to return the common prefixes without the
delimiter character ("/"). However, other providers do include the
delimiter (I checked S3 and Google Cloud Storage) and LocalBlobStore
should include it as well.


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

Branch: refs/heads/master
Commit: 8677ffcb213f4182eee03039431a9562c2eee16a
Parents: db4e4e5
Author: Timur Alperovich <ti...@gmail.com>
Authored: Wed Jun 24 16:52:28 2015 -0700
Committer: Timur Alperovich <ti...@gmail.com>
Committed: Mon Jun 29 17:31:55 2015 -0700

----------------------------------------------------------------------
 .../blobstore/config/LocalBlobStore.java        |  68 +++++++---
 .../internal/BaseContainerLiveTest.java         |   4 +-
 .../strategy/internal/ListContainerTest.java    | 128 +++++++++++++++++++
 3 files changed, 180 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/8677ffcb/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
index 831e83a..a244a4b 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
@@ -216,6 +216,9 @@ public final class LocalBlobStore implements BlobStore {
     */
    @Override
    public PageSet<? extends StorageMetadata> list(final String containerName, ListContainerOptions options) {
+      if (options.getDir() != null && options.getPrefix() != null) {
+         throw new IllegalArgumentException("Cannot set both prefix and directory");
+      }
 
       // Check if the container exists
       if (!storageStrategy.containerExists(containerName))
@@ -254,6 +257,8 @@ public final class LocalBlobStore implements BlobStore {
 
          if (options.getDir() != null && !options.getDir().isEmpty()) {
             contents = filterDirectory(contents, options);
+         } else if (options.getPrefix() != null) {
+            contents = filterPrefix(contents, options);
          } else if (!options.isRecursive()) {
             contents = extractCommonPrefixes(contents, storageStrategy.getSeparator(), null);
          }
@@ -324,7 +329,22 @@ public final class LocalBlobStore implements BlobStore {
       }));
 
       if (!options.isRecursive()) {
-         return extractCommonPrefixes(contents, storageStrategy.getSeparator(), prefix);
+         return extractCommonPrefixes(contents, storageStrategy.getSeparator(), dirPrefix);
+      }
+
+      return contents;
+   }
+
+   private SortedSet<StorageMetadata> filterPrefix(SortedSet<StorageMetadata> contents, final ListContainerOptions
+                                                   options) {
+      contents = newTreeSet(filter(contents, new Predicate<StorageMetadata>() {
+         public boolean apply(StorageMetadata o) {
+            return o != null && o.getName().replace(File.separatorChar, '/').startsWith(options.getPrefix());
+         }
+      }));
+
+      if (!options.isRecursive()) {
+         return extractCommonPrefixes(contents, storageStrategy.getSeparator(), options.getPrefix());
       }
 
       return contents;
@@ -341,12 +361,9 @@ public final class LocalBlobStore implements BlobStore {
       for (String o : commonPrefixes) {
          MutableStorageMetadata md = new MutableStorageMetadataImpl();
          md.setType(StorageType.RELATIVE_PATH);
-         if (prefix != null && !prefix.isEmpty()) {
-            if (!prefix.endsWith(delimiter)) {
-               o = prefix + delimiter + o;
-            } else {
-               o = prefix + o;
-            }
+
+         if (prefix != null) {
+            o = prefix + o;
          }
          md.setName(o);
          contents.add(md);
@@ -450,12 +467,23 @@ public final class LocalBlobStore implements BlobStore {
       }
 
       public boolean apply(StorageMetadata metadata) {
+         String name = metadata.getName();
+         if (metadata.getType() == StorageType.RELATIVE_PATH) {
+            // For directories, we need to make sure to include the separator character, as that is what the common
+            // prefix will include.
+            name += '/';
+         }
          if (prefix == null || prefix.isEmpty())
-            return metadata.getName().indexOf(delimiter) == -1;
-         // ensure we don't accidentally append twice
-         String toMatch = prefix.endsWith("/") ? prefix : prefix + delimiter;
-         if (metadata.getName().startsWith(toMatch)) {
-            String unprefixedName = metadata.getName().replaceFirst(Pattern.quote(toMatch), "");
+            return name.indexOf(delimiter) == -1;
+         String prefixMatch;
+         if (prefix.endsWith(delimiter)) {
+            prefixMatch = "^" + Pattern.quote(prefix) + ".*";
+         } else {
+            // We should correctly match strings like "foobar/" where the prefix is only "foo"
+            prefixMatch = "^" + Pattern.quote(prefix) + ".*" + Pattern.quote(delimiter) + ".*";
+         }
+         if (name.matches(prefixMatch)) {
+            String unprefixedName = name.replaceFirst(prefix, "");
             if (unprefixedName.equals("")) {
                // we are the prefix in this case, return false
                return false;
@@ -479,16 +507,18 @@ public final class LocalBlobStore implements BlobStore {
       public String apply(StorageMetadata metadata) {
          String working = metadata.getName();
          if (prefix != null) {
-            // ensure we don't accidentally append twice
-            String toMatch = prefix.endsWith("/") ? prefix : prefix + delimiter;
-            if (working.startsWith(toMatch)) {
-               working = working.replaceFirst(Pattern.quote(toMatch), "");
+            if (working.startsWith(prefix)) {
+               working = working.replaceFirst(Pattern.quote(prefix), "");
+            } else {
+               return NO_PREFIX;
             }
          }
-         if (working.contains(delimiter)) {
-            return working.substring(0, working.indexOf(delimiter));
+         if (working.indexOf(delimiter) >= 0) {
+            // include the delimiter in the result
+            return working.substring(0, working.indexOf(delimiter) + 1);
+         } else {
+            return NO_PREFIX;
          }
-         return NO_PREFIX;
       }
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8677ffcb/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java
index 7c620b2..b11b5dd 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java
@@ -17,6 +17,8 @@
 package org.jclouds.blobstore.integration.internal;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
+
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.jclouds.blobstore.options.CreateContainerOptions.Builder.publicRead;
 import static org.jclouds.util.Predicates2.retry;
 import static org.testng.Assert.assertEquals;
@@ -141,7 +143,7 @@ public class BaseContainerLiveTest extends BaseBlobStoreIntegrationTest {
          names.add(sm.getName());
       }
 
-      assertEquals(expectedSet, names);
+      assertThat(names).containsExactlyElementsOf(expectedSet);
    }
 
    private void runCreateContainerInLocation(String payload, Location nonDefault) throws InterruptedException,

http://git-wip-us.apache.org/repos/asf/jclouds/blob/8677ffcb/blobstore/src/test/java/org/jclouds/blobstore/strategy/internal/ListContainerTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/strategy/internal/ListContainerTest.java b/blobstore/src/test/java/org/jclouds/blobstore/strategy/internal/ListContainerTest.java
new file mode 100644
index 0000000..435df30
--- /dev/null
+++ b/blobstore/src/test/java/org/jclouds/blobstore/strategy/internal/ListContainerTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.blobstore.strategy.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.google.common.collect.Iterables;
+import com.google.inject.Injector;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.blobstore.domain.StorageType;
+import org.jclouds.blobstore.options.ListContainerOptions;
+import org.jclouds.util.Closeables2;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+@Test(testName = "PrefixTest", singleThreaded = true)
+public class ListContainerTest {
+   private BlobStore blobStore;
+   private ConcatenateContainerLists concatter;
+
+   @BeforeClass
+   public void setupBlobStore() {
+      Injector injector = ContextBuilder.newBuilder("transient").buildInjector();
+      blobStore = injector.getInstance(BlobStore.class);
+      concatter = injector.getInstance(ConcatenateContainerLists.class);
+   }
+
+   @AfterClass
+   public void closeBlobSore() {
+      if (blobStore != null) {
+         Closeables2.closeQuietly(blobStore.getContext());
+      }
+   }
+
+   public void testListWithPrefix() {
+      String containerName = "prefix";
+      String prefix = "foo";
+      blobStore.createContainerInLocation(null, containerName);
+      blobStore.putBlob(containerName, blobStore.blobBuilder(prefix).payload("").build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "bar").payload("").build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "baz").payload("").build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder("bar").payload("").build());
+
+      Iterable<? extends StorageMetadata> results = concatter.execute(containerName,
+            ListContainerOptions.Builder.prefix(prefix).recursive());
+      assertThat(results).hasSize(3);
+      assertThat(Iterables.get(results, 0).getName()).isEqualTo(prefix);
+      assertThat(Iterables.get(results, 0).getType()).isEqualTo(StorageType.BLOB);
+      assertThat(Iterables.get(results, 1).getName()).isEqualTo(prefix + "bar");
+      assertThat(Iterables.get(results, 1).getType()).isEqualTo(StorageType.BLOB);
+      assertThat(Iterables.get(results, 2).getName()).isEqualTo(prefix + "baz");
+      assertThat(Iterables.get(results, 2).getType()).isEqualTo(StorageType.BLOB);
+   }
+
+   public void testListWithPrefixAndSeparator() {
+      String containerName = "prefixWithSeparator";
+      String prefix = "foo";
+      blobStore.createContainerInLocation(null, containerName);
+      blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "/object").payload("").build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "bar/object").payload("")
+            .build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "baz/object").payload("")
+            .build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder("bar/object").payload("").build());
+
+      Iterable<? extends StorageMetadata> results = concatter.execute(containerName,
+            ListContainerOptions.Builder.prefix(prefix));
+      assertThat(Iterables.size(results)).isEqualTo(3);
+      assertThat(Iterables.get(results, 0).getType()).isEqualTo(StorageType.RELATIVE_PATH);
+      assertThat(Iterables.get(results, 0).getName()).isEqualTo(prefix + "/");
+      assertThat(Iterables.get(results, 1).getName()).isEqualTo(prefix + "bar/");
+      assertThat(Iterables.get(results, 1).getType()).isEqualTo(StorageType.RELATIVE_PATH);
+      assertThat(Iterables.get(results, 2).getName()).isEqualTo(prefix + "baz/");
+      assertThat(Iterables.get(results, 2).getType()).isEqualTo(StorageType.RELATIVE_PATH);
+   }
+
+   public void testListRecursivePrefix() {
+      String containerName = "testListRecursive";
+      String prefix = "foo";
+      blobStore.createContainerInLocation(null, containerName);
+      blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "/object").payload("").build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "bar/object").payload("")
+            .build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "baz/object").payload("")
+            .build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder("bar/object").payload("").build());
+
+      Iterable<? extends StorageMetadata> results = concatter.execute(containerName,
+            ListContainerOptions.Builder.prefix(prefix).recursive());
+      assertThat(results).hasSize(3);
+      assertThat(Iterables.get(results, 0).getType()).isEqualTo(StorageType.BLOB);
+      assertThat(Iterables.get(results, 0).getName()).isEqualTo(prefix + "/object");
+      assertThat(Iterables.get(results, 1).getType()).isEqualTo(StorageType.BLOB);
+      assertThat(Iterables.get(results, 1).getName()).isEqualTo(prefix + "bar/object");
+      assertThat(Iterables.get(results, 2).getType()).isEqualTo(StorageType.BLOB);
+      assertThat(Iterables.get(results, 2).getName()).isEqualTo(prefix + "baz/object");
+   }
+
+   public void testListDirectory() {
+      String containerName = "testListDir";
+      String directory = "dir";
+      blobStore.createContainerInLocation(null, containerName);
+      blobStore.createDirectory(containerName, directory);
+      blobStore.putBlob(containerName, blobStore.blobBuilder(directory + "/foo").payload("").build());
+      blobStore.putBlob(containerName, blobStore.blobBuilder(directory + "/bar").payload("").build());
+      Iterable<? extends StorageMetadata> results = concatter.execute(containerName, ListContainerOptions.NONE);
+      assertThat(results).hasSize(1);
+      assertThat(Iterables.get(results, 0).getName()).isEqualTo(directory + '/');
+   }
+}


[5/6] jclouds git commit: JCLOUDS-930: Plumb prefix support down to S3.

Posted by ga...@apache.org.
JCLOUDS-930: Plumb prefix support down to S3.

Plumbs the ListContainerOptions.prefix setting down to the S3 API.


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

Branch: refs/heads/master
Commit: c409c19ff3faafa77d72e96335d50bb984bf3773
Parents: 497a013
Author: Timur Alperovich <ti...@gmail.com>
Authored: Thu Jun 25 14:08:02 2015 -0700
Committer: Timur Alperovich <ti...@gmail.com>
Committed: Mon Jun 29 17:31:56 2015 -0700

----------------------------------------------------------------------
 .../s3/blobstore/functions/ContainerToBucketListOptions.java  | 7 +++++++
 .../jclouds/s3/blobstore/integration/S3ContainerLiveTest.java | 6 ------
 2 files changed, 7 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/c409c19f/apis/s3/src/main/java/org/jclouds/s3/blobstore/functions/ContainerToBucketListOptions.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/main/java/org/jclouds/s3/blobstore/functions/ContainerToBucketListOptions.java b/apis/s3/src/main/java/org/jclouds/s3/blobstore/functions/ContainerToBucketListOptions.java
index afe8b81..fd8e924 100644
--- a/apis/s3/src/main/java/org/jclouds/s3/blobstore/functions/ContainerToBucketListOptions.java
+++ b/apis/s3/src/main/java/org/jclouds/s3/blobstore/functions/ContainerToBucketListOptions.java
@@ -30,6 +30,10 @@ public class ContainerToBucketListOptions implements
          Function<ListContainerOptions, ListBucketOptions> {
    public ListBucketOptions apply(ListContainerOptions from) {
       checkNotNull(from, "set options to instance NONE instead of passing null");
+      if (from.getPrefix() != null && from.getDir() != null) {
+         throw new IllegalArgumentException("Cannot set both directory and prefix options");
+      }
+
       ListBucketOptions httpOptions = new ListBucketOptions();
       if (!from.isRecursive()) {
          httpOptions.delimiter("/");
@@ -40,6 +44,9 @@ public class ContainerToBucketListOptions implements
             path = path + "/";
          httpOptions.withPrefix(path);
       }
+      if (from.getPrefix() != null) {
+         httpOptions.withPrefix(from.getPrefix());
+      }
       if (from.getMarker() != null) {
          httpOptions.afterMarker(from.getMarker());
       }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/c409c19f/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java b/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java
index bbab3a4..7c90ea6 100644
--- a/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java
+++ b/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java
@@ -18,7 +18,6 @@ package org.jclouds.s3.blobstore.integration;
 
 import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
 import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
-import org.testng.SkipException;
 import org.testng.annotations.Test;
 
 @Test(groups = "live", testName = "S3ContainerLiveTest")
@@ -28,9 +27,4 @@ public class S3ContainerLiveTest extends BaseContainerLiveTest {
       provider = "s3";
       BaseBlobStoreIntegrationTest.SANITY_CHECK_RETURNED_BUCKET_NAME = true;
    }
-
-   @Override
-   public void testContainerListWithPrefix() {
-      throw new SkipException("Prefix option has not been plumbed down to S3");
-   }
 }


[4/6] jclouds git commit: JCLOUDS-930: Plumb prefix support down to Azure.

Posted by ga...@apache.org.
JCLOUDS-930: Plumb prefix support down to Azure.

Plumbs support for the prefix query option in the Azure provider. This
option is not compatible with the "directory" list option and an
exception is thrown if both are set.


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

Branch: refs/heads/master
Commit: 497a013c8a395da18a713363209d12b26aeedcae
Parents: 8677ffc
Author: Timur Alperovich <ti...@gmail.com>
Authored: Thu Jun 25 13:38:39 2015 -0700
Committer: Timur Alperovich <ti...@gmail.com>
Committed: Mon Jun 29 17:31:56 2015 -0700

----------------------------------------------------------------------
 .../blobstore/functions/ListOptionsToListBlobsOptions.java     | 6 ++++++
 .../blobstore/integration/AzureBlobContainerLiveTest.java      | 6 ------
 2 files changed, 6 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/497a013c/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/functions/ListOptionsToListBlobsOptions.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/functions/ListOptionsToListBlobsOptions.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/functions/ListOptionsToListBlobsOptions.java
index 4bb1534..063a91a 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/functions/ListOptionsToListBlobsOptions.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/functions/ListOptionsToListBlobsOptions.java
@@ -30,6 +30,9 @@ public class ListOptionsToListBlobsOptions implements
          Function<ListContainerOptions, ListBlobsOptions> {
    public ListBlobsOptions apply(ListContainerOptions from) {
       checkNotNull(from, "set options to instance NONE instead of passing null");
+      if (from.getDir() != null && from.getPrefix() != null) {
+         throw new IllegalArgumentException("Cannot set both directory and prefix");
+      }
       ListBlobsOptions httpOptions = new ListBlobsOptions();
       if (!from.isRecursive()) {
          httpOptions.delimiter("/");
@@ -37,6 +40,9 @@ public class ListOptionsToListBlobsOptions implements
       if (from.getDir() != null) {
          httpOptions.prefix(from.getDir().endsWith("/") ? from.getDir() : from.getDir() + "/");
       }
+      if (from.getPrefix() != null) {
+         httpOptions.prefix(from.getPrefix());
+      }
       if (from.getMarker() != null) {
          httpOptions.marker(from.getMarker());
       }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/497a013c/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java
index c5a83c2..5bfdcf2 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java
@@ -17,7 +17,6 @@
 package org.jclouds.azureblob.blobstore.integration;
 
 import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
-import org.testng.SkipException;
 import org.testng.annotations.Test;
 
 @Test(groups = { "live" })
@@ -25,9 +24,4 @@ public class AzureBlobContainerLiveTest extends BaseContainerLiveTest {
    public AzureBlobContainerLiveTest() {
       provider = "azureblob";
    }
-
-   @Override
-   public void testContainerListWithPrefix() {
-      throw new SkipException("Prefix option has not been plumbed down to Azure");
-   }
 }


[3/6] jclouds git commit: Factor out the common prefixes method.

Posted by ga...@apache.org.
Factor out the common prefixes method.

Refactoring the code in the list() method to create a common
extractCommonPrefixes method.


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

Branch: refs/heads/master
Commit: db4e4e5ba72e6cbd281130df80e970dd9eca4d23
Parents: 7c7d2e2
Author: Timur Alperovich <ti...@gmail.com>
Authored: Wed Jun 24 16:42:21 2015 -0700
Committer: Timur Alperovich <ti...@gmail.com>
Committed: Mon Jun 29 17:31:55 2015 -0700

----------------------------------------------------------------------
 .../blobstore/config/LocalBlobStore.java        | 84 ++++++++++++--------
 1 file changed, 49 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/db4e4e5b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
index f85af2a..831e83a 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/config/LocalBlobStore.java
@@ -250,43 +250,12 @@ public final class LocalBlobStore implements BlobStore {
             }));
 
       String marker = null;
-      String prefix;
       if (options != null) {
-         prefix = options.getDir();
-         if (prefix != null && !prefix.isEmpty()) {
-            final String dirPrefix = prefix.endsWith("/") ?
-                    prefix :
-                    prefix + "/";
-            contents = newTreeSet(filter(contents, new Predicate<StorageMetadata>() {
-               public boolean apply(StorageMetadata o) {
-                  return o != null
-                        && o.getName().replace(File.separatorChar, '/').startsWith(dirPrefix)
-                        && !o.getName().replace(File.separatorChar, '/').equals(dirPrefix);
-               }
-            }));
-         }
 
-         if (!options.isRecursive()) {
-            String delimiter = storageStrategy.getSeparator();
-            SortedSet<String> commonPrefixes = newTreeSet(
-                  transform(contents, new CommonPrefixes(prefix, delimiter)));
-            commonPrefixes.remove(CommonPrefixes.NO_PREFIX);
-
-            contents = newTreeSet(filter(contents, new DelimiterFilter(prefix, delimiter)));
-
-            for (String o : commonPrefixes) {
-               MutableStorageMetadata md = new MutableStorageMetadataImpl();
-               md.setType(StorageType.RELATIVE_PATH);
-               if (prefix != null && !prefix.isEmpty()) {
-                  if (!prefix.endsWith(delimiter)) {
-                     o = prefix + delimiter + o;
-                  } else {
-                     o = prefix + o;
-                  }
-               }
-               md.setName(o);
-               contents.add(md);
-            }
+         if (options.getDir() != null && !options.getDir().isEmpty()) {
+            contents = filterDirectory(contents, options);
+         } else if (!options.isRecursive()) {
+            contents = extractCommonPrefixes(contents, storageStrategy.getSeparator(), null);
          }
 
          if (options.getMarker() != null) {
@@ -340,6 +309,51 @@ public final class LocalBlobStore implements BlobStore {
       return new PageSetImpl<StorageMetadata>(contents, marker);
    }
 
+   private SortedSet<StorageMetadata> filterDirectory(SortedSet<StorageMetadata> contents, ListContainerOptions
+           options) {
+      String prefix = options.getDir();
+      final String dirPrefix = prefix.endsWith("/") ?
+              prefix :
+              prefix + "/";
+      contents = newTreeSet(filter(contents, new Predicate<StorageMetadata>() {
+         public boolean apply(StorageMetadata o) {
+            return o != null
+                    && o.getName().replace(File.separatorChar, '/').startsWith(dirPrefix)
+                    && !o.getName().replace(File.separatorChar, '/').equals(dirPrefix);
+         }
+      }));
+
+      if (!options.isRecursive()) {
+         return extractCommonPrefixes(contents, storageStrategy.getSeparator(), prefix);
+      }
+
+      return contents;
+   }
+
+   private SortedSet<StorageMetadata> extractCommonPrefixes(SortedSet<StorageMetadata> contents, String delimiter,
+                                                            String prefix) {
+      SortedSet<String> commonPrefixes = newTreeSet(
+              transform(contents, new CommonPrefixes(prefix, delimiter)));
+      commonPrefixes.remove(CommonPrefixes.NO_PREFIX);
+
+      contents = newTreeSet(filter(contents, new DelimiterFilter(prefix, delimiter)));
+
+      for (String o : commonPrefixes) {
+         MutableStorageMetadata md = new MutableStorageMetadataImpl();
+         md.setType(StorageType.RELATIVE_PATH);
+         if (prefix != null && !prefix.isEmpty()) {
+            if (!prefix.endsWith(delimiter)) {
+               o = prefix + delimiter + o;
+            } else {
+               o = prefix + o;
+            }
+         }
+         md.setName(o);
+         contents.add(md);
+      }
+      return contents;
+   }
+
    private ContainerNotFoundException cnfe(final String name) {
       return new ContainerNotFoundException(name, String.format(
             "container %s not in %s", name,


[2/6] jclouds git commit: JCLOUDS-930: Add the prefix option.

Posted by ga...@apache.org.
JCLOUDS-930: Add the prefix option.

Add a prefix option to the ListContainerOptions class.


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

Branch: refs/heads/master
Commit: 7c7d2e2df6bcd9ca57f4d0807d1615afafec79ca
Parents: 61b0632
Author: Timur Alperovich <ti...@gmail.com>
Authored: Wed Jun 24 16:09:34 2015 -0700
Committer: Timur Alperovich <ti...@gmail.com>
Committed: Mon Jun 29 17:31:55 2015 -0700

----------------------------------------------------------------------
 .../integration/AtmosContainerLiveTest.java     |  6 ++++
 .../integration/SwiftContainerLiveTest.java     |  6 ++++
 .../integration/S3ContainerLiveTest.java        |  6 ++++
 .../integration/SwiftContainerLiveTest.java     |  6 ++++
 .../blobstore/options/ListContainerOptions.java | 36 ++++++++++++++++++--
 .../internal/BaseContainerLiveTest.java         | 31 +++++++++++++++++
 .../integration/AzureBlobContainerLiveTest.java |  6 ++++
 7 files changed, 95 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c7d2e2d/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosContainerLiveTest.java b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosContainerLiveTest.java
index 8c45388..e76f580 100644
--- a/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosContainerLiveTest.java
+++ b/apis/atmos/src/test/java/org/jclouds/atmos/blobstore/integration/AtmosContainerLiveTest.java
@@ -17,6 +17,7 @@
 package org.jclouds.atmos.blobstore.integration;
 
 import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
+import org.testng.SkipException;
 import org.testng.annotations.Test;
 
 @Test(groups = { "live" })
@@ -24,4 +25,9 @@ public class AtmosContainerLiveTest extends BaseContainerLiveTest {
    public AtmosContainerLiveTest() {
       provider = "atmos";
    }
+
+   @Override
+   public void testContainerListWithPrefix() {
+      throw new SkipException("Prefix option has not been plumbed down to Atmos");
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c7d2e2d/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
index 9bd85d6..c2fd800 100644
--- 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
@@ -21,6 +21,7 @@ import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CRED
 import java.util.Properties;
 
 import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
+import org.testng.SkipException;
 import org.testng.annotations.Test;
 
 @Test(groups = "live", testName = "SwiftContainerLiveTest")
@@ -36,4 +37,9 @@ public class SwiftContainerLiveTest extends BaseContainerLiveTest {
       setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
       return props;
    }
+
+   @Override
+   public void testContainerListWithPrefix() {
+      throw new SkipException("Prefix option has not been plumbed down to Swift");
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c7d2e2d/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java b/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java
index 7c90ea6..bbab3a4 100644
--- a/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java
+++ b/apis/s3/src/test/java/org/jclouds/s3/blobstore/integration/S3ContainerLiveTest.java
@@ -18,6 +18,7 @@ package org.jclouds.s3.blobstore.integration;
 
 import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
 import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
+import org.testng.SkipException;
 import org.testng.annotations.Test;
 
 @Test(groups = "live", testName = "S3ContainerLiveTest")
@@ -27,4 +28,9 @@ public class S3ContainerLiveTest extends BaseContainerLiveTest {
       provider = "s3";
       BaseBlobStoreIntegrationTest.SANITY_CHECK_RETURNED_BUCKET_NAME = true;
    }
+
+   @Override
+   public void testContainerListWithPrefix() {
+      throw new SkipException("Prefix option has not been plumbed down to S3");
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c7d2e2d/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftContainerLiveTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftContainerLiveTest.java
index d83d35b..e668f17 100644
--- a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftContainerLiveTest.java
+++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftContainerLiveTest.java
@@ -22,6 +22,7 @@ import java.util.Properties;
 
 import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
 import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties;
+import org.testng.SkipException;
 import org.testng.annotations.Test;
 
 @Test(groups = "live", testName = "SwiftContainerLiveTest" )
@@ -52,4 +53,9 @@ public class SwiftContainerLiveTest extends BaseContainerLiveTest {
             IOException {
       super.testPublicAccessInNonDefaultLocationWithBigBlob();
    }
+
+   @Override
+   public void testContainerListWithPrefix() {
+      throw new SkipException("Prefix option has not been plumbed down to Swift");
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c7d2e2d/blobstore/src/main/java/org/jclouds/blobstore/options/ListContainerOptions.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/options/ListContainerOptions.java b/blobstore/src/main/java/org/jclouds/blobstore/options/ListContainerOptions.java
index d366e2a..e3d3f1c 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/options/ListContainerOptions.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/options/ListContainerOptions.java
@@ -40,6 +40,7 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
             new ListContainerOptions());
 
    private String dir;
+   private String prefix;
    private boolean recursive;
    private boolean detailed;
 
@@ -47,11 +48,12 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
    }
 
    ListContainerOptions(Integer maxKeys, String marker, String dir, boolean recursive,
-            boolean detailed) {
+            boolean detailed, String prefix) {
       super(maxKeys, marker);
       this.dir = dir;
       this.recursive = recursive;
       this.detailed = detailed;
+      this.prefix = prefix;
    }
 
    public static class ImmutableListContainerOptions extends ListContainerOptions {
@@ -108,6 +110,16 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
       }
 
       @Override
+      public String getPrefix() {
+         return delegate.getPrefix();
+      }
+
+      @Override
+      public ListContainerOptions prefix(String prefix) {
+         throw new UnsupportedOperationException();
+      }
+
+      @Override
       public ListContainerOptions clone() {
          return delegate.clone();
       }
@@ -131,6 +143,10 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
       return detailed;
    }
 
+   public String getPrefix() {
+      return prefix;
+   }
+
    /**
     * This will list the contents of a virtual or real directory path.
     * 
@@ -176,6 +192,14 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
       return this;
    }
 
+   /**
+    * Only list keys that start with the supplied prefix
+    */
+   public ListContainerOptions prefix(String prefix) {
+      this.prefix = prefix;
+      return this;
+   }
+
    public static class Builder {
 
       /**
@@ -217,11 +241,19 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
          ListContainerOptions options = new ListContainerOptions();
          return options.withDetails();
       }
+
+      /**
+       * @see ListContainerOptions#prefix(String)
+       */
+      public static ListContainerOptions prefix(String prefix) {
+         ListContainerOptions options = new ListContainerOptions();
+         return options.prefix(prefix);
+      }
    }
 
    @Override
    public ListContainerOptions clone() {
-      return new ListContainerOptions(getMaxResults(), getMarker(), dir, recursive, detailed);
+      return new ListContainerOptions(getMaxResults(), getMarker(), dir, recursive, detailed, prefix);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c7d2e2d/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java
index e03e29b..7c620b2 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerLiveTest.java
@@ -26,13 +26,17 @@ import static org.testng.Assert.assertTrue;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.logging.Logger;
 
 import com.google.common.net.HostAndPort;
 import org.jclouds.blobstore.BlobStore;
 import org.jclouds.blobstore.domain.BlobMetadata;
+import org.jclouds.blobstore.domain.PageSet;
 import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.blobstore.options.ListContainerOptions;
 import org.jclouds.domain.Location;
 import org.jclouds.javax.annotation.Nullable;
 import org.jclouds.predicates.SocketOpen;
@@ -43,6 +47,7 @@ import org.testng.annotations.Test;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 
@@ -113,6 +118,32 @@ public class BaseContainerLiveTest extends BaseBlobStoreIntegrationTest {
       runCreateContainerInLocation(payload, nonDefault);
    }
 
+   @Test(groups = "live", dependsOnMethods = "testPublicAccess")
+   public void testContainerListWithPrefix() throws InterruptedException {
+      final String containerName = getContainerName();
+      BlobStore blobStore = view.getBlobStore();
+      String prefix = "blob";
+      try {
+         blobStore.putBlob(containerName, blobStore.blobBuilder(prefix).payload("").build());
+         blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "foo").payload("").build());
+         blobStore.putBlob(containerName, blobStore.blobBuilder(prefix + "bar").payload("").build());
+         blobStore.putBlob(containerName, blobStore.blobBuilder("foo").payload("").build());
+         checkEqualNames(ImmutableSet.of(prefix, prefix + "foo", prefix + "bar"),
+               blobStore.list(containerName, ListContainerOptions.Builder.prefix(prefix)));
+      } finally {
+         returnContainer(containerName);
+      }
+   }
+
+   private void checkEqualNames(ImmutableSet<String> expectedSet, PageSet<? extends StorageMetadata> results) {
+      Set<String> names = new HashSet<String>();
+      for (StorageMetadata sm : results) {
+         names.add(sm.getName());
+      }
+
+      assertEquals(expectedSet, names);
+   }
+
    private void runCreateContainerInLocation(String payload, Location nonDefault) throws InterruptedException,
             IOException {
       String blobName = "hello";

http://git-wip-us.apache.org/repos/asf/jclouds/blob/7c7d2e2d/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java
index 5bfdcf2..c5a83c2 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/blobstore/integration/AzureBlobContainerLiveTest.java
@@ -17,6 +17,7 @@
 package org.jclouds.azureblob.blobstore.integration;
 
 import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
+import org.testng.SkipException;
 import org.testng.annotations.Test;
 
 @Test(groups = { "live" })
@@ -24,4 +25,9 @@ public class AzureBlobContainerLiveTest extends BaseContainerLiveTest {
    public AzureBlobContainerLiveTest() {
       provider = "azureblob";
    }
+
+   @Override
+   public void testContainerListWithPrefix() {
+      throw new SkipException("Prefix option has not been plumbed down to Azure");
+   }
 }


[6/6] jclouds git commit: JCLOUDS-930: Add prefix option to OpenStack Swift.

Posted by ga...@apache.org.
JCLOUDS-930: Add prefix option to OpenStack Swift.

Plumbs the prefix option to the openstack-swift provider. In the
process, the support for the recursive option is modified to avoid
setting the _path_ parameter, but rather use the delimiter if required
(setting the delimiter is sufficient for a non-recursive listing).


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

Branch: refs/heads/master
Commit: 1cb082297218532510f32a77a36915a97a09c192
Parents: c409c19
Author: Timur Alperovich <ti...@gmail.com>
Authored: Thu Jun 25 15:16:52 2015 -0700
Committer: Timur Alperovich <ti...@gmail.com>
Committed: Mon Jun 29 17:31:56 2015 -0700

----------------------------------------------------------------------
 .../v1/blobstore/functions/ToListContainerOptions.java | 13 ++++++++-----
 .../blobstore/integration/SwiftContainerLiveTest.java  |  6 ------
 2 files changed, 8 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/1cb08229/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
index 2650f5b..fbfb0c9 100644
--- 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
@@ -28,12 +28,12 @@ public class ToListContainerOptions implements
    @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.getPrefix() != null) {
+         throw new IllegalArgumentException("Cannot set both directory and prefix");
       }
-      if ((from.getDir() == null) && (!from.isRecursive())) {
-         options.path("");
+      org.jclouds.openstack.swift.v1.options.ListContainerOptions options = new org.jclouds.openstack.swift.v1.options.ListContainerOptions();
+      if (from.getDir() == null && !from.isRecursive()) {
+         options.delimiter('/');
       }
       if ((from.getDir() != null) && (from.isRecursive())) {
          options.prefix(from.getDir().endsWith("/") ? from.getDir() : from.getDir() + "/");
@@ -41,6 +41,9 @@ public class ToListContainerOptions implements
       if ((from.getDir() != null) && (!from.isRecursive())) {
          options.path(from.getDir());
       }
+      if (from.getPrefix() != null) {
+         options.prefix(from.getPrefix());
+      }
       if (from.getMarker() != null) {
          options.marker(from.getMarker());
       }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1cb08229/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
index c2fd800..9bd85d6 100644
--- 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
@@ -21,7 +21,6 @@ import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CRED
 import java.util.Properties;
 
 import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
-import org.testng.SkipException;
 import org.testng.annotations.Test;
 
 @Test(groups = "live", testName = "SwiftContainerLiveTest")
@@ -37,9 +36,4 @@ public class SwiftContainerLiveTest extends BaseContainerLiveTest {
       setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
       return props;
    }
-
-   @Override
-   public void testContainerListWithPrefix() {
-      throw new SkipException("Prefix option has not been plumbed down to Swift");
-   }
 }