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 2014/07/26 08:05:38 UTC

git commit: Add deleteContainerIfEmpty to BlobStore

Repository: jclouds
Updated Branches:
  refs/heads/master 10262df81 -> 1e1eb5a09


Add deleteContainerIfEmpty to BlobStore

This matches how most blobstores operate: delete container is a single
operation, not a compound operation which recursively deletes blobs.
Azure is the only provider which allows deleting a non-empty
container.


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

Branch: refs/heads/master
Commit: 1e1eb5a092275ddc3cb9de8a8d65ca07d713d27b
Parents: 10262df
Author: Andrew Gaul <ga...@apache.org>
Authored: Tue Jul 22 13:30:03 2014 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Fri Jul 25 23:02:39 2014 -0700

----------------------------------------------------------------------
 .../org/jclouds/atmos/AtmosAsyncClient.java     |  6 +--
 .../atmos/blobstore/AtmosAsyncBlobStore.java    |  5 ++-
 .../fallbacks/TrueOn404FalseOnPathNotEmpty.java | 47 ++++++++++++++++++++
 .../org/jclouds/atmos/AtmosAsyncClientTest.java |  7 +--
 .../atmos/internal/StubAtmosAsyncClient.java    | 14 ++----
 .../FilesystemContainerIntegrationTest.java     | 24 +++++++---
 .../src/main/clojure/org/jclouds/blobstore2.clj |  5 +++
 .../org/jclouds/blobstore/AsyncBlobStore.java   |  5 +++
 .../java/org/jclouds/blobstore/BlobStore.java   |  9 ++++
 .../jclouds/blobstore/LocalAsyncBlobStore.java  |  1 +
 .../blobstore/internal/BaseAsyncBlobStore.java  | 21 +++++++++
 .../blobstore/internal/BaseBlobStore.java       | 15 +++++++
 .../internal/BaseContainerIntegrationTest.java  | 30 ++++++++++++-
 .../blobstore/AzureAsyncBlobStore.java          |  9 +++-
 .../azureblob/blobstore/AzureBlobStore.java     |  8 +++-
 15 files changed, 180 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/apis/atmos/src/main/java/org/jclouds/atmos/AtmosAsyncClient.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/AtmosAsyncClient.java b/apis/atmos/src/main/java/org/jclouds/atmos/AtmosAsyncClient.java
index 35a3027..5334487 100644
--- a/apis/atmos/src/main/java/org/jclouds/atmos/AtmosAsyncClient.java
+++ b/apis/atmos/src/main/java/org/jclouds/atmos/AtmosAsyncClient.java
@@ -35,13 +35,13 @@ import javax.ws.rs.core.MediaType;
 
 import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
 import org.jclouds.Fallbacks.NullOnNotFoundOr404;
-import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
 import org.jclouds.atmos.binders.BindMetadataToHeaders;
 import org.jclouds.atmos.domain.AtmosObject;
 import org.jclouds.atmos.domain.BoundedSet;
 import org.jclouds.atmos.domain.DirectoryEntry;
 import org.jclouds.atmos.domain.SystemMetadata;
 import org.jclouds.atmos.domain.UserMetadata;
+import org.jclouds.atmos.fallbacks.TrueOn404FalseOnPathNotEmpty;
 import org.jclouds.atmos.filters.SignRequest;
 import org.jclouds.atmos.functions.AtmosObjectName;
 import org.jclouds.atmos.functions.ParseDirectoryListFromContentAndHeaders;
