JCLOUDS-929: Implement generic delimiter support.

The patch adds delimiter option support in the generic blob store
interface. A live integration test is added to verify that jclouds
correctly lists objects separated by a delimiter.
This commit is contained in:
Timur Alperovich 2015-06-08 17:25:52 -07:00 committed by Andrew Gaul
parent 1fe90b03c9
commit a29d75a5d1
10 changed files with 107 additions and 7 deletions

View File

@ -30,4 +30,9 @@ public class AtmosContainerLiveTest extends BaseContainerLiveTest {
public void testContainerListWithPrefix() { public void testContainerListWithPrefix() {
throw new SkipException("Prefix option has not been plumbed down to Atmos"); throw new SkipException("Prefix option has not been plumbed down to Atmos");
} }
@Override
public void testDelimiterList() {
throw new SkipException("Delimiter support is not yet implemented");
}
} }

View File

@ -21,6 +21,7 @@ import static org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CRED
import java.util.Properties; import java.util.Properties;
import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest; import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
import org.testng.SkipException;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@Test(groups = "live", testName = "SwiftContainerLiveTest") @Test(groups = "live", testName = "SwiftContainerLiveTest")
@ -36,4 +37,10 @@ public class SwiftContainerLiveTest extends BaseContainerLiveTest {
setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE); setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
return props; return props;
} }
@Override
@Test
public void testDelimiterList() {
throw new SkipException("Delimiter support is not yet implemented");
}
} }

View File

@ -18,6 +18,7 @@ package org.jclouds.s3.blobstore.integration;
import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest; import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest; import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
import org.testng.SkipException;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@Test(groups = "live", testName = "S3ContainerLiveTest") @Test(groups = "live", testName = "S3ContainerLiveTest")
@ -27,4 +28,10 @@ public class S3ContainerLiveTest extends BaseContainerLiveTest {
provider = "s3"; provider = "s3";
BaseBlobStoreIntegrationTest.SANITY_CHECK_RETURNED_BUCKET_NAME = true; BaseBlobStoreIntegrationTest.SANITY_CHECK_RETURNED_BUCKET_NAME = true;
} }
@Override
@Test
public void testDelimiterList() {
throw new SkipException("not yet implemented");
}
} }

View File

@ -58,4 +58,9 @@ public class SwiftContainerLiveTest extends BaseContainerLiveTest {
public void testContainerListWithPrefix() { public void testContainerListWithPrefix() {
throw new SkipException("Prefix option has not been plumbed down to Swift"); throw new SkipException("Prefix option has not been plumbed down to Swift");
} }
@Override
public void testDelimiterList() {
throw new SkipException("The test fails as the path parameter elides subdirectories");
}
} }

View File

@ -39,6 +39,7 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
public static final ImmutableListContainerOptions NONE = new ImmutableListContainerOptions( public static final ImmutableListContainerOptions NONE = new ImmutableListContainerOptions(
new ListContainerOptions()); new ListContainerOptions());
private String delimiter;
private String dir; private String dir;
private String prefix; private String prefix;
private boolean recursive; private boolean recursive;
@ -48,12 +49,13 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
} }
ListContainerOptions(Integer maxKeys, String marker, String dir, boolean recursive, ListContainerOptions(Integer maxKeys, String marker, String dir, boolean recursive,
boolean detailed, String prefix) { boolean detailed, String prefix, String delimiter) {
super(maxKeys, marker); super(maxKeys, marker);
this.dir = dir; this.dir = dir;
this.recursive = recursive; this.recursive = recursive;
this.detailed = detailed; this.detailed = detailed;
this.prefix = prefix; this.prefix = prefix;
this.delimiter = delimiter;
} }
public static class ImmutableListContainerOptions extends ListContainerOptions { public static class ImmutableListContainerOptions extends ListContainerOptions {
@ -124,6 +126,16 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
return delegate.clone(); return delegate.clone();
} }
@Override
public ListContainerOptions delimiter(String delimiterString) {
throw new UnsupportedOperationException();
}
@Override
public String getDelimiter() {
return delegate.getDelimiter();
}
@Override @Override
public String toString() { public String toString() {
return delegate.toString(); return delegate.toString();
@ -135,6 +147,10 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
return dir; return dir;
} }
public String getDelimiter() {
return delimiter;
}
public boolean isRecursive() { public boolean isRecursive() {
return recursive; return recursive;
} }
@ -200,6 +216,15 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
return this; return this;
} }
/**
* specify the delimiter to be used when listing
*
*/
public ListContainerOptions delimiter(String delimiterString) {
this.delimiter = delimiterString;
return this;
}
public static class Builder { public static class Builder {
/** /**
@ -249,11 +274,18 @@ public class ListContainerOptions extends ListOptions implements Cloneable {
ListContainerOptions options = new ListContainerOptions(); ListContainerOptions options = new ListContainerOptions();
return options.prefix(prefix); return options.prefix(prefix);
} }
/**
* @see ListContainerOptions#delimiter(String)
*/
public static ListContainerOptions delimiter(String delimiterString) {
ListContainerOptions options = new ListContainerOptions();
return options.delimiter(delimiterString);
}
} }
@Override @Override
public ListContainerOptions clone() { public ListContainerOptions clone() {
return new ListContainerOptions(getMaxResults(), getMarker(), dir, recursive, detailed, prefix); return new ListContainerOptions(getMaxResults(), getMarker(), dir, recursive, detailed, prefix, delimiter);
} }
@Override @Override

