Issue 80: public acl for atmos

This commit is contained in:
Adrian Cole 2011-06-03 02:07:12 -07:00
parent 9257f3a1e6
commit 1f535ebb6f
21 changed files with 475 additions and 86 deletions

View File

@ -44,7 +44,9 @@ import org.jclouds.atmos.functions.ParseObjectFromHeadersAndHttpContent;
import org.jclouds.atmos.functions.ParseSystemMetadataFromHeaders;
import org.jclouds.atmos.functions.ParseUserMetadataFromHeaders;
import org.jclouds.atmos.functions.ReturnEndpointIfAlreadyExists;
import org.jclouds.atmos.functions.ReturnTrueIfGroupACLIsOtherRead;
import org.jclouds.atmos.options.ListOptions;
import org.jclouds.atmos.options.PutOptions;
import org.jclouds.blobstore.functions.ThrowContainerNotFoundOn404;
import org.jclouds.blobstore.functions.ThrowKeyNotFoundOn404;
import org.jclouds.http.options.GetOptions;
@ -107,7 +109,7 @@ public interface AtmosAsyncClient {
@ExceptionParser(ReturnEndpointIfAlreadyExists.class)
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Consumes(MediaType.WILDCARD)
ListenableFuture<URI> createDirectory(@PathParam("directoryName") String directoryName);
ListenableFuture<URI> createDirectory(@PathParam("directoryName") String directoryName, PutOptions... options);
/**
* @see AtmosClient#createFile
@ -117,7 +119,8 @@ public interface AtmosAsyncClient {
@Consumes(MediaType.WILDCARD)
ListenableFuture<URI> createFile(
@PathParam("parent") String parent,
@PathParam("name") @ParamParser(AtmosObjectName.class) @BinderParam(BindMetadataToHeaders.class) AtmosObject object);
@PathParam("name") @ParamParser(AtmosObjectName.class) @BinderParam(BindMetadataToHeaders.class) AtmosObject object,
PutOptions... options);
/**
* @see AtmosClient#updateFile
@ -128,7 +131,8 @@ public interface AtmosAsyncClient {
@Consumes(MediaType.WILDCARD)
ListenableFuture<Void> updateFile(
@PathParam("parent") String parent,
@PathParam("name") @ParamParser(AtmosObjectName.class) @BinderParam(BindMetadataToHeaders.class) AtmosObject object);
@PathParam("name") @ParamParser(AtmosObjectName.class) @BinderParam(BindMetadataToHeaders.class) AtmosObject object,
PutOptions... options);
/**
* @see AtmosClient#readFile
@ -190,12 +194,14 @@ public interface AtmosAsyncClient {
@Consumes(MediaType.WILDCARD)
ListenableFuture<Boolean> pathExists(@PathParam("path") String path);
// signature currently doesn't work
// @POST
// @QueryParams(keys = "acl")
// @Headers(keys = { "x-emc-useracl", "x-emc-groupacl" }, values = { "root=FULL_CONTROL",
// "other=READ" })
// @Consumes(MediaType.WILDCARD)
// void makePublic(@Endpoint URI url);
/**
* @see AtmosClient#isPublic
*/
@HEAD
@ResponseParser(ReturnTrueIfGroupACLIsOtherRead.class)
@Path("/{path}")
@Consumes(MediaType.WILDCARD)
@ExceptionParser(ReturnFalseOnNotFoundOr404.class)
ListenableFuture<Boolean> isPublic(@PathParam("path") String path);
}

View File

