Updated Branches:
  refs/heads/master cbac63e18 -> 44021fc9e

JCLOUDS-299. implement multi-region support in openstack-swift


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/44021fc9
Tree: 
http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/tree/44021fc9
Diff: 
http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/diff/44021fc9

Branch: refs/heads/master
Commit: 44021fc9eeaf9cc7886fd849323a2cdfc8bf97c7
Parents: cbac63e
Author: Adrian Cole <[email protected]>
Authored: Mon Sep 30 12:06:42 2013 -0700
Committer: Adrian Cole <[email protected]>
Committed: Mon Sep 30 12:37:16 2013 -0700

----------------------------------------------------------------------
 .../openstack/swift/v1/SwiftApiMetadata.java    |   4 +-
 .../blobstore/RegionScopedBlobStoreContext.java | 177 ++++++++++++++++++
 .../blobstore/RegionScopedSwiftBlobStore.java   | 181 ++++++++++++++-----
 .../RegionScopedTemporaryUrlBlobSigner.java     |  31 ++--
 .../config/SignUsingTemporaryUrls.java          |  63 +++----
 .../config/SwiftBlobStoreContextModule.java     |  41 ++++-
 .../blobstore/functions/ToResourceMetadata.java |   7 +-
 .../internal/SubmissionAsyncBlobStore.java      |   2 +-
 .../RegionScopedBlobStoreContextLiveTest.java   |  95 ++++++++++
 9 files changed, 494 insertions(+), 107 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
index a3ce97c..e2a1bbd 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/SwiftApiMetadata.java
@@ -24,11 +24,11 @@ import java.net.URI;
 import java.util.Properties;
 
 import org.jclouds.apis.ApiMetadata;
-import org.jclouds.blobstore.BlobStoreContext;
 import org.jclouds.openstack.keystone.v2_0.config.AuthenticationApiModule;
 import org.jclouds.openstack.keystone.v2_0.config.CredentialTypes;
 import org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule;
 import 
org.jclouds.openstack.keystone.v2_0.config.KeystoneAuthenticationModule.RegionModule;
+import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext;
 import org.jclouds.openstack.swift.v1.blobstore.config.SignUsingTemporaryUrls;
 import 
org.jclouds.openstack.swift.v1.blobstore.config.SwiftBlobStoreContextModule;
 import org.jclouds.openstack.swift.v1.config.SwiftHttpApiModule;