View File

@ -382,6 +382,9 @@ public class DeleteAllKeysInList implements ClearListStrategy, ClearContainerStr
public void execute(final String containerName, public void execute(final String containerName,
ListContainerOptions listOptions) { ListContainerOptions listOptions) {
if (listOptions.getDelimiter() != null || listOptions.getPrefix() != null) {
throw new IllegalArgumentException("Prefix and delimiter support has not yet been added");
}
final AtomicBoolean deleteFailure = new AtomicBoolean(); final AtomicBoolean deleteFailure = new AtomicBoolean();
int retries = maxErrors; int retries = maxErrors;

View File

@ -133,6 +133,29 @@ public class BaseContainerLiveTest extends BaseBlobStoreIntegrationTest {
blobStore.putBlob(containerName, blobStore.blobBuilder("foo").payload("").build()); blobStore.putBlob(containerName, blobStore.blobBuilder("foo").payload("").build());
checkEqualNames(ImmutableSet.of(prefix, prefix + "foo", prefix + "bar"), checkEqualNames(ImmutableSet.of(prefix, prefix + "foo", prefix + "bar"),
blobStore.list(containerName, ListContainerOptions.Builder.prefix(prefix))); blobStore.list(containerName, ListContainerOptions.Builder.prefix(prefix)));
}
finally {
returnContainer(containerName);
}
}
@Test(groups = "live")
public void testDelimiterList() throws InterruptedException {
final String containerName = getContainerName();
BlobStore blobStore = view.getBlobStore();
String payload = "foo";
try {
blobStore.putBlob(containerName, blobStore.blobBuilder("test/foo/foo").payload(payload).build());
blobStore.putBlob(containerName, blobStore.blobBuilder("test/bar/foo").payload(payload).build());
blobStore.putBlob(containerName, blobStore.blobBuilder("foo").payload(payload).build());
blobStore.putBlob(containerName, blobStore.blobBuilder("test-foo").payload(payload).build());
blobStore.putBlob(containerName, blobStore.blobBuilder("test-bar").payload(payload).build());
checkEqualNames(ImmutableSet.of("foo", "test/", "test-foo", "test-bar"), blobStore.list(
containerName, ListContainerOptions.Builder.delimiter("/")));
checkEqualNames(ImmutableSet.of("test/foo/foo", "test/bar/foo", "foo", "test-foo", "test-bar"),
blobStore.list(containerName, ListContainerOptions.Builder.delimiter("\\")));
checkEqualNames(ImmutableSet.of("test-", "test/foo/foo", "test/bar/foo", "foo"), blobStore.list(
containerName, ListContainerOptions.Builder.delimiter("-")));
} finally { } finally {
returnContainer(containerName); returnContainer(containerName);
} }
@ -173,5 +196,4 @@ public class BaseContainerLiveTest extends BaseBlobStoreIntegrationTest {
recycleContainerAndAddToPool(containerName); recycleContainerAndAddToPool(containerName);
} }
} }
} }

View File

@ -49,6 +49,13 @@ public class ListOptionsTest {
assertEquals(options.getDir(), "test"); assertEquals(options.getDir(), "test");
} }
@Test
public void testDelimiter() {
ListContainerOptions options = new ListContainerOptions();
options.delimiter("-");
assertEquals(options.getDelimiter(), "-");
}
@Test @Test
public void testPathStatic() { public void testPathStatic() {
ListContainerOptions options = inDirectory("test"); ListContainerOptions options = inDirectory("test");

View File

@ -17,6 +17,7 @@
package org.jclouds.azureblob.blobstore.integration; package org.jclouds.azureblob.blobstore.integration;
import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest; import org.jclouds.blobstore.integration.internal.BaseContainerLiveTest;
import org.testng.SkipException;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@Test(groups = { "live" }) @Test(groups = { "live" })
@ -24,4 +25,10 @@ public class AzureBlobContainerLiveTest extends BaseContainerLiveTest {
public AzureBlobContainerLiveTest() { public AzureBlobContainerLiveTest() {
provider = "azureblob"; provider = "azureblob";
} }
@Override
@Test
public void testDelimiterList() {
throw new SkipException("The delimiter support has not been plumbed through to Azure blob");
}
} }

View File

@ -34,4 +34,9 @@ public class HPCloudObjectStorageContainerLiveTest extends BaseContainerLiveTest
@Test @Test
public void testPublicAccessInNonDefaultLocationWithBigBlob() { throw new SkipException("Locations are ignored"); } public void testPublicAccessInNonDefaultLocationWithBigBlob() { throw new SkipException("Locations are ignored"); }
@Override
@Test
public void testDelimiterList() {
throw new SkipException("\"path\" parameter elides subdirectories");
}
} }