{
+ private final AccountApi api;
+
+ private TemporaryUrlKeyFromAccount(AccountApi api) {
+ this.api = checkNotNull(api, "accountApi");
+ }
+
+ @Override
+ public String get() {
+ return api.get().getTemporaryUrlKey().orNull();
+ }
+
+ @Override
+ public String toString() {
+ return format("get().getTemporaryUrlKey() using %s", api);
+ }
+ }
+}
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
new file mode 100644
index 00000000000..4dabc68b402
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/BindMetadataToHeaders.java
@@ -0,0 +1,141 @@
+/*
+ * 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.binders;
+
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.ACCOUNT_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.CONTAINER_METADATA_PREFIX;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_METADATA_PREFIX;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.Binder;
+
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableMultimap.Builder;
+
+/**
+ * Will lower-case header keys due to a swift implementation to return headers
+ * in a different case than sent. ex.
+ *
+ *
+ * >> X-Account-Meta-MyDelete1: foo
+ * >> X-Account-Meta-MyDelete2: bar
+ *
+ *
+ * results in:
+ *
+ *
+ * << X-Account-Meta-Mydelete1: foo
+ * << X-Account-Meta-Mydelete2: bar
+ *
+ *
+ * Note
+ * HTTP response headers keys are known to be case-insensitive, but this
+ * practice of mixing up case will prevent metadata keys such as those in
+ * Turkish from working.
+ */
+public class BindMetadataToHeaders implements Binder {
+
+ public static class BindAccountMetadataToHeaders extends BindMetadataToHeaders {
+ BindAccountMetadataToHeaders() {
+ super(ACCOUNT_METADATA_PREFIX);
+ }
+ }
+
+ public static class BindRemoveAccountMetadataToHeaders extends BindMetadataToHeaders.ForRemoval {
+ BindRemoveAccountMetadataToHeaders() {
+ super(ACCOUNT_METADATA_PREFIX);
+ }
+ }
+
+ public static class BindContainerMetadataToHeaders extends BindMetadataToHeaders {
+ BindContainerMetadataToHeaders() {
+ super(CONTAINER_METADATA_PREFIX);
+ }
+ }
+
+ public static class BindRemoveContainerMetadataToHeaders extends BindMetadataToHeaders.ForRemoval {
+ BindRemoveContainerMetadataToHeaders() {
+ super(CONTAINER_METADATA_PREFIX);
+ }
+ }
+
+ public static class BindObjectMetadataToHeaders extends BindMetadataToHeaders {
+ BindObjectMetadataToHeaders() {
+ super(OBJECT_METADATA_PREFIX);
+ }
+ }
+
+ public static class BindRemoveObjectMetadataToHeaders extends BindMetadataToHeaders.ForRemoval {
+ BindRemoveObjectMetadataToHeaders() {
+ super(OBJECT_METADATA_PREFIX);
+ }
+ }
+
+ /**
+ * @see documentation
+ */
+ public abstract static class ForRemoval extends BindMetadataToHeaders {
+ ForRemoval(String metadataPrefix) {
+ super(metadataPrefix);
+ }
+
+ @Override
+ protected void putMetadata(Builder headers, String key, String value) {
+ headers.put(String.format("x-remove%s", key.substring(1)), "ignored");
+ }
+ }
+
+ private final String metadataPrefix;
+
+ public BindMetadataToHeaders(String metadataPrefix) {
+ this.metadataPrefix = checkNotNull(metadataPrefix, "metadataPrefix");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public R bindToRequest(R request, Object input) {
+ checkNotNull(request, "request");
+ checkArgument(input instanceof Map, ?>, "input must be a non-null java.util.Map!");
+ Map metadata = Map.class.cast(input);
+ ImmutableMultimap headers = toHeaders(metadata);
+ return (R) request.toBuilder().replaceHeaders(headers).build();
+ }
+
+ protected void putMetadata(Builder headers, String key, String value) {
+ headers.put(key, value);
+ }
+
+ public ImmutableMultimap toHeaders(Map metadata) {
+ Builder builder = ImmutableMultimap. builder();
+ for (Entry keyVal : metadata.entrySet()) {
+ String keyInLowercase = keyVal.getKey().toLowerCase();
+ if (keyVal.getKey().startsWith(metadataPrefix)) {
+ putMetadata(builder, keyInLowercase, keyVal.getValue());
+ } else {
+ putMetadata(builder, String.format("%s%s", metadataPrefix, keyInLowercase), keyVal.getValue());
+ }
+ }
+ return builder.build();
+ }
+}
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java
new file mode 100644
index 00000000000..f24a3adf1f3
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/binders/SetPayload.java
@@ -0,0 +1,72 @@
+/*
+ * 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.binders;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.net.HttpHeaders.ETAG;
+import static com.google.common.net.HttpHeaders.TRANSFER_ENCODING;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.jclouds.openstack.swift.v1.reference.SwiftHeaders.OBJECT_DELETE_AT;
+
+import java.util.Date;
+
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpRequest.Builder;
+import org.jclouds.io.Payload;
+import org.jclouds.rest.Binder;
+
+import com.google.common.hash.HashCode;
+
+public class SetPayload implements Binder {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public R bindToRequest(R request, Object input) {
+ Builder> builder = request.toBuilder();
+ Payload payload = Payload.class.cast(input);
+
+ if (payload.getContentMetadata().getContentType() == null) {
+ // TODO: use `X-Detect-Content-Type` here. Should be configurable via a property.
+ payload.getContentMetadata().setContentType(MediaType.APPLICATION_OCTET_STREAM);
+ }
+
+ Long contentLength = payload.getContentMetadata().getContentLength();
+ if (contentLength != null && contentLength >= 0) {
+ checkArgument(contentLength <= 5l * 1024 * 1024 * 1024, "maximum size for put object is 5GB, %s",
+ contentLength);
+ } else {
+ builder.replaceHeader(TRANSFER_ENCODING, "chunked").build();
+ }
+
+ HashCode md5 = payload.getContentMetadata().getContentMD5AsHashCode();
+ if (md5 != null) {
+ // Swift will validate the md5, if placed as an ETag header
+ builder.replaceHeader(ETAG, base16().lowerCase().encode(md5.asBytes()));
+ }
+
+ Date expires = payload.getContentMetadata().getExpires();
+ if (expires != null) {
+ builder.addHeader(OBJECT_DELETE_AT,
+ String.valueOf(MILLISECONDS.toSeconds(expires.getTime()))).build();
+ }
+
+ return (R) builder.payload(payload).build();
+ }
+}
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java
new file mode 100644
index 00000000000..e58df8993c7
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedBlobStoreContext.java
@@ -0,0 +1,154 @@
+/*
+ * 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 java.util.Set;
+
+import javax.inject.Inject;
+
+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;
+
+/**
+ * Implementation of {@link BlobStoreContext} which allows you to employ
+ * multiple regions.
+ *
+ * Example.
+ *
+ *
+ * ctx = contextBuilder.buildView(RegionScopedBlobStoreContext.class);
+ *
+ * Set<String> regionIds = ctx.getConfiguredRegions();
+ *
+ * // isolated to a specific region
+ * BlobStore texasBlobStore = ctx.getBlobStore("US-TX");
+ * BlobStore virginiaBlobStore = ctx.getBlobStore("US-VA");
+ *
+ */
+public class RegionScopedBlobStoreContext extends BaseView implements BlobStoreContext {
+
+ /**
+ * @return regions supported in this context.
+ */
+ public Set getConfiguredRegions() {
+ return regionIds.get();
+ }
+
+ /**
+ * @param regionId
+ * valid region id from {@link #getConfiguredRegions()}
+ * @throws IllegalArgumentException
+ * if {@code regionId} was invalid.
+ */
+ public BlobStore getBlobStore(String regionId) {
+ checkRegionId(regionId);
+ return blobStore.apply(regionId);
+ }
+
+ /**
+ * @param regionId
+ * valid region id from {@link #getConfiguredRegions()}
+ * @throws IllegalArgumentException
+ * if {@code regionId} was invalid.
+ */
+ public BlobRequestSigner getSigner(String regionId) {
+ checkRegionId(regionId);
+ return blobRequestSigner.apply(regionId);
+ }
+
+ protected void checkRegionId(String regionId) {
+ checkArgument(getConfiguredRegions().contains(checkNotNull(regionId, "regionId was null")), "region %s not in %s",
+ regionId, getConfiguredRegions());
+ }
+
+ private final Supplier> regionIds;
+ private final Supplier implicitRegionId;
+ // factory functions are decoupled so that you can exchange how requests are
+ // signed or decorate without a class hierarchy dependency
+ private final Function blobStore;
+ private final Function blobRequestSigner;
+ private final Utils utils;
+ private final ConsistencyModel consistencyModel;
+
+ @Inject
+ public RegionScopedBlobStoreContext(@Provider Context backend, @Provider TypeToken extends Context> backendType,
+ @Region Supplier> regionIds, @Region Supplier implicitRegionId,
+ Function blobStore, Function blobRequestSigner, Utils utils,
+ ConsistencyModel consistencyModel) {
+ 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.consistencyModel = checkNotNull(consistencyModel, "consistencyModel");
+ }
+
+ @Override
+ public ConsistencyModel getConsistencyModel() {
+ return consistencyModel;
+ }
+
+ @Override
+ public BlobStore getBlobStore() {
+ return getBlobStore(implicitRegionId.get());
+ }
+
+ @Override
+ public BlobRequestSigner getSigner() {
+ return getSigner(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);
+ }
+
+}
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
new file mode 100644
index 00000000000..c0f65c5457a
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedSwiftBlobStore.java
@@ -0,0 +1,312 @@
+/*
+ * 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 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 static org.jclouds.openstack.swift.v1.options.PutOptions.Builder.metadata;
+
+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.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.ClearListStrategy;
+import org.jclouds.collect.Memoized;
+import org.jclouds.domain.Location;
+import org.jclouds.io.Payload;
+import org.jclouds.io.payloads.ByteSourcePayload;
+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;
+import org.jclouds.openstack.swift.v1.blobstore.functions.ToResourceMetadata;
+import org.jclouds.openstack.swift.v1.domain.Container;
+import org.jclouds.openstack.swift.v1.domain.ObjectList;
+import org.jclouds.openstack.swift.v1.domain.SwiftObject;
+import org.jclouds.openstack.swift.v1.features.ObjectApi;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.cache.CacheBuilder;
+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.ImmutableSet;
+import com.google.common.io.ByteSource;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.assistedinject.Assisted;
+
+public class RegionScopedSwiftBlobStore implements BlobStore {
+
+ @Inject
+ protected RegionScopedSwiftBlobStore(Injector baseGraph, BlobStoreContext context, SwiftApi api,
+ @Memoized Supplier> 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.regionId = regionId;
+ 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
+ protected void configure() {
+ bind(BlobStore.class).toInstance(RegionScopedSwiftBlobStore.this);
+ }
+ }).getInstance(ClearListStrategy.class);
+ }
+
+ private final BlobStoreContext context;
+ private final ClearListStrategy clearList;
+ private final SwiftApi api;
+ private final Location region;
+ private final String regionId;
+ 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 containers = api.getContainerApi(regionId).list()
+ .transform(toResourceMetadata);
+ return new PageSetImpl(containers, null);
+ }
+
+ @Override
+ public boolean containerExists(String container) {
+ Container val = api.getContainerApi(regionId).get(container);
+ containerCache.put(container, Optional.fromNullable(val));
+ return val != null;
+ }
+
+ @Override
+ public boolean createContainerInLocation(Location location, String container) {
+ return createContainerInLocation(location, container, CreateContainerOptions.NONE);
+ }
+
+ @Override
+ public boolean createContainerInLocation(Location location, String container, CreateContainerOptions options) {
+ checkArgument(location == null || location.equals(region), "location must be null or %s", region);
+ if (options.isPublicRead()) {
+ return api.getContainerApi(regionId).create(container, ANYBODY_READ);
+ }
+ return api.getContainerApi(regionId).create(container, BASIC_CONTAINER);
+ }
+
+ private static final org.jclouds.openstack.swift.v1.options.CreateContainerOptions BASIC_CONTAINER = new org.jclouds.openstack.swift.v1.options.CreateContainerOptions();
+ private static final org.jclouds.openstack.swift.v1.options.CreateContainerOptions ANYBODY_READ = new org.jclouds.openstack.swift.v1.options.CreateContainerOptions()
+ .anybodyRead();
+
+ @Override
+ 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.getObjectApi(regionId, container);
+ ObjectList objects = objectApi.list(toListContainerOptions.apply(options));
+ if (objects == null) {
+ containerCache.put(container, Optional. absent());
+ return new PageSetImpl(ImmutableList. of(), null);
+ } else {
+ containerCache.put(container, Optional.of(objects.getContainer()));
+ 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;
+ // TODO: we should probably deprecate this option
+ if (options.isDetailed()) {
+ list = transform(list, new Function() {
+ @Override
+ public StorageMetadata apply(StorageMetadata input) {
+ if (input.getType() != StorageType.BLOB) {
+ return input;
+ }
+ return blobMetadata(container, input.getName());
+ }
+ });
+ }
+ return new PageSetImpl(list, marker);
+ }
+ }
+
+ @Override
+ public boolean blobExists(String container, String name) {
+ return blobMetadata(container, name) != null;
+ }
+
+ @Override
+ public String putBlob(String container, Blob blob) {
+ return putBlob(container, blob, PutOptions.NONE);
+ }
+
+ @Override
+ public String putBlob(String container, Blob blob, PutOptions options) {
+ if (options.isMultipart()) {
+ throw new UnsupportedOperationException();
+ }
+ ObjectApi objectApi = api.getObjectApi(regionId, container);
+ return objectApi.put(blob.getMetadata().getName(), blob.getPayload(), metadata(blob.getMetadata().getUserMetadata()));
+ }
+
+ @Override
+ public BlobMetadata blobMetadata(String container, String name) {
+ SwiftObject object = api.getObjectApi(regionId, container).get(name);
+ if (object == null) {
+ return null;
+ }
+ return toBlobMetadata(container).apply(object);
+ }
+
+ @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.getObjectApi(regionId, container);
+ SwiftObject object = objectApi.get(name, toGetOptions.apply(options));
+ if (object == null) {
+ return null;
+ }
+ Blob blob = new BlobImpl(toBlobMetadata(container).apply(object));
+ blob.setPayload(object.getPayload());
+ blob.setAllHeaders(object.getHeaders());
+ return blob;
+ }
+
+ @Override
+ public void removeBlob(String container, String name) {
+ api.getObjectApi(regionId, container).delete(name);
+ }
+
+ @Override
+ public BlobStoreContext getContext() {
+ return context;
+ }
+
+ @Override
+ public BlobBuilder blobBuilder(String name) {
+ return new BlobBuilderImpl().name(name);
+ }
+
+ @Override
+ public boolean directoryExists(String containerName, String directory) {
+ return api.getObjectApi(regionId, containerName)
+ .get(directory) != null;
+ }
+
+ @Override
+ public void createDirectory(String containerName, String directory) {
+ api.getObjectApi(regionId, containerName)
+ .put(directory, directoryPayload);
+ }
+
+ private final Payload directoryPayload = new ByteSourcePayload(ByteSource.wrap(new byte[] {})) {
+ {
+ getContentMetadata().setContentType("application/directory");
+ }
+ };
+
+ @Override
+ public void deleteDirectory(String containerName, String directory) {
+ api.getObjectApi(regionId, containerName).delete(directory);
+ }
+
+ @Override
+ public long countBlobs(String containerName) {
+ Container container = api.getContainerApi(regionId).get(containerName);
+ // undefined if container doesn't exist, so default to zero
+ return container != null ? container.getObjectCount() : 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.getContainerApi(regionId).deleteIfEmpty(container);
+ containerCache.invalidate(container);
+ }
+
+ @Override
+ public boolean deleteContainerIfEmpty(String container) {
+ boolean deleted = api.getContainerApi(regionId).deleteIfEmpty(container);
+ if (deleted) {
+ containerCache.invalidate(container);
+ }
+ return deleted;
+ }
+
+ protected final LoadingCache> containerCache = CacheBuilder.newBuilder().build(
+ new CacheLoader>() {
+ public Optional load(String container) {
+ return Optional.fromNullable(api.getContainerApi(regionId).get(container));
+ }
+ });
+
+ protected Function toBlobMetadata(String container) {
+ return new ToBlobMetadata(containerCache.getUnchecked(container).get());
+ }
+
+ @Override
+ public long countBlobs(String containerName, ListContainerOptions options) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
new file mode 100644
index 00000000000..5134efae34f
--- /dev/null
+++ b/apis/openstack-swift/src/main/java/org/jclouds/openstack/swift/v1/blobstore/RegionScopedTemporaryUrlBlobSigner.java
@@ -0,0 +1,109 @@
+/*
+ * 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.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;
+
+import org.jclouds.blobstore.BlobRequestSigner;
+import org.jclouds.blobstore.domain.Blob;
+import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
+import org.jclouds.date.TimeStamp;
+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 {
+
+ @Inject
+ protected RegionScopedTemporaryUrlBlobSigner(@Region Supplier