@@ -79,7 +79,7 @@ public class SwiftApiMetadata extends 
BaseHttpApiMetadata<SwiftApi> {
          .endpointName("KeyStone base url ending in /v2.0/")
          .defaultEndpoint("http://localhost:5000/v2.0/";)
          .defaultProperties(SwiftApiMetadata.defaultProperties())
-         .view(typeToken(BlobStoreContext.class))
+         .view(typeToken(RegionScopedBlobStoreContext.class))
          .defaultModules(ImmutableSet.<Class<? extends Module>>builder()
                                      .add(AuthenticationApiModule.class)
                                      .add(KeystoneAuthenticationModule.class)

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java
new file mode 100644
index 0000000..11e54d6
--- /dev/null
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.swift.v1.blobstore;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.Constants.PROPERTY_USER_THREADS;
+
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.jclouds.Context;
+import org.jclouds.blobstore.BlobRequestSigner;
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.blobstore.attr.ConsistencyModel;
+import org.jclouds.internal.BaseView;
+import org.jclouds.location.Provider;
+import org.jclouds.location.Region;
+import org.jclouds.rest.Utils;
+
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
+import com.google.common.reflect.TypeToken;
+import com.google.common.util.concurrent.ListeningExecutorService;
+
+/**
+ * Implementation of {@link BlobStoreContext} which allows you to employ
+ * multiple regions.
+ * 
+ * Example.
+ * 
+ * <pre>
+ * ctx = contextBuilder.buildView(RegionScopedBlobStoreContext.class);
+ * 
+ * Set&lt;String&gt; regionIds = ctx.configuredRegions();
+ * 
+ * // isolated to a specific region
+ * BlobStore texasBlobStore = ctx.blobStoreInRegion(&quot;US-TX&quot;);
+ * BlobStore virginiaBlobStore = ctx.blobStoreInRegion(&quot;US-VA&quot;);
+ * </pre>
+ */
+public class RegionScopedBlobStoreContext extends BaseView implements 
BlobStoreContext {
+
+   /**
+    * @return regions supported in this context.
+    */
+   public Set<String> configuredRegions() {
+      return regionIds.get();
+   }
+
+   /**
+    * @param regionId
+    *           valid region id from {@link #configuredRegions()}
+    * @throws IllegalArgumentException
+    *            if {@code regionId} was invalid.
+    */
+   public BlobStore blobStoreInRegion(String regionId) {
+      checkRegionId(regionId);
+      return blobStore.apply(regionId);
+   }
+
+   /**
+    * @param regionId
+    *           valid region id from {@link #configuredRegions()}
+    * @throws IllegalArgumentException
+    *            if {@code regionId} was invalid.
+    */
+   public BlobRequestSigner signerInRegion(String regionId) {
+      checkRegionId(regionId);
+      return blobRequestSigner.apply(regionId);
+   }
+
+   /**
+    * @param regionId
+    *           valid region id from {@link #configuredRegions()}
+    * @throws IllegalArgumentException
+    *            if {@code regionId} was invalid. longer supported. Please use
+    *            {@link org.jclouds.blobstore.BlobStore}
+    */
+   @Deprecated
+   public org.jclouds.blobstore.AsyncBlobStore asyncBlobStoreInRegion(String 
regionId) {
+      checkRegionId(regionId);
+      return new 
org.jclouds.openstack.swift.v1.blobstore.internal.SubmissionAsyncBlobStore(
+            blobStoreInRegion(regionId), executor);
+   }
+
+   protected void checkRegionId(String regionId) {
+      checkArgument(configuredRegions().contains(checkNotNull(regionId, 
"regionId was null")), "region %s not in %s",
+            regionId, configuredRegions());
+   }
+
+   private final Supplier<Set<String>> regionIds;
+   private final Supplier<String> implicitRegionId;
+   // factory functions are decoupled so that you can exchange how requests are
+   // signed or decorate without a class hierarchy dependency
+   private final Function<String, BlobStore> blobStore;
+   private final Function<String, BlobRequestSigner> blobRequestSigner;
+   private final Utils utils;
+   private final ListeningExecutorService executor;
+
+   @Inject
+   public RegionScopedBlobStoreContext(@Provider Context backend, @Provider 
TypeToken<? extends Context> backendType,
+         @Region Supplier<Set<String>> regionIds, @Region Supplier<String> 
implicitRegionId,
+         Function<String, BlobStore> blobStore, Function<String, 
BlobRequestSigner> blobRequestSigner, Utils utils,
+         @Named(PROPERTY_USER_THREADS) ListeningExecutorService executor) {
+      super(backend, backendType);
+      this.regionIds = checkNotNull(regionIds, "regionIds");
+      this.implicitRegionId = checkNotNull(implicitRegionId, 
"implicitRegionId");
+      this.blobStore = checkNotNull(blobStore, "blobStore");
+      this.blobRequestSigner = checkNotNull(blobRequestSigner, 
"blobRequestSigner");
+      this.utils = checkNotNull(utils, "utils");
+      this.executor = checkNotNull(executor, "executor");
+   }
+
+   @Override
+   public ConsistencyModel getConsistencyModel() {
+      return ConsistencyModel.STRICT;
+   }
+
+   @Override
+   public BlobStore getBlobStore() {
+      return blobStoreInRegion(implicitRegionId.get());
+   }
+
+   @Override
+   public BlobRequestSigner getSigner() {
+      return signerInRegion(implicitRegionId.get());
+   }
+
+   @Override
+   @Deprecated
+   public org.jclouds.blobstore.AsyncBlobStore getAsyncBlobStore() {
+      return asyncBlobStoreInRegion(implicitRegionId.get());
+   }
+
+   @Override
+   public Utils utils() {
+      return utils;
+   }
+
+   @Override
+   public void close() {
+      delegate().close();
+   }
+
+   public int hashCode() {
+      return delegate().hashCode();
+   }
+
+   @Override
+   public String toString() {
+      return delegate().toString();
+   }
+
+   @Override
+   public boolean equals(Object obj) {
+      return delegate().equals(obj);
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
index ad88550..eedfa27 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
@@ -17,29 +17,39 @@
 package org.jclouds.openstack.swift.v1.blobstore;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.tryFind;
+import static com.google.common.collect.Lists.transform;
+import static 
org.jclouds.blobstore.options.ListContainerOptions.Builder.recursive;
+import static org.jclouds.location.predicates.LocationPredicates.idEquals;
 
 import java.util.List;
 import java.util.Set;
 
 import javax.inject.Inject;
 
+import org.jclouds.blobstore.BlobStore;
 import org.jclouds.blobstore.BlobStoreContext;
 import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.domain.BlobBuilder;
 import org.jclouds.blobstore.domain.BlobMetadata;
 import org.jclouds.blobstore.domain.MutableBlobMetadata;
 import org.jclouds.blobstore.domain.PageSet;
 import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.blobstore.domain.StorageType;
+import org.jclouds.blobstore.domain.internal.BlobBuilderImpl;
 import org.jclouds.blobstore.domain.internal.BlobImpl;
 import org.jclouds.blobstore.domain.internal.PageSetImpl;
 import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
-import org.jclouds.blobstore.internal.BaseBlobStore;
 import org.jclouds.blobstore.options.CreateContainerOptions;
 import org.jclouds.blobstore.options.GetOptions;
 import org.jclouds.blobstore.options.ListContainerOptions;
 import org.jclouds.blobstore.options.PutOptions;
-import org.jclouds.blobstore.strategy.internal.FetchBlobMetadata;
-import org.jclouds.blobstore.util.BlobUtils;
+import org.jclouds.blobstore.strategy.ClearListStrategy;
+import org.jclouds.collect.Memoized;
 import org.jclouds.domain.Location;
+import org.jclouds.io.Payload;
+import org.jclouds.io.payloads.ByteArrayPayload;
 import org.jclouds.openstack.swift.v1.SwiftApi;
 import org.jclouds.openstack.swift.v1.blobstore.functions.ToBlobMetadata;
 import 
org.jclouds.openstack.swift.v1.blobstore.functions.ToListContainerOptions;
@@ -57,50 +67,57 @@ import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.assistedinject.Assisted;
 
-public class RegionScopedSwiftBlobStore extends BaseBlobStore {
-
-   private final BlobToHttpGetOptions toGetOptions = new 
BlobToHttpGetOptions();
-   private final ToListContainerOptions toListContainerOptions = new 
ToListContainerOptions();
-
-   private final SwiftApi api;
-   private final Supplier<Location> region;
-   private final ToResourceMetadata toResourceMetadata;
-   private final FetchBlobMetadata fetchMetadata;
+public class RegionScopedSwiftBlobStore implements BlobStore {
 
    @Inject
-   protected RegionScopedSwiftBlobStore(BlobStoreContext context, BlobUtils 
blobUtils, final Supplier<Location> region,
-         SwiftApi api, FetchBlobMetadata fetchMetadata) {
-      super(context, blobUtils, region, new Supplier<Set<? extends 
Location>>() {
+   protected RegionScopedSwiftBlobStore(Injector baseGraph, BlobStoreContext 
context, SwiftApi api,
+         @Memoized Supplier<Set<? extends Location>> locations, @Assisted 
String regionId) {
+      checkNotNull(regionId, "regionId");
+      Optional<? extends Location> found = tryFind(locations.get(), 
idEquals(regionId));
+      checkArgument(found.isPresent(), "region %s not in %s", regionId, 
locations.get());
+      this.region = found.get();
+      this.toResourceMetadata = new ToResourceMetadata(found.get());
+      this.context = context;
+      this.api = api;
+      // until we parameterize ClearListStrategy with a factory
+      this.clearList = baseGraph.createChildInjector(new AbstractModule() {
          @Override
-         public Set<? extends Location> get() {
-            return ImmutableSet.of(region.get());
+         protected void configure() {
+            bind(BlobStore.class).toInstance(RegionScopedSwiftBlobStore.this);
          }
-      });
-      this.api = api;
-      this.toResourceMetadata = new ToResourceMetadata(region);
-      this.region = region;
-      this.fetchMetadata = fetchMetadata;
+      }).getInstance(ClearListStrategy.class);
    }
 
-   /** all commands are scoped to a region. */
-   protected String regionId() {
-      return region.get().getId();
+   private final BlobStoreContext context;
+   private final ClearListStrategy clearList;
+   private final SwiftApi api;
+   private final Location region;
+   private final BlobToHttpGetOptions toGetOptions = new 
BlobToHttpGetOptions();
+   private final ToListContainerOptions toListContainerOptions = new 
ToListContainerOptions();
+   private final ToResourceMetadata toResourceMetadata;
+
+   @Override
+   public Set<? extends Location> listAssignableLocations() {
+      return ImmutableSet.of(region);
    }
 
    @Override
    public PageSet<? extends StorageMetadata> list() {
       // TODO: there may eventually be >10k containers..
-      FluentIterable<StorageMetadata> containers = 
api.containerApiInRegion(regionId()).listFirstPage()
+      FluentIterable<StorageMetadata> containers = 
api.containerApiInRegion(region.getId()).listFirstPage()
             .transform(toResourceMetadata);
       return new PageSetImpl<StorageMetadata>(containers, null);
    }
 
    @Override
    public boolean containerExists(String container) {
-      Container val = api.containerApiInRegion(regionId()).get(container);
+      Container val = api.containerApiInRegion(region.getId()).get(container);
       containerCache.put(container, Optional.fromNullable(val));
       return val != null;
    }
@@ -112,11 +129,11 @@ public class RegionScopedSwiftBlobStore extends 
BaseBlobStore {
 
    @Override
    public boolean createContainerInLocation(Location location, String 
container, CreateContainerOptions options) {
-      checkArgument(location == null || location.equals(region.get()), 
"location must be null or %s", region.get());
+      checkArgument(location == null || location.equals(region), "location 
must be null or %s", region);
       if (options.isPublicRead()) {
-         return api.containerApiInRegion(regionId()).createIfAbsent(container, 
ANYBODY_READ);
+         return 
api.containerApiInRegion(region.getId()).createIfAbsent(container, 
ANYBODY_READ);
       }
-      return api.containerApiInRegion(regionId()).createIfAbsent(container, 
BASIC_CONTAINER);
+      return 
api.containerApiInRegion(region.getId()).createIfAbsent(container, 
BASIC_CONTAINER);
    }
 
    private static final 
org.jclouds.openstack.swift.v1.options.CreateContainerOptions BASIC_CONTAINER = 
new org.jclouds.openstack.swift.v1.options.CreateContainerOptions();
@@ -124,22 +141,35 @@ public class RegionScopedSwiftBlobStore extends 
BaseBlobStore {
          .anybodyRead();
 
    @Override
-   public PageSet<? extends StorageMetadata> list(String container, 
ListContainerOptions options) {
-      ObjectApi objectApi = api.objectApiInRegionForContainer(regionId(), 
container);
+   public PageSet<? extends StorageMetadata> list(String container) {
+      return list(container, ListContainerOptions.NONE);
+   }
+
+   @Override
+   public PageSet<? extends StorageMetadata> list(final String container, 
ListContainerOptions options) {
+      ObjectApi objectApi = api.objectApiInRegionForContainer(region.getId(), 
container);
       ObjectList objects = 
objectApi.list(toListContainerOptions.apply(options));
       if (objects == null) {
          containerCache.put(container, Optional.<Container> absent());
          return new 
PageSetImpl<StorageMetadata>(ImmutableList.<StorageMetadata> of(), null);
       } else {
          containerCache.put(container, Optional.of(objects.container()));
-         List<MutableBlobMetadata> list = Lists.transform(objects, 
toBlobMetadata(container));
+         List<? extends StorageMetadata> list = transform(objects, 
toBlobMetadata(container));
          int limit = Optional.fromNullable(options.getMaxResults()).or(10000);
          String marker = list.size() == limit ? list.get(limit - 1).getName() 
: null;
-         PageSet<StorageMetadata> pageSet = new 
PageSetImpl<StorageMetadata>(list, marker);
+         // TODO: we should probably deprecate this option
          if (options.isDetailed()) {
-            return fetchMetadata.setContainerName(container).apply(pageSet);
+            list = transform(list, new Function<StorageMetadata, 
StorageMetadata>() {
+               @Override
+               public StorageMetadata apply(StorageMetadata input) {
+                  if (input.getType() != StorageType.BLOB) {
+                     return input;
+                  }
+                  return blobMetadata(container, input.getName());
+               }
+            });
          }
-         return pageSet;
+         return new PageSetImpl<StorageMetadata>(list, marker);
       }
    }
 
@@ -158,13 +188,13 @@ public class RegionScopedSwiftBlobStore extends 
BaseBlobStore {
       if (options.isMultipart()) {
          throw new UnsupportedOperationException();
       }
-      ObjectApi objectApi = api.objectApiInRegionForContainer(regionId(), 
container);
+      ObjectApi objectApi = api.objectApiInRegionForContainer(region.getId(), 
container);
       return objectApi.replace(blob.getMetadata().getName(), 
blob.getPayload(), blob.getMetadata().getUserMetadata());
    }
 
    @Override
    public BlobMetadata blobMetadata(String container, String name) {
-      SwiftObject object = api.objectApiInRegionForContainer(regionId(), 
container).head(name);
+      SwiftObject object = api.objectApiInRegionForContainer(region.getId(), 
container).head(name);
       if (object == null) {
          return null;
       }
@@ -172,8 +202,13 @@ public class RegionScopedSwiftBlobStore extends 
BaseBlobStore {
    }
 
    @Override
+   public Blob getBlob(String container, String key) {
+      return getBlob(container, key, GetOptions.NONE);
+   }
+
+   @Override
    public Blob getBlob(String container, String name, GetOptions options) {
-      ObjectApi objectApi = api.objectApiInRegionForContainer(regionId(), 
container);
+      ObjectApi objectApi = api.objectApiInRegionForContainer(region.getId(), 
container);
       SwiftObject object = objectApi.get(name, toGetOptions.apply(options));
       if (object == null) {
          return null;
@@ -185,24 +220,80 @@ public class RegionScopedSwiftBlobStore extends 
BaseBlobStore {
 
    @Override
    public void removeBlob(String container, String name) {
-      api.objectApiInRegionForContainer(regionId(), container).delete(name);
+      api.objectApiInRegionForContainer(region.getId(), 
container).delete(name);
+   }
+
+   @Override
+   public BlobStoreContext getContext() {
+      return context;
    }
 
    @Override
-   protected boolean deleteAndVerifyContainerGone(String container) {
-      api.containerApiInRegion(regionId()).deleteIfEmpty(container);
+   public BlobBuilder blobBuilder(String name) {
+      return new BlobBuilderImpl().name(name);
+   }
+
+   @Override
+   public boolean directoryExists(String containerName, String directory) {
+      return api.objectApiInRegionForContainer(region.getId(), containerName) 
//
+            .head(directory) != null;
+   }
+
+   @Override
+   public void createDirectory(String containerName, String directory) {
+      api.objectApiInRegionForContainer(region.getId(), containerName) //
+            .replace(directory, directoryPayload, ImmutableMap.<String, 
String> of());
+   }
+
+   private final Payload directoryPayload = new ByteArrayPayload(new byte[] 
{}) {
+      {
+         getContentMetadata().setContentType("application/directory");
+      }
+   };
+
+   @Override
+   public void deleteDirectory(String containerName, String directory) {
+      api.objectApiInRegionForContainer(region.getId(), 
containerName).delete(directory);
+   }
+
+   @Override
+   public long countBlobs(String containerName) {
+      Container container = 
api.containerApiInRegion(region.getId()).get(containerName);
+      // undefined if container doesn't exist, so default to zero
+      return container != null ? container.objectCount() : 0;
+   }
+
+   @Override
+   public void clearContainer(String containerName) {
+      clearContainer(containerName, recursive());
+   }
+
+   @Override
+   public void clearContainer(String containerName, ListContainerOptions 
options) {
+      // this could be implemented to use bulk delete
+      clearList.execute(containerName, options);
+   }
+
+   @Override
+   public void deleteContainer(String container) {
+      clearContainer(container, recursive());
+      api.containerApiInRegion(region.getId()).deleteIfEmpty(container);
       containerCache.invalidate(container);
-      return true;
    }
 
    protected final LoadingCache<String, Optional<Container>> containerCache = 
CacheBuilder.newBuilder().build(
          new CacheLoader<String, Optional<Container>>() {
             public Optional<Container> load(String container) {
-               return 
Optional.fromNullable(api.containerApiInRegion(regionId()).get(container));
+               return 
Optional.fromNullable(api.containerApiInRegion(region.getId()).get(container));
             }
          });
 
    protected Function<SwiftObject, MutableBlobMetadata> toBlobMetadata(String 
container) {
       return new ToBlobMetadata(containerCache.getUnchecked(container).get());
    }
+
+   @Override
+   public long countBlobs(String containerName, ListContainerOptions options) {
+      throw new UnsupportedOperationException();
+   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
index 81dce15..78dc63f 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
@@ -17,8 +17,10 @@
 package org.jclouds.openstack.swift.v1.blobstore;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
 
 import java.net.URI;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 import javax.inject.Provider;
@@ -31,31 +33,36 @@ import org.jclouds.http.HttpRequest;
 import org.jclouds.http.Uris;
 import org.jclouds.http.options.GetOptions;
 import org.jclouds.location.Region;
+import org.jclouds.openstack.swift.v1.SwiftApi;
 import org.jclouds.openstack.swift.v1.TemporaryUrlSigner;
 
 import com.google.common.base.Supplier;
 import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.name.Named;
 
 /**
  * Uses {@link TemporaryUrlSigner} to sign requests for access to blobs. If no
  * interval is supplied, it defaults to a year.
  */
 public class RegionScopedTemporaryUrlBlobSigner implements BlobRequestSigner {
-   private static final long YEAR = TimeUnit.DAYS.toSeconds(365);
-   private final BlobToHttpGetOptions toGetOptions = new 
BlobToHttpGetOptions();
-
-   private final Supplier<TemporaryUrlSigner> signer;
-   private final Supplier<URI> storageUrl;
-   private final Provider<Long> timestamp;
 
    @Inject
-   protected RegionScopedTemporaryUrlBlobSigner(Supplier<TemporaryUrlSigner> 
signer, @Region Supplier<URI> storageUrl,
-         @TimeStamp Provider<Long> timestamp) {
-      this.signer = signer;
-      this.storageUrl = storageUrl;
+   protected RegionScopedTemporaryUrlBlobSigner(@Region Supplier<Map<String, 
Supplier<URI>>> regionToUris,
+         @Named(PROPERTY_SESSION_INTERVAL) long seconds, @TimeStamp 
Provider<Long> timestamp, SwiftApi api,
+         @Assisted String regionId) {
+      checkNotNull(regionId, "regionId");
       this.timestamp = timestamp;
+      this.signer = 
TemporaryUrlSigner.checkApiEvery(api.accountApiInRegion(regionId), seconds);
+      this.storageUrl = regionToUris.get().get(regionId).get();
    }
 
+   private static final long YEAR = TimeUnit.DAYS.toSeconds(365);
+   private final BlobToHttpGetOptions toGetOptions = new 
BlobToHttpGetOptions();
+   private final Provider<Long> timestamp;
+   private final TemporaryUrlSigner signer;
+   private final URI storageUrl;
+
    @Override
    public HttpRequest signGetBlob(String container, String name) {
       return signGetBlob(container, name, YEAR);
@@ -89,8 +96,8 @@ public class RegionScopedTemporaryUrlBlobSigner implements 
BlobRequestSigner {
    private HttpRequest sign(String method, String container, String name, 
GetOptions options, long expires) {
       checkNotNull(container, "container");
       checkNotNull(name, "name");
-      URI url = 
Uris.uriBuilder(storageUrl.get()).appendPath(container).appendPath(name).build();
-      String signature = signer.get().sign(method, url.getPath(), expires);
+      URI url = 
Uris.uriBuilder(storageUrl).appendPath(container).appendPath(name).build();
+      String signature = signer.sign(method, url.getPath(), expires);
       return HttpRequest.builder()
                         .method(method)
                         .endpoint(url)

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java
index 91c753c..3a551a1 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SignUsingTemporaryUrls.java
@@ -16,63 +16,52 @@
  */
 package org.jclouds.openstack.swift.v1.blobstore.config;
 
-import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
-
-import java.net.URI;
-import java.util.Map;
-
-import javax.inject.Singleton;
+import javax.inject.Inject;
 
 import org.jclouds.blobstore.BlobRequestSigner;
 import org.jclouds.date.TimeStamp;
-import org.jclouds.location.Region;
-import org.jclouds.openstack.swift.v1.SwiftApi;
-import org.jclouds.openstack.swift.v1.TemporaryUrlSigner;
 import 
org.jclouds.openstack.swift.v1.blobstore.RegionScopedTemporaryUrlBlobSigner;
 
-import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
+import com.google.common.base.Function;
+import com.google.common.collect.ForwardingObject;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
-import com.google.inject.name.Named;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
 
 public class SignUsingTemporaryUrls extends AbstractModule {
 
    @Override
    protected void configure() {
-      
bind(BlobRequestSigner.class).to(RegionScopedTemporaryUrlBlobSigner.class);
+      install(new FactoryModuleBuilder().build(Factory.class));
    }
 
-   @Provides
-   @TimeStamp
-   protected Long unixEpochTimestamp() {
-      return System.currentTimeMillis() / 1000;
+   static interface Factory {
+      RegionScopedTemporaryUrlBlobSigner create(String in);
    }
 
    @Provides
-   @Singleton
-   Supplier<TemporaryUrlSigner> regionScopedTemporaryUrlSigner(final SwiftApi 
api,
-         @Region final Supplier<String> defaultRegion, 
@Named(PROPERTY_SESSION_INTERVAL) final long seconds) {
-      return Suppliers.memoize(new Supplier<TemporaryUrlSigner>() {
+   Function<String, BlobRequestSigner> blobRequestSigner(FactoryFunction in) {
+      return in;
+   }
 
-         @Override
-         public TemporaryUrlSigner get() {
-            return 
TemporaryUrlSigner.checkApiEvery(api.accountApiInRegion(defaultRegion.get()), 
seconds);
-         }
-      });
+   static class FactoryFunction extends ForwardingObject implements 
Function<String, BlobRequestSigner> {
+      @Inject
+      Factory delegate;
+
+      @Override
+      protected Factory delegate() {
+         return delegate;
+      }
+
+      @Override
+      public BlobRequestSigner apply(String in) {
+         return delegate.create(in);
+      }
    }
 
    @Provides
-   @Singleton
-   @Region
-   Supplier<URI> storageUrl(@Region final Supplier<String> defaultRegion,
-         @Region final Supplier<Map<String, Supplier<URI>>> regionToUris) {
-      return Suppliers.memoize(new Supplier<URI>() {
-
-         @Override
-         public URI get() {
-            return regionToUris.get().get(defaultRegion.get()).get();
-         }
-      });
+   @TimeStamp
+   protected Long unixEpochTimestamp() {
+      return System.currentTimeMillis() / 1000;
    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java
index 4452cdd..efd2166 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/config/SwiftBlobStoreContextModule.java
@@ -16,19 +16,48 @@
  */
 package org.jclouds.openstack.swift.v1.blobstore.config;
 
-import org.jclouds.blobstore.AsyncBlobStore;
+import javax.inject.Inject;
+
 import org.jclouds.blobstore.BlobStore;
-import org.jclouds.blobstore.attr.ConsistencyModel;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext;
 import org.jclouds.openstack.swift.v1.blobstore.RegionScopedSwiftBlobStore;
-import 
org.jclouds.openstack.swift.v1.blobstore.internal.SubmissionAsyncBlobStore;
 
+import com.google.common.base.Function;
+import com.google.common.collect.ForwardingObject;
 import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
 
 public class SwiftBlobStoreContextModule extends AbstractModule {
+
    @Override
    protected void configure() {
-      bind(ConsistencyModel.class).toInstance(ConsistencyModel.STRICT);
-      bind(AsyncBlobStore.class).to(SubmissionAsyncBlobStore.class);
-      bind(BlobStore.class).to(RegionScopedSwiftBlobStore.class);
+      bind(BlobStoreContext.class).to(RegionScopedBlobStoreContext.class);
+      install(new FactoryModuleBuilder().build(Factory.class));
+   }
+
+   static interface Factory {
+      RegionScopedSwiftBlobStore create(String in);
+   }
+
+   @Provides
+   Function<String, BlobStore> blobStore(FactoryFunction in) {
+      return in;
+   }
+
+   static class FactoryFunction extends ForwardingObject implements 
Function<String, BlobStore> {
+      @Inject
+      Factory delegate;
+
+      @Override
+      protected Factory delegate() {
+         return delegate;
+      }
+
+      @Override
+      public BlobStore apply(String in) {
+         return delegate.create(in);
+      }
    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java
index 6d12ce8..0384388 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/functions/ToResourceMetadata.java
@@ -24,12 +24,11 @@ import org.jclouds.domain.Location;
 import org.jclouds.openstack.swift.v1.domain.Container;
 
 import com.google.common.base.Function;
-import com.google.common.base.Supplier;
 
 public class ToResourceMetadata implements Function<Container, 
StorageMetadata> {
-   private Supplier<Location> region;
+   private Location region;
 
-   public ToResourceMetadata(Supplier<Location> region) {
+   public ToResourceMetadata(Location region) {
       this.region = region;
    }
 
@@ -37,7 +36,7 @@ public class ToResourceMetadata implements 
Function<Container, StorageMetadata>
    public StorageMetadata apply(Container from) {
       MutableStorageMetadata to = new MutableStorageMetadataImpl();
       to.setName(from.name());
-      to.setLocation(region.get());
+      to.setLocation(region);
       to.setType(StorageType.CONTAINER);
       to.setUserMetadata(from.metadata());
       return to;

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java
 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java
index 1d083b2..3710270 100644
--- 
a/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java
+++ 
b/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/internal/SubmissionAsyncBlobStore.java
@@ -56,7 +56,7 @@ public class SubmissionAsyncBlobStore extends 
ForwardingObject implements AsyncB
    private final ListeningExecutorService executor;
 
    @Inject
-   SubmissionAsyncBlobStore(BlobStore blobstore, @Named(PROPERTY_USER_THREADS) 
ListeningExecutorService executor) {
+   public SubmissionAsyncBlobStore(BlobStore blobstore, 
@Named(PROPERTY_USER_THREADS) ListeningExecutorService executor) {
       this.blobstore = checkNotNull(blobstore, "blobstore");
       this.executor = checkNotNull(executor, "executor");
    }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds-labs-openstack/blob/44021fc9/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java
----------------------------------------------------------------------
diff --git 
a/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java
 
b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java
new file mode 100644
index 0000000..75f273e
--- /dev/null
+++ 
b/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContextLiveTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.swift.v1.blobstore;
+
+import static 
org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties.CREDENTIAL_TYPE;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.blobstore.domain.PageSet;
+import org.jclouds.blobstore.domain.StorageMetadata;
+import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
+import org.jclouds.domain.Location;
+import org.jclouds.http.HttpRequest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Iterables;
+
+@Test(groups = "live")
+public class RegionScopedBlobStoreContextLiveTest extends 
BaseBlobStoreIntegrationTest {
+
+   public RegionScopedBlobStoreContextLiveTest() {
+      provider = "openstack-swift";
+   }
+
+   @Override
+   protected Properties setupProperties() {
+      Properties props = super.setupProperties();
+      setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
+      return props;
+   }
+
+   @Test
+   public void regionsAreNotEmpty() {
+      
assertFalse(RegionScopedBlobStoreContext.class.cast(view).configuredRegions().isEmpty());
+   }
+
+   @Test
+   public void locationsMatch() {
+      RegionScopedBlobStoreContext ctx = 
RegionScopedBlobStoreContext.class.cast(view);
+      for (String regionId : ctx.configuredRegions()) {
+         Set<? extends Location> locations = 
ctx.blobStoreInRegion(regionId).listAssignableLocations();
+         assertEquals(locations.size(), 1, "expected one region " + regionId + 
" " + locations);
+         Location location = locations.iterator().next();
+         assertEquals(location.getId(), regionId, "region id " + regionId + " 
didn't match getId(): " + location);
+      }
+   }
+
+   @Test
+   public void tryList() throws InterruptedException, ExecutionException {
+      RegionScopedBlobStoreContext ctx = 
RegionScopedBlobStoreContext.class.cast(view);
+      for (String regionId : ctx.configuredRegions()) {
+         assertEquals(ctx.asyncBlobStoreInRegion(regionId).list().get(), 
ctx.blobStoreInRegion(regionId).list());
+      }
+   }
+
+   @Test
+   public void trySign() throws InterruptedException, ExecutionException {
+      RegionScopedBlobStoreContext ctx = 
RegionScopedBlobStoreContext.class.cast(view);
+      for (String regionId : ctx.configuredRegions()) {
+         BlobStore region = ctx.blobStoreInRegion(regionId);
+         PageSet<? extends StorageMetadata> containers = region.list();
+         if (containers.isEmpty()) {
+            continue;
+         }
+         String containerName = Iterables.getLast(containers).getName();
+         PageSet<? extends StorageMetadata> blobs = region.list(containerName);
+         if (blobs.isEmpty()) {
+            continue;
+         }
+         String blobName = Iterables.getLast(blobs).getName();
+         HttpRequest request = 
ctx.signerInRegion(regionId).signGetBlob(containerName, blobName);
+         assertNotNull(request, "regionId=" + regionId + ", container=" + 
containerName + ", blob=" + blobName);
+      }
+   }
+}

Reply via email to