@@ -201,10 +201,10 @@ public interface AtmosAsyncClient extends Closeable {
     */
    @Named("DeleteObject")
    @DELETE
-   @Fallback(VoidOnNotFoundOr404.class)
+   @Fallback(TrueOn404FalseOnPathNotEmpty.class)
    @Path("/{path}")
    @Consumes(MediaType.WILDCARD)
-   ListenableFuture<Void> deletePath(@PathParam("path") String path);
+   ListenableFuture<Boolean> deletePath(@PathParam("path") String path);
 
    /**
     * @see AtmosClient#pathExists

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosAsyncBlobStore.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosAsyncBlobStore.java b/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosAsyncBlobStore.java
index 7a98a3d..983f068 100644
--- a/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosAsyncBlobStore.java
+++ b/apis/atmos/src/main/java/org/jclouds/atmos/blobstore/AtmosAsyncBlobStore.java
@@ -59,9 +59,11 @@ import org.jclouds.domain.Location;
 import org.jclouds.http.options.GetOptions;
 
 import com.google.common.base.Function;
+import com.google.common.base.Functions;
 import com.google.common.base.Supplier;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 
@@ -260,7 +262,8 @@ public class AtmosAsyncBlobStore extends BaseAsyncBlobStore {
     */
    @Override
    public ListenableFuture<Void> removeBlob(String container, String key) {
-      return async.deletePath(container + "/" + key);
+      return Futures.transform(async.deletePath(container + "/" + key), Functions.constant((Void) null),
+            userExecutor);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/apis/atmos/src/main/java/org/jclouds/atmos/fallbacks/TrueOn404FalseOnPathNotEmpty.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/main/java/org/jclouds/atmos/fallbacks/TrueOn404FalseOnPathNotEmpty.java b/apis/atmos/src/main/java/org/jclouds/atmos/fallbacks/TrueOn404FalseOnPathNotEmpty.java
new file mode 100644
index 0000000..18dda1e
--- /dev/null
+++ b/apis/atmos/src/main/java/org/jclouds/atmos/fallbacks/TrueOn404FalseOnPathNotEmpty.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.atmos.fallbacks;
+
+import static com.google.common.base.Throwables.propagate;
+import static com.google.common.util.concurrent.Futures.immediateFuture;
+import static org.jclouds.util.Throwables2.getFirstThrowableOfType;
+
+import org.jclouds.Fallback;
+import org.jclouds.atmos.AtmosResponseException;
+import org.jclouds.atmos.reference.AtmosErrorCode;
+import org.jclouds.http.HttpUtils;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+public final class TrueOn404FalseOnPathNotEmpty implements Fallback<Boolean> {
+   @Override
+   public ListenableFuture<Boolean> create(Throwable t) throws Exception {
+      return immediateFuture(createOrPropagate(t));
+   }
+
+   @Override
+   public Boolean createOrPropagate(Throwable t) throws Exception {
+      if (HttpUtils.contains404(t)) {
+         return true;
+      }
+      AtmosResponseException exception = getFirstThrowableOfType(t, AtmosResponseException.class);
+      if (exception != null && exception.getError().getCode() == AtmosErrorCode.DIRECTORY_NOT_EMPTY.getCode()) {
+         return false;
+      }
+      throw propagate(t);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/apis/atmos/src/test/java/org/jclouds/atmos/AtmosAsyncClientTest.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/test/java/org/jclouds/atmos/AtmosAsyncClientTest.java b/apis/atmos/src/test/java/org/jclouds/atmos/AtmosAsyncClientTest.java
index 2592a05..7dc2297 100644
--- a/apis/atmos/src/test/java/org/jclouds/atmos/AtmosAsyncClientTest.java
+++ b/apis/atmos/src/test/java/org/jclouds/atmos/AtmosAsyncClientTest.java
@@ -23,11 +23,11 @@ import java.io.IOException;
 
 import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
 import org.jclouds.Fallbacks.NullOnNotFoundOr404;
-import org.jclouds.Fallbacks.VoidOnNotFoundOr404;
 import org.jclouds.apis.ApiMetadata;
 import org.jclouds.atmos.blobstore.functions.BlobToObject;
 import org.jclouds.atmos.config.AtmosRestClientModule;
 import org.jclouds.atmos.domain.AtmosObject;
+import org.jclouds.atmos.fallbacks.TrueOn404FalseOnPathNotEmpty;
 import org.jclouds.atmos.filters.SignRequest;
 import org.jclouds.atmos.functions.ParseDirectoryListFromContentAndHeaders;
 import org.jclouds.atmos.functions.ParseNullableURIFromListOrLocationHeaderIf20x;
@@ -44,6 +44,7 @@ import org.jclouds.date.TimeStamp;
 import org.jclouds.http.HttpRequest;
 import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
 import org.jclouds.http.functions.ReleasePayloadAndReturn;
+import org.jclouds.http.functions.ReturnTrueIf2xx;
 import org.jclouds.http.options.GetOptions;
 import org.jclouds.rest.ConfiguresRestClient;
 import org.jclouds.rest.internal.BaseAsyncClientTest;
@@ -264,9 +265,9 @@ public class AtmosAsyncClientTest extends BaseAsyncClientTest<AtmosAsyncClient>
       assertNonPayloadHeadersEqual(request, HttpHeaders.ACCEPT + ": */*\n");
       assertPayloadEquals(request, null, null, false);
 
-      assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
+      assertResponseParserClassEquals(method, request, ReturnTrueIf2xx.class);
       assertSaxResponseParserClassEquals(method, null);
-      assertFallbackClassEquals(method, VoidOnNotFoundOr404.class);
+      assertFallbackClassEquals(method, TrueOn404FalseOnPathNotEmpty.class);
 
       checkFilters(request);
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/apis/atmos/src/test/java/org/jclouds/atmos/internal/StubAtmosAsyncClient.java
----------------------------------------------------------------------
diff --git a/apis/atmos/src/test/java/org/jclouds/atmos/internal/StubAtmosAsyncClient.java b/apis/atmos/src/test/java/org/jclouds/atmos/internal/StubAtmosAsyncClient.java
index bb56596..211d0a4 100644
--- a/apis/atmos/src/test/java/org/jclouds/atmos/internal/StubAtmosAsyncClient.java
+++ b/apis/atmos/src/test/java/org/jclouds/atmos/internal/StubAtmosAsyncClient.java
@@ -49,6 +49,7 @@ import org.jclouds.http.options.GetOptions;
 import org.jclouds.lifecycle.Closer;
 
 import com.google.common.base.Function;
+import com.google.common.base.Functions;
 import com.google.common.base.Throwables;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -134,21 +135,14 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
    }
 
    @Override
-   public ListenableFuture<Void> deletePath(String path) {
+   public ListenableFuture<Boolean> deletePath(String path) {
       if (path.indexOf('/') == path.length() - 1) {
          // chop off the trailing slash
-         return Futures.transform(blobStore.deleteContainerIfEmpty(path.substring(0, path.length() - 1)),
-                  new Function<Boolean, Void>() {
-
-                     public Void apply(Boolean from) {
-                        return null;
-                     }
-
-                  }, userExecutor);
+         return blobStore.deleteContainerIfEmpty(path.substring(0, path.length() - 1));
       } else {
          String container = path.substring(0, path.indexOf('/'));
          path = path.substring(path.indexOf('/') + 1);
-         return blobStore.removeBlob(container, path);
+         return Futures.transform(blobStore.removeBlob(container, path), Functions.constant(Boolean.TRUE), userExecutor);
       }
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemContainerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemContainerIntegrationTest.java b/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemContainerIntegrationTest.java
index 39ae537..f495479 100644
--- a/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemContainerIntegrationTest.java
+++ b/apis/filesystem/src/test/java/org/jclouds/filesystem/integration/FilesystemContainerIntegrationTest.java
@@ -101,6 +101,24 @@ public class FilesystemContainerIntegrationTest extends BaseContainerIntegration
 
    @Override
    @Test(dataProvider = "ignoreOnWindows")
+   public void deleteContainerWithoutContents() throws InterruptedException {
+      super.deleteContainerWithoutContents();
+   }
+
+   @Override
+   @Test(dataProvider = "ignoreOnWindows")
+   public void deleteContainerIfEmptyWithContents() throws InterruptedException {
+      super.deleteContainerIfEmptyWithContents();
+   }
+
+   @Override
+   @Test(dataProvider = "ignoreOnWindows")
+   public void deleteContainerIfEmptyWithoutContents() throws InterruptedException {
+      super.deleteContainerIfEmptyWithoutContents();
+   }
+
+   @Override
+   @Test(dataProvider = "ignoreOnWindows")
    public void testListContainer() throws InterruptedException, ExecutionException, TimeoutException {
       super.testListContainer();
    }
@@ -131,12 +149,6 @@ public class FilesystemContainerIntegrationTest extends BaseContainerIntegration
 
    @Override
    @Test(dataProvider = "ignoreOnWindows")
-   public void deleteContainerIfEmpty() throws InterruptedException {
-      super.deleteContainerIfEmpty();
-   }
-
-   @Override
-   @Test(dataProvider = "ignoreOnWindows")
    public void testListContainerMaxResults() throws InterruptedException {
       super.testListContainerMaxResults();
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/blobstore/src/main/clojure/org/jclouds/blobstore2.clj
----------------------------------------------------------------------
diff --git a/blobstore/src/main/clojure/org/jclouds/blobstore2.clj b/blobstore/src/main/clojure/org/jclouds/blobstore2.clj
index 6eb57d5..8b18636 100644
--- a/blobstore/src/main/clojure/org/jclouds/blobstore2.clj
+++ b/blobstore/src/main/clojure/org/jclouds/blobstore2.clj
@@ -207,6 +207,11 @@ Options can also be specified for extension modules
   [^BlobStore blobstore container-name]
   (.deleteContainer blobstore container-name))
 
+(defn delete-container-if-empty
+  "Delete a container if empty."
+  [^BlobStore blobstore container-name]
+  (.deleteContainerIfEmpty blobstore container-name))
+
 (defn container-exists?
   "Predicate to check presence of a container"
   [^BlobStore blobstore container-name]

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/blobstore/src/main/java/org/jclouds/blobstore/AsyncBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/AsyncBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/AsyncBlobStore.java
index 5b4609d..22feebd 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/AsyncBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/AsyncBlobStore.java
@@ -103,6 +103,11 @@ public interface AsyncBlobStore {
    ListenableFuture<Void> deleteContainer(String container);
 
    /**
+    * @see BlobStore#deleteContainerIfEmpty
+    */
+   ListenableFuture<Boolean> deleteContainerIfEmpty(String container);
+
+   /**
     * @see BlobStore#directoryExists
     */
    ListenableFuture<Boolean> directoryExists(String container, String directory);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
index eab9ae6..88db39a 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/BlobStore.java
@@ -140,10 +140,19 @@ public interface BlobStore {
     * 
     * @param container
     *           what to delete
+    * @param container name of the container to delete
     */
    void deleteContainer(String container);
 
    /**
+    * Deletes a container if it is empty.
+    *
+    * @param container name of the container to delete
+    * @return true if the container was deleted or does not exist
+    */
+   boolean deleteContainerIfEmpty(String container);
+
+   /**
     * Determines if a directory exists
     * 
     * @param container

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/blobstore/src/main/java/org/jclouds/blobstore/LocalAsyncBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/LocalAsyncBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/LocalAsyncBlobStore.java
index a411e21..59a3f26 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/LocalAsyncBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/LocalAsyncBlobStore.java
@@ -256,6 +256,7 @@ public class LocalAsyncBlobStore extends BaseAsyncBlobStore {
       return immediateFuture(null);
    }
 
+   @Override
    public ListenableFuture<Boolean> deleteContainerIfEmpty(final String container) {
       Boolean returnVal = true;
       if (storageStrategy.containerExists(container)) {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseAsyncBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseAsyncBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseAsyncBlobStore.java
index 36e5ff5..8bddf7f 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseAsyncBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseAsyncBlobStore.java
@@ -266,6 +266,21 @@ public abstract class BaseAsyncBlobStore implements AsyncBlobStore {
       });
    }
 
+   @Override
+   public ListenableFuture<Boolean> deleteContainerIfEmpty(final String container) {
+      return userExecutor.submit(new Callable<Boolean>() {
+
+         public Boolean call() throws Exception {
+            return deleteAndVerifyContainerGone(container);
+         }
+
+         @Override
+         public String toString() {
+            return "deleteContainerIfEmpty(" + container + ")";
+         }
+      });
+   }
+
    protected void deletePathAndEnsureGone(String path) {
       checkState(retry(new Predicate<String>() {
          public boolean apply(String in) {
@@ -284,6 +299,12 @@ public abstract class BaseAsyncBlobStore implements AsyncBlobStore {
       return Futures.<Set<? extends Location>> immediateFuture(locations.get());
    }
 
+   /**
+    * Delete a container if it is empty.
+    *
+    * @param container what to delete
+    * @return true if the container was deleted or does not exist
+    */
    protected abstract boolean deleteAndVerifyContainerGone(String container);
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
index 1ef8553..fe08c8a 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
@@ -189,6 +189,15 @@ public abstract class BaseBlobStore implements BlobStore {
       deletePathAndEnsureGone(container);
    }
 
+   @Override
+   public boolean deleteContainerIfEmpty(String container) {
+      try {
+         return deleteAndVerifyContainerGone(container);
+      } catch (ContainerNotFoundException cnfe) {
+         return true;
+      }
+   }
+
    protected void deletePathAndEnsureGone(String path) {
       checkState(retry(new Predicate<String>() {
          public boolean apply(String in) {
@@ -207,6 +216,12 @@ public abstract class BaseBlobStore implements BlobStore {
       return locations.get();
    }
 
+   /**
+    * Delete a container if it is empty.
+    *
+    * @param container what to delete
+    * @return whether container was deleted
+    */
    protected abstract boolean deleteAndVerifyContainerGone(String container);
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerIntegrationTest.java
index d97f26e..c3bdbb9 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseContainerIntegrationTest.java
@@ -24,6 +24,8 @@ import static org.jclouds.blobstore.options.ListContainerOptions.Builder.afterMa
 import static org.jclouds.blobstore.options.ListContainerOptions.Builder.inDirectory;
 import static org.jclouds.blobstore.options.ListContainerOptions.Builder.maxResults;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
 import java.io.IOException;
 import java.util.Set;
@@ -280,7 +282,7 @@ public class BaseContainerIntegrationTest extends BaseBlobStoreIntegrationTest {
    }
 
    @Test(groups = { "integration", "live" })
-   public void deleteContainerIfEmpty() throws InterruptedException {
+   public void deleteContainerWithoutContents() throws InterruptedException {
       final String containerName = getContainerName();
       try {
          view.getBlobStore().deleteContainer(containerName);
@@ -292,6 +294,32 @@ public class BaseContainerIntegrationTest extends BaseBlobStoreIntegrationTest {
    }
 
    @Test(groups = { "integration", "live" })
+   public void deleteContainerIfEmptyWithContents() throws InterruptedException {
+      String containerName = getContainerName();
+      try {
+         addBlobToContainer(containerName, "test");
+         assertFalse(view.getBlobStore().deleteContainerIfEmpty(containerName));
+         assertTrue(view.getBlobStore().containerExists(containerName));
+      } finally {
+         recycleContainerAndAddToPool(containerName);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
+   public void deleteContainerIfEmptyWithoutContents() throws InterruptedException {
+      final String containerName = getContainerName();
+      try {
+         assertTrue(view.getBlobStore().deleteContainerIfEmpty(containerName));
+         assertNotExists(containerName);
+         // verify that false is returned even if the container does not exist
+         assertTrue(view.getBlobStore().deleteContainerIfEmpty(containerName));
+      } finally {
+         // this container is now deleted, so we can't reuse it directly
+         recycleContainerAndAddToPool(containerName);
+      }
+   }
+
+   @Test(groups = { "integration", "live" })
    public void testListContainer() throws InterruptedException, ExecutionException, TimeoutException {
       String containerName = getContainerName();
       try {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureAsyncBlobStore.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureAsyncBlobStore.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureAsyncBlobStore.java
index d1ba11e..a0bba9f 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureAsyncBlobStore.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureAsyncBlobStore.java
@@ -64,6 +64,7 @@ import org.jclouds.http.options.GetOptions;
 import com.google.common.base.Function;
 import com.google.common.base.Supplier;
 import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import org.jclouds.io.Payload;
@@ -278,7 +279,13 @@ public class AzureAsyncBlobStore extends BaseAsyncBlobStore {
 
    @Override
    protected boolean deleteAndVerifyContainerGone(String container) {
-      throw new UnsupportedOperationException("please use deleteContainer");
+      // Azure deleteContainer supports deleting empty containers so emulate
+      // deleteIfEmpty by listing.
+      if (!Futures.getUnchecked(list(container)).isEmpty()) {
+         return false;
+      }
+      Futures.getUnchecked(async.deleteContainer(container));
+      return true;
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1e1eb5a0/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
index c4d4c8a..15a3f8b 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/blobstore/AzureBlobStore.java
@@ -266,7 +266,13 @@ public class AzureBlobStore extends BaseBlobStore {
 
    @Override
    protected boolean deleteAndVerifyContainerGone(String container) {
-      throw new UnsupportedOperationException("please use deleteContainer");
+      // Azure deleteContainer supports deleting empty containers so emulate
+      // deleteIfEmpty by listing.
+      if (!list(container).isEmpty()) {
+         return false;
+      }
+      sync.deleteContainer(container);
+      return true;
    }
 
    @Override