@ -27,6 +27,7 @@ import org.jclouds.atmos.domain.DirectoryEntry;
import org.jclouds.atmos.domain.SystemMetadata;
import org.jclouds.atmos.domain.UserMetadata;
import org.jclouds.atmos.options.ListOptions;
import org.jclouds.atmos.options.PutOptions;
import org.jclouds.concurrent.Timeout;
import org.jclouds.http.options.GetOptions;
@ -52,13 +53,13 @@ public interface AtmosClient {
BoundedSet<? extends DirectoryEntry> listDirectory(String directoryName, ListOptions... options);
URI createDirectory(String directoryName);
URI createDirectory(String directoryName, PutOptions... options);
@Timeout(duration = 10, timeUnit = TimeUnit.MINUTES)
URI createFile(String parent, AtmosObject object);
URI createFile(String parent, AtmosObject object, PutOptions... options);
@Timeout(duration = 10, timeUnit = TimeUnit.MINUTES)
void updateFile(String parent, AtmosObject object);
void updateFile(String parent, AtmosObject object, PutOptions... options);
@Timeout(duration = 10, timeUnit = TimeUnit.MINUTES)
AtmosObject readFile(String path, GetOptions... options);
@ -73,4 +74,6 @@ public interface AtmosClient {
boolean pathExists(String path);
boolean isPublic(String path);
}

View File

@ -19,8 +19,10 @@
package org.jclouds.atmos.blobstore;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.atmos.options.PutOptions.Builder.publicRead;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
@ -79,6 +81,7 @@ public class AtmosAsyncBlobStore extends BaseAsyncBlobStore {
private final Crypto crypto;
private final BlobToHttpGetOptions blob2ObjectGetOptions;
private final Provider<FetchBlobMetadata> fetchBlobMetadataProvider;
private final Map<String, Boolean> isPublic;
@Inject
AtmosAsyncBlobStore(BlobStoreContext context, BlobUtils blobUtils,
@ -87,7 +90,8 @@ public class AtmosAsyncBlobStore extends BaseAsyncBlobStore {
ObjectToBlob object2Blob, ObjectToBlobMetadata object2BlobMd, BlobToObject blob2Object,
BlobStoreListOptionsToListOptions container2ContainerListOptions,
DirectoryEntryListToResourceMetadataList container2ResourceList, Crypto crypto,
BlobToHttpGetOptions blob2ObjectGetOptions, Provider<FetchBlobMetadata> fetchBlobMetadataProvider) {
BlobToHttpGetOptions blob2ObjectGetOptions, Provider<FetchBlobMetadata> fetchBlobMetadataProvider,
Map<String, Boolean> isPublic) {
super(context, blobUtils, service, defaultLocation, locations);
this.blob2ObjectGetOptions = checkNotNull(blob2ObjectGetOptions, "blob2ObjectGetOptions");
this.sync = checkNotNull(sync, "sync");
@ -100,6 +104,7 @@ public class AtmosAsyncBlobStore extends BaseAsyncBlobStore {
this.object2BlobMd = checkNotNull(object2BlobMd, "object2BlobMd");
this.crypto = checkNotNull(crypto, "crypto");
this.fetchBlobMetadataProvider = checkNotNull(fetchBlobMetadataProvider, "fetchBlobMetadataProvider");
this.isPublic = checkNotNull(isPublic, "isPublic");
}
/**
@ -231,11 +236,18 @@ public class AtmosAsyncBlobStore extends BaseAsyncBlobStore {
*/
@Override
public ListenableFuture<String> putBlob(final String container, final Blob blob) {
final org.jclouds.atmos.options.PutOptions options = new org.jclouds.atmos.options.PutOptions();
try {
if (isPublic.get(container + "/"))
options.publicRead();
} catch (NullPointerException e) {
// MapMaker
}
return Futures.makeListenable(service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return AtmosUtils.putBlob(sync, crypto, blob2Object, container, blob);
return AtmosUtils.putBlob(sync, crypto, blob2Object, container, blob, options);
}
@Override
@ -264,7 +276,13 @@ public class AtmosAsyncBlobStore extends BaseAsyncBlobStore {
public ListenableFuture<Boolean> createContainerInLocation(Location location, String container,
CreateContainerOptions options) {
if (options.isPublicRead())
throw new UnsupportedOperationException("publicRead");
return Futures.compose(async.createDirectory(container, publicRead()), new Function<URI, Boolean>() {
public Boolean apply(URI from) {
return true;
}
}, service);
return createContainerInLocation(location, container);
}

View File

@ -29,6 +29,7 @@ import javax.inject.Singleton;
import org.jclouds.atmos.AtmosAsyncClient;
import org.jclouds.atmos.blobstore.functions.BlobToObject;
import org.jclouds.atmos.domain.AtmosObject;
import org.jclouds.atmos.options.PutOptions;
import org.jclouds.blobstore.BlobRequestSigner;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
@ -58,7 +59,7 @@ public class AtmosBlobRequestSigner implements BlobRequestSigner {
this.blob2ObjectGetOptions = checkNotNull(blob2ObjectGetOptions, "blob2ObjectGetOptions");
this.getMethod = AtmosAsyncClient.class.getMethod("readFile", String.class, GetOptions[].class);
this.deleteMethod = AtmosAsyncClient.class.getMethod("deletePath", String.class);
this.createMethod = AtmosAsyncClient.class.getMethod("createFile", String.class, AtmosObject.class);
this.createMethod = AtmosAsyncClient.class.getMethod("createFile", String.class, AtmosObject.class, PutOptions[].class);
}
@Override

View File

@ -19,7 +19,9 @@
package org.jclouds.atmos.blobstore;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.atmos.options.PutOptions.Builder.publicRead;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
@ -67,6 +69,7 @@ public class AtmosBlobStore extends BaseBlobStore {
private final Crypto crypto;
private final BlobToHttpGetOptions blob2ObjectGetOptions;
private final Provider<FetchBlobMetadata> fetchBlobMetadataProvider;
private final Map<String, Boolean> isPublic;
@Inject
AtmosBlobStore(BlobStoreContext context, BlobUtils blobUtils, Supplier<Location> defaultLocation,
@ -74,7 +77,8 @@ public class AtmosBlobStore extends BaseBlobStore {
ObjectToBlobMetadata object2BlobMd, BlobToObject blob2Object,
BlobStoreListOptionsToListOptions container2ContainerListOptions,
DirectoryEntryListToResourceMetadataList container2ResourceList, Crypto crypto,
BlobToHttpGetOptions blob2ObjectGetOptions, Provider<FetchBlobMetadata> fetchBlobMetadataProvider) {
BlobToHttpGetOptions blob2ObjectGetOptions, Provider<FetchBlobMetadata> fetchBlobMetadataProvider,
Map<String, Boolean> isPublic) {
super(context, blobUtils, defaultLocation, locations);
this.blob2ObjectGetOptions = checkNotNull(blob2ObjectGetOptions, "blob2ObjectGetOptions");
this.sync = checkNotNull(sync, "sync");
@ -86,6 +90,7 @@ public class AtmosBlobStore extends BaseBlobStore {
this.object2BlobMd = checkNotNull(object2BlobMd, "object2BlobMd");
this.crypto = checkNotNull(crypto, "crypto");
this.fetchBlobMetadataProvider = checkNotNull(fetchBlobMetadataProvider, "fetchBlobMetadataProvider");
this.isPublic = checkNotNull(isPublic, "isPublic");
}
/**
@ -205,7 +210,14 @@ public class AtmosBlobStore extends BaseBlobStore {
*/
@Override
public String putBlob(final String container, final Blob blob) {
return AtmosUtils.putBlob(sync, crypto, blob2Object, container, blob);
final org.jclouds.atmos.options.PutOptions options = new org.jclouds.atmos.options.PutOptions();
try {
if (isPublic.get(container + "/"))
options.publicRead();
} catch (NullPointerException e) {
// MapMaker
}
return AtmosUtils.putBlob(sync, crypto, blob2Object, container, blob, options);
}
/**
@ -229,8 +241,10 @@ public class AtmosBlobStore extends BaseBlobStore {
@Override
public boolean createContainerInLocation(Location location, String container, CreateContainerOptions options) {
if (options.isPublicRead())
throw new UnsupportedOperationException("publicRead");
if (options.isPublicRead()) {
sync.createDirectory(container, publicRead());
return true;
}
return createContainerInLocation(location, container);
}
}

View File

@ -18,6 +18,11 @@
*/
package org.jclouds.atmos.blobstore.config;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.inject.Singleton;
import org.jclouds.atmos.AtmosAsyncClient;
import org.jclouds.atmos.AtmosClient;
import org.jclouds.atmos.blobstore.AtmosAsyncBlobStore;
@ -34,7 +39,10 @@ import org.jclouds.blobstore.internal.BlobStoreContextImpl;
import org.jclouds.blobstore.strategy.ContainsValueInListStrategy;
import org.jclouds.location.config.JustProviderLocationModule;
import com.google.common.base.Function;
import com.google.common.collect.MapMaker;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
@ -57,4 +65,19 @@ public class AtmosBlobStoreContextModule extends AbstractModule {
bind(BlobRequestSigner.class).to(AtmosBlobRequestSigner.class);
install(new JustProviderLocationModule());
}
@Provides
@Singleton
protected Map<String, Boolean> isPublic(final AtmosClient client) {
return new MapMaker().expireAfterWrite(30, TimeUnit.SECONDS).makeComputingMap(new Function<String, Boolean>() {
public Boolean apply(String directory) {
return client.isPublic(directory);
}
@Override
public String toString() {
return "isPublic()";
}
});
}
}

View File

@ -36,7 +36,8 @@ public class BlobToContentMetadata implements Function<BlobMetadata, MutableCont
public MutableContentMetadata apply(BlobMetadata base) {
MutableBlobMetadataImpl to = new MutableBlobMetadataImpl();
HttpUtils.copy(base.getContentMetadata(), to.getContentMetadata());
return new DelegatingMutableContentMetadata(base.getName(), to.getContentMetadata());
return new DelegatingMutableContentMetadata(base.getUri(), base.getName(), base.getUri() != null ? base.getUri()
.getPath() : null, to.getContentMetadata());
}
}

View File

@ -27,6 +27,7 @@ import javax.inject.Singleton;
import org.jclouds.atmos.domain.AtmosObject;
import org.jclouds.atmos.domain.FileType;
import org.jclouds.atmos.filters.ShareUrl;
import org.jclouds.atmos.functions.AtmosObjectName;
import org.jclouds.blobstore.domain.MutableBlobMetadata;
import org.jclouds.blobstore.domain.StorageType;
@ -34,7 +35,9 @@ import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl;
import org.jclouds.http.HttpUtils;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
/**
@ -43,13 +46,16 @@ import com.google.common.collect.Maps;
@Singleton
public class ObjectToBlobMetadata implements Function<AtmosObject, MutableBlobMetadata> {
private final AtmosObjectName objectName;
private static final Set<String> systemMetadata = ImmutableSet.of("atime", "mtime", "ctime",
"itime", "type", "uid", "gid", "objectid", "objname", "size", "nlink", "policyname",
"content-md5");
private final ShareUrl shareUrl;
private static final Set<String> systemMetadata = ImmutableSet.of("atime", "mtime", "ctime", "itime", "type", "uid",
"gid", "objectid", "objname", "size", "nlink", "policyname", "content-md5");
@Inject
protected ObjectToBlobMetadata(AtmosObjectName objectName) {
protected ObjectToBlobMetadata(AtmosObjectName objectName, ShareUrl shareUrl)
throws SecurityException, NoSuchMethodException {
this.objectName = objectName;
this.shareUrl = shareUrl;
}
public MutableBlobMetadata apply(AtmosObject from) {
@ -60,6 +66,10 @@ public class ObjectToBlobMetadata implements Function<AtmosObject, MutableBlobMe
to.setLastModified(from.getSystemMetadata().getLastUserDataModification());
HttpUtils.copy(from.getContentMetadata(), to.getContentMetadata());
to.setName(objectName.apply(from));
to.setUri(from.getContentMetadata().getUri());
to.setContainer(Iterables.get(Splitter.on('/').split(from.getContentMetadata().getPath()),0));
if (from.getAllHeaders().containsEntry("x-emc-groupacl", "other=READ"))
to.setPublicUri(shareUrl.apply(from.getContentMetadata().getPath()));
if (from.getSystemMetadata().getType() == FileType.DIRECTORY) {
to.setType(StorageType.FOLDER);
} else {

View File

@ -18,6 +18,7 @@
*/
package org.jclouds.atmos.config;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import javax.inject.Named;
@ -43,8 +44,8 @@ import com.google.common.base.Suppliers;
import com.google.inject.Provides;
/**
* Configures the EMC Atmos Online Storage authentication service connection,
* including logging and http transport.
* Configures the EMC Atmos Online Storage authentication service connection, including logging and
* http transport.
*
* @author Adrian Cole
*/
@ -82,6 +83,12 @@ public class AtmosRestClientModule extends RestClientModule<AtmosClient, AtmosAs
}, seconds, TimeUnit.SECONDS);
}
@Provides
@TimeStamp
protected Long provideShareableUrlTimeout() {
return new Date().getTime() + TimeUnit.HOURS.toMillis(1);
}
@Override
protected void bindErrorHandlers() {
bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ParseAtmosErrorFromXmlContent.class);

View File

@ -18,6 +18,8 @@
*/
package org.jclouds.atmos.domain;
import java.net.URI;
import org.jclouds.atmos.domain.internal.DelegatingMutableContentMetadata;
import com.google.inject.ImplementedBy;
@ -34,4 +36,11 @@ public interface MutableContentMetadata extends org.jclouds.io.MutableContentMet
public void setName(String name);
public URI getUri();
public void setUri(URI uri);
public String getPath();
public void setPath(String path);
}

View File

@ -165,7 +165,7 @@ public class AtmosObjectImpl extends PayloadEnclosingImpl implements AtmosObject
@Override
public void setPayload(Payload data) {
this.payload = data;
this.contentMetadata = new DelegatingMutableContentMetadata(contentMetadata.getName(),
payload.getContentMetadata());
this.contentMetadata = new DelegatingMutableContentMetadata(contentMetadata.getUri(), contentMetadata.getName(),
contentMetadata.getPath(), payload.getContentMetadata());
}
}

View File

@ -18,6 +18,8 @@
*/
package org.jclouds.atmos.domain.internal;
import java.net.URI;
import org.jclouds.atmos.domain.MutableContentMetadata;
import org.jclouds.io.ContentMetadataBuilder;
import org.jclouds.io.payloads.BaseMutableContentMetadata;
@ -29,16 +31,21 @@ import com.google.common.collect.Multimap;
* @author Adrian Cole
*/
public class DelegatingMutableContentMetadata implements MutableContentMetadata {
private URI uri;
private String name;
private String path;
private final org.jclouds.io.MutableContentMetadata delegate;
public DelegatingMutableContentMetadata() {
this(null, new BaseMutableContentMetadata());
this(null, null, null, new BaseMutableContentMetadata());
}
public DelegatingMutableContentMetadata(String name, org.jclouds.io.MutableContentMetadata delegate) {
public DelegatingMutableContentMetadata(URI uri, String name, String path,
org.jclouds.io.MutableContentMetadata delegate) {
this.uri = uri;
this.name = name;
this.delegate = delegate;
this.path = path;
}
@Override
@ -90,15 +97,10 @@ public class DelegatingMutableContentMetadata implements MutableContentMetadata
if (getClass() != obj.getClass())
return false;
DelegatingMutableContentMetadata other = (DelegatingMutableContentMetadata) obj;
if (delegate == null) {
if (other.delegate != null)
if (uri == null) {
if (other.uri != null)
return false;
} else if (!delegate.equals(other.delegate))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
} else if (!uri.equals(other.uri))
return false;
return true;
}
@ -107,14 +109,13 @@ public class DelegatingMutableContentMetadata implements MutableContentMetadata
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((delegate == null) ? 0 : delegate.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((uri == null) ? 0 : uri.hashCode());
return result;
}
@Override
public String toString() {
return "[name=" + name + ", delegate=" + delegate + "]";
return "[uri=" + uri + ", name=" + name + ", path=" + path + ", delegate=" + delegate + "]";
}
public org.jclouds.io.MutableContentMetadata getDelegate() {
@ -162,4 +163,24 @@ public class DelegatingMutableContentMetadata implements MutableContentMetadata
return delegate.toBuilder();
}
@Override
public URI getUri() {
return uri;
}
@Override
public void setUri(URI uri) {
this.uri = uri;
}
@Override
public String getPath() {
return path;
}
@Override
public void setPath(String path) {
this.path = path;
}
}

View File

@ -0,0 +1,107 @@
/**
*
* Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.filters;
import static org.jclouds.Constants.LOGGER_SIGNATURE;
import static org.jclouds.Constants.PROPERTY_CREDENTIAL;
import static org.jclouds.Constants.PROPERTY_IDENTITY;
import java.net.URI;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.core.UriBuilder;
import org.jclouds.crypto.Crypto;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.date.TimeStamp;
import org.jclouds.http.HttpException;
import org.jclouds.io.InputSuppliers;
import org.jclouds.location.Provider;
import org.jclouds.logging.Logger;
import com.google.common.base.Function;
/**
* Signs the EMC Atmos Online Storage request.
*
* @see <a href="https://community.emc.com/community/labs/atmos_online" />
* @author Adrian Cole
*
*/
@Singleton
public class ShareUrl implements Function<String, URI> {
private final String uid;
private final byte[] key;
private final URI provider;
private final javax.inject.Provider<Long> timeStampProvider;
private final javax.inject.Provider<UriBuilder> uriBuilders;
private final Crypto crypto;
@Resource
Logger logger = Logger.NULL;
@Resource
@Named(LOGGER_SIGNATURE)
Logger signatureLog = Logger.NULL;
@Inject
public ShareUrl(@Named(PROPERTY_IDENTITY) String uid, @Named(PROPERTY_CREDENTIAL) String encodedKey,
@Provider URI provider, @TimeStamp javax.inject.Provider<Long> timeStampProvider,
javax.inject.Provider<UriBuilder> uriBuilders, Crypto crypto) {
this.uid = uid;
this.key = CryptoStreams.base64(encodedKey);
this.provider = provider;
this.uriBuilders = uriBuilders;
this.timeStampProvider = timeStampProvider;
this.crypto = crypto;
}
@Override
public URI apply(String path) throws HttpException {
String requestedResource = new StringBuilder().append("/rest/namespace/").append(path).toString();
long expires = timeStampProvider.get();
String signature = signString(createStringToSign(requestedResource, expires));
return uriBuilders.get().uri(provider).path(requestedResource).queryParam("uid", uid).queryParam("expires",
expires).queryParam("signature", signature).build();
}
public String createStringToSign(String requestedResource, long expires) {
StringBuilder toSign = new StringBuilder();
toSign.append("GET\n");
toSign.append(requestedResource.toLowerCase()).append("\n");
toSign.append(uid).append("\n");
toSign.append(expires);
return toSign.toString();
}
public String signString(String toSign) {
String signature;
try {
signature = CryptoStreams.base64(CryptoStreams.mac(InputSuppliers.of(toSign), crypto.hmacSHA1(key)));
} catch (Exception e) {
throw new HttpException("error signing request", e);
}
return signature;
}
}

View File

@ -21,12 +21,16 @@ package org.jclouds.atmos.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.http.HttpUtils.attemptToParseSizeAndRangeFromHeaders;
import java.net.URI;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.atmos.domain.AtmosObject;
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse;
import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import com.google.common.base.Function;
@ -36,12 +40,14 @@ import com.google.common.base.Function;
* @see ParseMetadataFromHeaders
* @author Adrian Cole
*/
@Singleton
public class ParseObjectFromHeadersAndHttpContent implements Function<HttpResponse, AtmosObject> {
public class ParseObjectFromHeadersAndHttpContent implements Function<HttpResponse, AtmosObject>,
InvocationContext<ParseObjectFromHeadersAndHttpContent> {
private final ParseSystemMetadataFromHeaders systemMetadataParser;
private final ParseUserMetadataFromHeaders userMetadataParser;
private final AtmosObject.Factory objectProvider;
private URI uri;
private String path;
@Inject
public ParseObjectFromHeadersAndHttpContent(ParseSystemMetadataFromHeaders systemMetadataParser,
@ -63,9 +69,22 @@ public class ParseObjectFromHeadersAndHttpContent implements Function<HttpRespon
checkNotNull(from, "http response");
AtmosObject object = objectProvider.create(systemMetadataParser.apply(from), userMetadataParser.apply(from));
object.getContentMetadata().setName(object.getSystemMetadata().getObjectName());
object.getContentMetadata().setPath(path);
object.getContentMetadata().setUri(uri);
object.getAllHeaders().putAll(from.getHeaders());
object.setPayload(from.getPayload());
object.getContentMetadata().setContentLength(attemptToParseSizeAndRangeFromHeaders(from));
return object;
}
@Override
public ParseObjectFromHeadersAndHttpContent setContext(HttpRequest request) {
this.uri = request.getEndpoint();
return setPath(GeneratedHttpRequest.class.cast(request).getArgs().get(0).toString());
}
private ParseObjectFromHeadersAndHttpContent setPath(String path) {
this.path = path;
return this;
}
}

View File

@ -16,27 +16,24 @@
* limitations under the License.
* ====================================================================
*/
package org.jclouds.atmos.blobstore.functions;
package org.jclouds.atmos.functions;
import static org.testng.Assert.assertEquals;
import static com.google.common.base.Preconditions.checkNotNull;
import org.testng.annotations.Test;
import javax.inject.Singleton;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.jclouds.http.HttpResponse;
import com.google.common.base.Function;
/**
* Tests behavior of {@code ObjectToBlobMetadata}
*
* @author Adrian Cole
*/
@Test(groups = "unit")
public class ObjectToBlobMetadataTest {
@Singleton
public class ReturnTrueIfGroupACLIsOtherRead implements Function<HttpResponse, Boolean> {
public void testFromWhenTypeIsDirectory() {
Injector injector = Guice.createInjector();
injector.getInstance(ObjectToBlobMetadata.class);
assertEquals("", "");
public Boolean apply(HttpResponse from) {
checkNotNull(from, "http response");
return from.getHeaders().containsEntry("x-emc-groupacl", "other=READ");
}
}

View File

@ -0,0 +1,65 @@
/**
*
* Copyright (C) 2011 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* Licensed 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.options;
import org.jclouds.http.options.BaseHttpRequestOptions;
/**
* Contains options supported in the REST API for the PUT operations.
* <p/>
* <h2>
* Usage</h2> The recommended way to instantiate a PutOptions object is to statically import
* PutOptions.Builder.* and invoke a static creation method followed by an instance mutator (if
* needed):
* <p/>
* <code>
* import org.jclouds.atmos.options.PutOptions.Builder.*
* import org.jclouds.atmos.AtmosClient;
*
* AtmosClient connection = // get connection
* connection.createDirectory("directory", publicRead());
* <code>
*
* @author Adrian Cole
*
*/
public class PutOptions extends BaseHttpRequestOptions {
public static final PutOptions NONE = new PutOptions();
/**
* Add public access to all users
*
*/
public PutOptions publicRead() {
this.replaceHeader("x-emc-useracl", "root=FULL_CONTROL");
this.replaceHeader("x-emc-groupacl", "other=READ");
return this;
}
public static class Builder {
/**
* @see PutOptions#publicRead
*/
public static PutOptions publicRead() {
PutOptions options = new PutOptions();
return options.publicRead();
}
}
}

View File

@ -28,6 +28,7 @@ import org.jclouds.atmos.AtmosClient;
import org.jclouds.atmos.blobstore.functions.BlobToObject;
import org.jclouds.atmos.domain.AtmosError;
import org.jclouds.atmos.filters.SignRequest;
import org.jclouds.atmos.options.PutOptions;
import org.jclouds.atmos.xml.ErrorHandler;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.crypto.Crypto;
@ -56,8 +57,8 @@ public class AtmosUtils {
@Inject
Provider<ErrorHandler> errorHandlerProvider;
public AtmosError parseAtmosErrorFromContent(HttpCommand command, HttpResponse response,
InputStream content) throws HttpException {
public AtmosError parseAtmosErrorFromContent(HttpCommand command, HttpResponse response, InputStream content)
throws HttpException {
AtmosError error = (AtmosError) factory.create(errorHandlerProvider.get()).parse(content);
if (error.getCode() == 1032) {
error.setStringSigned(signer.createStringToSign(command.getCurrentRequest()));
@ -66,11 +67,11 @@ public class AtmosUtils {
}
public static String putBlob(final AtmosClient sync, Crypto crypto, BlobToObject blob2Object,
String container, Blob blob) {
public static String putBlob(final AtmosClient sync, Crypto crypto, BlobToObject blob2Object, String container,
Blob blob, PutOptions options) {
final String path = container + "/" + blob.getMetadata().getName();
deleteAndEnsureGone(sync, path);
sync.createFile(container, blob2Object.apply(blob));
sync.createFile(container, blob2Object.apply(blob), options);
return path;
}

View File

@ -21,7 +21,6 @@ package org.jclouds.atmos;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Properties;
@ -35,7 +34,9 @@ import org.jclouds.atmos.functions.ParseDirectoryListFromContentAndHeaders;
import org.jclouds.atmos.functions.ParseObjectFromHeadersAndHttpContent;
import org.jclouds.atmos.functions.ParseSystemMetadataFromHeaders;
import org.jclouds.atmos.functions.ReturnEndpointIfAlreadyExists;
import org.jclouds.atmos.functions.ReturnTrueIfGroupACLIsOtherRead;
import org.jclouds.atmos.options.ListOptions;
import org.jclouds.atmos.options.PutOptions;
import org.jclouds.blobstore.binders.BindBlobToMultipartFormTest;
import org.jclouds.blobstore.functions.ThrowContainerNotFoundOn404;
import org.jclouds.blobstore.functions.ThrowKeyNotFoundOn404;
@ -49,6 +50,7 @@ import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.RestClientTest;
import org.jclouds.rest.RestContextFactory;
import org.jclouds.rest.RestContextSpec;
import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404;
import org.jclouds.rest.internal.RestAnnotationProcessor;
@ -71,8 +73,7 @@ public class AtmosAsyncClientTest extends RestClientTest<AtmosAsyncClient> {
private BlobToObject blobToObject;
public void testListDirectories() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("listDirectories", Array.newInstance(ListOptions.class, 0)
.getClass());
Method method = AtmosAsyncClient.class.getMethod("listDirectories", ListOptions[].class);
HttpRequest request = processor.createRequest(method);
assertRequestLineEquals(request, "GET https://accesspoint.atmosonline.com/rest/namespace HTTP/1.1");
@ -87,8 +88,7 @@ public class AtmosAsyncClientTest extends RestClientTest<AtmosAsyncClient> {
}
public void testListDirectory() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("listDirectory", String.class, Array.newInstance(
ListOptions.class, 0).getClass());
Method method = AtmosAsyncClient.class.getMethod("listDirectory", String.class, ListOptions[].class);
HttpRequest request = processor.createRequest(method, "directory");
assertRequestLineEquals(request, "GET https://accesspoint.atmosonline.com/rest/namespace/directory/ HTTP/1.1");
@ -103,8 +103,7 @@ public class AtmosAsyncClientTest extends RestClientTest<AtmosAsyncClient> {
}
public void testListDirectoriesOptions() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("listDirectories", Array.newInstance(ListOptions.class, 0)
.getClass());
Method method = AtmosAsyncClient.class.getMethod("listDirectories", ListOptions[].class);
HttpRequest request = processor.createRequest(method, new ListOptions().limit(1).token("asda"));
assertRequestLineEquals(request, "GET https://accesspoint.atmosonline.com/rest/namespace HTTP/1.1");
@ -119,8 +118,7 @@ public class AtmosAsyncClientTest extends RestClientTest<AtmosAsyncClient> {
}
public void testListDirectoryOptions() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("listDirectory", String.class, Array.newInstance(
ListOptions.class, 0).getClass());
Method method = AtmosAsyncClient.class.getMethod("listDirectory", String.class, ListOptions[].class);
HttpRequest request = processor.createRequest(method, "directory", new ListOptions().limit(1).token("asda"));
assertRequestLineEquals(request, "GET https://accesspoint.atmosonline.com/rest/namespace/directory/ HTTP/1.1");
@ -135,7 +133,7 @@ public class AtmosAsyncClientTest extends RestClientTest<AtmosAsyncClient> {
}
public void testCreateDirectory() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("createDirectory", String.class);
Method method = AtmosAsyncClient.class.getMethod("createDirectory", String.class, PutOptions[].class);
HttpRequest request = processor.createRequest(method, "dir");
assertRequestLineEquals(request, "POST https://accesspoint.atmosonline.com/rest/namespace/dir/ HTTP/1.1");
@ -149,8 +147,25 @@ public class AtmosAsyncClientTest extends RestClientTest<AtmosAsyncClient> {
checkFilters(request);
}
public void testCreateDirectoryOptions() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("createDirectory", String.class, PutOptions[].class);
HttpRequest request = processor.createRequest(method, "dir", PutOptions.Builder.publicRead());
assertRequestLineEquals(request, "POST https://accesspoint.atmosonline.com/rest/namespace/dir/ HTTP/1.1");
assertNonPayloadHeadersEqual(request, HttpHeaders.ACCEPT
+ ": */*\nx-emc-groupacl: other=READ\nx-emc-useracl: root=FULL_CONTROL\n");
assertPayloadEquals(request, "", "application/octet-stream", false);
assertResponseParserClassEquals(method, request, ParseURIFromListOrLocationHeaderIf20x.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnEndpointIfAlreadyExists.class);
checkFilters(request);
}
public void testCreateFile() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("createFile", String.class, AtmosObject.class);
Method method = AtmosAsyncClient.class.getMethod("createFile", String.class, AtmosObject.class,
PutOptions[].class);
HttpRequest request = processor.createRequest(method, "dir", blobToObject
.apply(BindBlobToMultipartFormTest.TEST_BLOB));
@ -165,8 +180,27 @@ public class AtmosAsyncClientTest extends RestClientTest<AtmosAsyncClient> {
checkFilters(request);
}
public void testCreateFileOptions() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("createFile", String.class, AtmosObject.class,
PutOptions[].class);
HttpRequest request = processor.createRequest(method, "dir", blobToObject
.apply(BindBlobToMultipartFormTest.TEST_BLOB), PutOptions.Builder.publicRead());
assertRequestLineEquals(request, "POST https://accesspoint.atmosonline.com/rest/namespace/dir/hello HTTP/1.1");
assertNonPayloadHeadersEqual(request, HttpHeaders.ACCEPT
+ ": */*\nx-emc-groupacl: other=READ\nx-emc-useracl: root=FULL_CONTROL\n");
assertPayloadEquals(request, "hello", "text/plain", false);
assertResponseParserClassEquals(method, request, ParseURIFromListOrLocationHeaderIf20x.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
checkFilters(request);
}
public void testUpdateFile() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("updateFile", String.class, AtmosObject.class);
Method method = AtmosAsyncClient.class.getMethod("updateFile", String.class, AtmosObject.class,
PutOptions[].class);
HttpRequest request = processor.createRequest(method, "dir", blobToObject
.apply(BindBlobToMultipartFormTest.TEST_BLOB));
@ -181,6 +215,24 @@ public class AtmosAsyncClientTest extends RestClientTest<AtmosAsyncClient> {
checkFilters(request);
}
public void testUpdateFileOptions() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("updateFile", String.class, AtmosObject.class,
PutOptions[].class);
HttpRequest request = processor.createRequest(method, "dir", blobToObject
.apply(BindBlobToMultipartFormTest.TEST_BLOB), PutOptions.Builder.publicRead());
assertRequestLineEquals(request, "PUT https://accesspoint.atmosonline.com/rest/namespace/dir/hello HTTP/1.1");
assertNonPayloadHeadersEqual(request, HttpHeaders.ACCEPT
+ ": */*\nx-emc-groupacl: other=READ\nx-emc-useracl: root=FULL_CONTROL\n");
assertPayloadEquals(request, "hello", "text/plain", false);
assertResponseParserClassEquals(method, request, ReleasePayloadAndReturn.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ThrowKeyNotFoundOn404.class);
checkFilters(request);
}
public void testReadFile() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("readFile", String.class, GetOptions[].class);
HttpRequest request = processor.createRequest(method, "dir/file");
@ -226,6 +278,21 @@ public class AtmosAsyncClientTest extends RestClientTest<AtmosAsyncClient> {
checkFilters(request);
}
public void testIsPublic() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("isPublic", String.class);
HttpRequest request = processor.createRequest(method, "dir/file");
assertRequestLineEquals(request, "HEAD https://accesspoint.atmosonline.com/rest/namespace/dir/file HTTP/1.1");
assertNonPayloadHeadersEqual(request, HttpHeaders.ACCEPT + ": */*\n");
assertPayloadEquals(request, null, null, false);
assertResponseParserClassEquals(method, request, ReturnTrueIfGroupACLIsOtherRead.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ReturnFalseOnNotFoundOr404.class);
checkFilters(request);
}
public void testNewObject() throws SecurityException, NoSuchMethodException, IOException {
Method method = AtmosAsyncClient.class.getMethod("newObject");
assertEquals(method.getReturnType(), AtmosObject.class);

View File

@ -53,7 +53,7 @@ import com.google.common.collect.Sets;
*
* @author Adrian Cole
*/
@Test(groups = "live", sequential = true)
@Test(groups = "live", singleThreaded = true)
public class AtmosClientLiveTest extends BaseBlobStoreIntegrationTest {
public AtmosClient getApi() {

View File

@ -42,6 +42,7 @@ import org.jclouds.atmos.domain.DirectoryEntry;
import org.jclouds.atmos.domain.SystemMetadata;
import org.jclouds.atmos.domain.UserMetadata;
import org.jclouds.atmos.options.ListOptions;
import org.jclouds.atmos.options.PutOptions;
import org.jclouds.blobstore.TransientAsyncBlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobMetadata;
@ -87,7 +88,8 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
this.service = service;
}
public ListenableFuture<URI> createDirectory(String directoryName) {
@Override
public ListenableFuture<URI> createDirectory(String directoryName, PutOptions... options) {
final String container;
final String path;
if (directoryName.indexOf('/') != -1) {
@ -112,7 +114,8 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
}, service);
}
public ListenableFuture<URI> createFile(String parent, AtmosObject object) {
@Override
public ListenableFuture<URI> createFile(String parent, AtmosObject object, PutOptions... options) {
final String uri = "http://stub/containers/" + parent + "/" + object.getContentMetadata().getName();
String file = object.getContentMetadata().getName();
String container = parent;
@ -132,6 +135,7 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
}, service);
}
@Override
public ListenableFuture<Void> deletePath(String path) {
if (path.indexOf('/') == path.length() - 1) {
// chop off the trailing slash
@ -150,10 +154,12 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
}
}
@Override
public ListenableFuture<SystemMetadata> getSystemMetadata(String path) {
throw new UnsupportedOperationException();
}
@Override
public ListenableFuture<UserMetadata> getUserMetadata(String path) {
if (path.indexOf('/') == -1)
throw new UnsupportedOperationException();
@ -168,6 +174,7 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
}
}
@Override
public ListenableFuture<AtmosObject> headFile(String path) {
String container = path.substring(0, path.indexOf('/'));
path = path.substring(path.indexOf('/') + 1);
@ -178,12 +185,14 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
}
}
@Override
public ListenableFuture<BoundedSet<? extends DirectoryEntry>> listDirectories(ListOptions... optionsList) {
// org.jclouds.blobstore.options.ListOptions options = container2ContainerListOptions
// .apply(optionsList);
return Futures.compose(blobStore.list(), resource2ObjectList, service);
}
@Override
public ListenableFuture<BoundedSet<? extends DirectoryEntry>> listDirectory(String directoryName,
ListOptions... optionsList) {
org.jclouds.blobstore.options.ListContainerOptions options = container2ContainerListOptions.apply(optionsList);
@ -197,10 +206,12 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
return Futures.compose(blobStore.list(container, options), resource2ObjectList, service);
}
@Override
public AtmosObject newObject() {
return this.objectProvider.create(null);
}
@Override
public ListenableFuture<Boolean> pathExists(final String path) {
if (path.indexOf('/') == path.length() - 1) {
// chop off the trailing slash
@ -218,6 +229,7 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
}
}
@Override
public ListenableFuture<AtmosObject> readFile(String path, GetOptions... options) {
String container = path.substring(0, path.indexOf('/'));
String blobName = path.substring(path.indexOf('/') + 1);
@ -225,7 +237,13 @@ public class StubAtmosAsyncClient implements AtmosAsyncClient {
return Futures.compose(blobStore.getBlob(container, blobName, getOptions), blob2Object, service);
}
public ListenableFuture<Void> updateFile(String parent, AtmosObject object) {
@Override
public ListenableFuture<Void> updateFile(String parent, AtmosObject object, PutOptions... options) {
throw new UnsupportedOperationException();
}
@Override
public ListenableFuture<Boolean> isPublic(String path) {
throw new UnsupportedOperationException();
}

View File

@ -47,7 +47,9 @@ public class BaseContainerLiveTest extends BaseBlobStoreIntegrationTest {
BlobMetadata metadata = context.getBlobStore().blobMetadata(containerName, "hello");
assertEquals(Strings2.toStringAndClose(metadata.getPublicUri().toURL().openStream()), TEST_STRING);
assert metadata.getPublicUri() != null : metadata;
assertEquals(Strings2.toStringAndClose(context.utils().http().get(metadata.getPublicUri())), TEST_STRING);
} finally {
// this container is now public, so we can't reuse it directly