Issue 75: cloud files now uses native rackspace object model

git-svn-id: http://jclouds.googlecode.com/svn/trunk@2005 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-10-30 05:55:53 +00:00
parent 1bc88f36f2
commit 903254a6f9
23 changed files with 1002 additions and 195 deletions

View File

@ -38,10 +38,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import org.jclouds.blobstore.binders.BindMapToHeadersWithPrefix; import org.jclouds.blobstore.binders.BindMapToHeadersWithPrefix;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.BoundedSortedSet; import org.jclouds.blobstore.domain.BoundedSortedSet;
import org.jclouds.blobstore.functions.BlobName;
import org.jclouds.blobstore.functions.ReturnVoidOnNotFoundOr404; import org.jclouds.blobstore.functions.ReturnVoidOnNotFoundOr404;
import org.jclouds.blobstore.functions.ThrowContainerNotFoundOn404; import org.jclouds.blobstore.functions.ThrowContainerNotFoundOn404;
import org.jclouds.blobstore.functions.ThrowKeyNotFoundOn404; import org.jclouds.blobstore.functions.ThrowKeyNotFoundOn404;
@ -50,17 +47,21 @@ import org.jclouds.http.functions.ReturnFalseOn404;
import org.jclouds.http.options.GetOptions; import org.jclouds.http.options.GetOptions;
import org.jclouds.rackspace.CloudFiles; import org.jclouds.rackspace.CloudFiles;
import org.jclouds.rackspace.CloudFilesCDN; import org.jclouds.rackspace.CloudFilesCDN;
import org.jclouds.rackspace.cloudfiles.binders.BindCFObjectAsEntity; import org.jclouds.rackspace.cloudfiles.binders.BindCFObjectToEntity;
import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata; import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import org.jclouds.rackspace.cloudfiles.domain.ContainerCDNMetadata; import org.jclouds.rackspace.cloudfiles.domain.ContainerCDNMetadata;
import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata; import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata;
import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import org.jclouds.rackspace.cloudfiles.domain.ObjectInfo;
import org.jclouds.rackspace.cloudfiles.functions.ObjectName;
import org.jclouds.rackspace.cloudfiles.functions.ParseAccountMetadataResponseFromHeaders; import org.jclouds.rackspace.cloudfiles.functions.ParseAccountMetadataResponseFromHeaders;
import org.jclouds.rackspace.cloudfiles.functions.ParseBlobFromHeadersAndHttpContent;
import org.jclouds.rackspace.cloudfiles.functions.ParseBlobMetadataListFromJsonResponse;
import org.jclouds.rackspace.cloudfiles.functions.ParseCdnUriFromHeaders; import org.jclouds.rackspace.cloudfiles.functions.ParseCdnUriFromHeaders;
import org.jclouds.rackspace.cloudfiles.functions.ParseContainerCDNMetadataFromHeaders; import org.jclouds.rackspace.cloudfiles.functions.ParseContainerCDNMetadataFromHeaders;
import org.jclouds.rackspace.cloudfiles.functions.ParseContainerCDNMetadataListFromGsonResponse; import org.jclouds.rackspace.cloudfiles.functions.ParseContainerCDNMetadataListFromGsonResponse;
import org.jclouds.rackspace.cloudfiles.functions.ParseContainerListFromJsonResponse; import org.jclouds.rackspace.cloudfiles.functions.ParseContainerListFromJsonResponse;
import org.jclouds.rackspace.cloudfiles.functions.ParseObjectFromHeadersAndHttpContent;
import org.jclouds.rackspace.cloudfiles.functions.ParseObjectInfoListFromJsonResponse;
import org.jclouds.rackspace.cloudfiles.functions.ParseObjectMetadataFromHeaders; import org.jclouds.rackspace.cloudfiles.functions.ParseObjectMetadataFromHeaders;
import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn404FalseOn409; import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn404FalseOn409;
import org.jclouds.rackspace.cloudfiles.options.ListCdnContainerOptions; import org.jclouds.rackspace.cloudfiles.options.ListCdnContainerOptions;
@ -91,7 +92,7 @@ import org.jclouds.rest.annotations.SkipEncoding;
@Endpoint(CloudFiles.class) @Endpoint(CloudFiles.class)
public interface CloudFilesClient { public interface CloudFilesClient {
Blob newBlob(); CFObject newCFObject();
/** /**
* HEAD operations against an account are performed to retrieve the number of Containers and the * HEAD operations against an account are performed to retrieve the number of Containers and the
@ -142,9 +143,9 @@ public interface CloudFilesClient {
SortedSet<ContainerMetadata> listContainers(ListContainerOptions... options); SortedSet<ContainerMetadata> listContainers(ListContainerOptions... options);
@POST @POST
@Path("{container}/{key}") @Path("{container}/{name}")
boolean setObjectMetadata(@PathParam("container") String container, boolean setObjectMetadata(@PathParam("container") String container,
@PathParam("key") String key, @PathParam("name") String name,
@BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> userMetadata); @BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> userMetadata);
@GET @GET
@ -200,9 +201,9 @@ public interface CloudFilesClient {
@GET @GET
@QueryParams(keys = "format", values = "json") @QueryParams(keys = "format", values = "json")
@ResponseParser(ParseBlobMetadataListFromJsonResponse.class) @ResponseParser(ParseObjectInfoListFromJsonResponse.class)
@Path("{container}") @Path("{container}")
Future<BoundedSortedSet<BlobMetadata>> listObjects(@PathParam("container") String container, Future<BoundedSortedSet<ObjectInfo>> listObjects(@PathParam("container") String container,
ListContainerOptions... options); ListContainerOptions... options);
@HEAD @HEAD
@ -211,29 +212,28 @@ public interface CloudFilesClient {
boolean containerExists(@PathParam("container") String container); boolean containerExists(@PathParam("container") String container);
@PUT @PUT
@Path("{container}/{key}") @Path("{container}/{name}")
@ResponseParser(ParseETagHeader.class) @ResponseParser(ParseETagHeader.class)
Future<String> putObject( Future<String> putObject(
@PathParam("container") String container, @PathParam("container") String container,
@PathParam("key") @ParamParser(BlobName.class) @BinderParam(BindCFObjectAsEntity.class) Blob object); @PathParam("name") @ParamParser(ObjectName.class) @BinderParam(BindCFObjectToEntity.class) CFObject object);
@GET @GET
@ResponseParser(ParseBlobFromHeadersAndHttpContent.class) @ResponseParser(ParseObjectFromHeadersAndHttpContent.class)
@ExceptionParser(ThrowKeyNotFoundOn404.class) @ExceptionParser(ThrowKeyNotFoundOn404.class)
@Path("{container}/{key}") @Path("{container}/{name}")
Future<Blob> getObject(@PathParam("container") String container, @PathParam("key") String key, Future<CFObject> getObject(@PathParam("container") String container, @PathParam("name") String name,
GetOptions... options); GetOptions... options);
@HEAD @HEAD
@ResponseParser(ParseObjectMetadataFromHeaders.class) @ResponseParser(ParseObjectMetadataFromHeaders.class)
@ExceptionParser(ThrowKeyNotFoundOn404.class) @ExceptionParser(ThrowKeyNotFoundOn404.class)
@Path("{container}/{key}") @Path("{container}/{name}")
BlobMetadata getObjectMetadata(@PathParam("container") String container, MutableObjectInfoWithMetadata getObjectInfo(@PathParam("container") String container, @PathParam("name") String name);
@PathParam("key") String key);
@DELETE @DELETE
@ExceptionParser(ReturnVoidOnNotFoundOr404.class) @ExceptionParser(ReturnVoidOnNotFoundOr404.class)
@Path("{container}/{key}") @Path("{container}/{name}")
Future<Void> removeObject(@PathParam("container") String container, @PathParam("key") String key); Future<Void> removeObject(@PathParam("container") String container, @PathParam("name") String name);
} }

View File

@ -26,25 +26,29 @@ package org.jclouds.rackspace.cloudfiles.binders;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import org.jclouds.blobstore.binders.BindBlobToEntityAndUserMetadataToHeadersWithPrefix; import org.jclouds.blobstore.binders.BindBlobToEntityAndUserMetadataToHeadersWithPrefix;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpUtils; import org.jclouds.http.HttpUtils;
import org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants; import org.jclouds.rackspace.cloudfiles.blobstore.functions.ObjectToBlob;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import org.jclouds.rest.Binder;
public class BindCFObjectToEntity implements Binder {
private final BindBlobToEntityAndUserMetadataToHeadersWithPrefix blobBinder;
private final ObjectToBlob object2Blob;
public class BindCFObjectAsEntity extends BindBlobToEntityAndUserMetadataToHeadersWithPrefix {
@Inject @Inject
public BindCFObjectAsEntity( public BindCFObjectToEntity(ObjectToBlob object2Blob,
@Named(CloudFilesConstants.PROPERTY_CLOUDFILES_METADATA_PREFIX) String metadataPrefix) { BindBlobToEntityAndUserMetadataToHeadersWithPrefix blobBinder) {
super(metadataPrefix); this.blobBinder = blobBinder;
this.object2Blob = object2Blob;
} }
public void bindToRequest(HttpRequest request, Object entity) { public void bindToRequest(HttpRequest request, Object entity) {
Blob object = (Blob) entity; CFObject object = (CFObject) entity;
if (object.getContentLength() >= 0) { if (object.getContentLength() != null && object.getContentLength() >= 0) {
checkArgument(object.getContentLength() <= 5 * 1024 * 1024 * 1024, checkArgument(object.getContentLength() <= 5 * 1024 * 1024 * 1024,
"maximum size for put object is 5GB"); "maximum size for put object is 5GB");
request.getHeaders().put(HttpHeaders.CONTENT_LENGTH, object.getContentLength() + ""); request.getHeaders().put(HttpHeaders.CONTENT_LENGTH, object.getContentLength() + "");
@ -52,14 +56,14 @@ public class BindCFObjectAsEntity extends BindBlobToEntityAndUserMetadataToHeade
// Enable "chunked"/"streamed" data, where the size needn't be known in advance. // Enable "chunked"/"streamed" data, where the size needn't be known in advance.
request.getHeaders().put("Transfer-Encoding", "chunked"); request.getHeaders().put("Transfer-Encoding", "chunked");
} }
/**
* rackspace uses ETag header instead of Content-MD5. blobBinder.bindToRequest(request, object2Blob.apply(object));
*/ if (object.getInfo().getHash() != null) {
if (object.getMetadata().getContentMD5() != null) { request.getHeaders().put(HttpHeaders.ETAG,
request.getHeaders().put(HttpHeaders.ETAG,// note it needs to be in hex! HttpUtils.toHexString(object.getInfo().getHash()));
HttpUtils.toHexString(object.getMetadata().getContentMD5())); request.getHeaders().removeAll("Content-MD5");
} }
super.bindToRequest(request, entity);
request.getHeaders().removeAll("Content-MD5");
} }
} }

View File

@ -0,0 +1,33 @@
package org.jclouds.rackspace.cloudfiles.blobstore.functions;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import com.google.common.base.Function;
/**
* @author Adrian Cole
*/
@Singleton
public class BlobToObject implements Function<Blob, CFObject> {
private final BlobToObjectInfo blob2ObjectMd;
private final CFObject.Factory objectProvider;
@Inject
BlobToObject(BlobToObjectInfo blob2ObjectMd, CFObject.Factory objectProvider) {
this.blob2ObjectMd = blob2ObjectMd;
this.objectProvider = objectProvider;
}
public CFObject apply(Blob from) {
CFObject object = objectProvider.create(blob2ObjectMd.apply(from.getMetadata()));
if (from.getContentLength() != null)
object.setContentLength(from.getContentLength());
object.setData(from.getData());
object.setAllHeaders(from.getAllHeaders());
return object;
}
}

View File

@ -0,0 +1,29 @@
package org.jclouds.rackspace.cloudfiles.blobstore.functions;
import javax.inject.Singleton;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import org.jclouds.rackspace.cloudfiles.domain.internal.MutableObjectInfoWithMetadataImpl;
import com.google.common.base.Function;
/**
* @author Adrian Cole
*/
@Singleton
public class BlobToObjectInfo implements Function<BlobMetadata, MutableObjectInfoWithMetadata> {
public MutableObjectInfoWithMetadata apply(BlobMetadata base) {
MutableObjectInfoWithMetadata to = new MutableObjectInfoWithMetadataImpl();
to.setContentType(base.getContentType());
to.setHash(base.getContentMD5());
to.setName(base.getName());
to.setLastModified(base.getLastModified());
if (base.getSize() != null)
to.setBytes(base.getSize());
if (base.getUserMetadata() != null)
to.getMetadata().putAll(base.getUserMetadata());
return to;
}
}

View File

@ -0,0 +1,34 @@
package org.jclouds.rackspace.cloudfiles.blobstore.functions;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.Blob.Factory;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import com.google.common.base.Function;
/**
* @author Adrian Cole
*/
@Singleton
public class ObjectToBlob implements Function<CFObject, Blob> {
private final Blob.Factory blobFactory;
private final ObjectToBlobMetadata object2BlobMd;
@Inject
ObjectToBlob(Factory blobFactory, ObjectToBlobMetadata object2BlobMd) {
this.blobFactory = blobFactory;
this.object2BlobMd = object2BlobMd;
}
public Blob apply(CFObject from) {
Blob blob = blobFactory.create(object2BlobMd.apply(from.getInfo()));
if (from.getContentLength() != null)
blob.setContentLength(from.getContentLength());
blob.setData(from.getData());
blob.setAllHeaders(from.getAllHeaders());
return blob;
}
}

View File

@ -0,0 +1,36 @@
package org.jclouds.rackspace.cloudfiles.blobstore.functions;
import javax.inject.Singleton;
import org.jclouds.blobstore.domain.MutableBlobMetadata;
import org.jclouds.blobstore.domain.ResourceType;
import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl;
import org.jclouds.http.HttpUtils;
import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import com.google.common.base.Function;
/**
* @author Adrian Cole
*/
@Singleton
public class ObjectToBlobMetadata implements
Function<MutableObjectInfoWithMetadata, MutableBlobMetadata> {
public MutableBlobMetadata apply(MutableObjectInfoWithMetadata from) {
MutableBlobMetadata to = new MutableBlobMetadataImpl();
to.setContentMD5(from.getHash());
if (from.getContentType() != null)
to.setContentType(from.getContentType());
if (from.getHash() != null)
to.setETag(HttpUtils.toHexString(from.getHash()));
to.setName(from.getName());
if (from.getBytes() != null)
to.setSize(from.getBytes());
to.setType(ResourceType.BLOB);
to.setUserMetadata(from.getMetadata());
if (from.getContentType() != null && from.getContentType().equals("application/directory")) {
to.setType(ResourceType.RELATIVE_PATH);
}
return to;
}
}

View File

@ -0,0 +1,55 @@
package org.jclouds.rackspace.cloudfiles.config;
import javax.inject.Inject;
import javax.inject.Provider;
import org.jclouds.blobstore.functions.CalculateSize;
import org.jclouds.blobstore.functions.GenerateMD5;
import org.jclouds.blobstore.functions.GenerateMD5Result;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import org.jclouds.rackspace.cloudfiles.domain.internal.CFObjectImpl;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Scopes;
/**
* Configures the domain object mappings needed for all CF implementations
*
* @author Adrian Cole
*/
public class CFObjectModule extends AbstractModule {
/**
* explicit factories are created here as it has been shown that Assisted Inject is extremely
* inefficient. http://code.google.com/p/google-guice/issues/detail?id=435
*/
@Override
protected void configure() {
bind(CFObject.Factory.class).to(CFObjectFactory.class).in(Scopes.SINGLETON);
}
private static class CFObjectFactory implements CFObject.Factory {
@Inject
GenerateMD5Result generateMD5Result;
@Inject
GenerateMD5 generateMD5;
@Inject
CalculateSize calculateSize;
@Inject
Provider<MutableObjectInfoWithMetadata> metadataProvider;
public CFObject create(MutableObjectInfoWithMetadata metadata) {
return new CFObjectImpl(generateMD5Result, generateMD5, calculateSize,
metadata != null ? metadata : metadataProvider.get());
}
}
@Provides
CFObject provideCFObject(CFObject.Factory factory) {
return factory.create(null);
}
}

View File

@ -50,6 +50,7 @@ public class CloudFilesContextModule extends AbstractModule {
protected void configure() { protected void configure() {
// for converters to work. // for converters to work.
install(new BlobStoreObjectModule()); install(new BlobStoreObjectModule());
install(new CFObjectModule());
} }
@Provides @Provides

View File

@ -0,0 +1,89 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.rackspace.cloudfiles.domain;
import java.io.IOException;
import com.google.common.collect.Multimap;
import com.google.inject.internal.Nullable;
/**
*
* @author Adrian Cole
*/
public interface CFObject extends Comparable<CFObject> {
public interface Factory {
CFObject create(@Nullable MutableObjectInfoWithMetadata info);
}
/**
* Sets entity for the request or the content from the response. If size isn't set, this will
* attempt to discover it.
*
* @param data
* typically InputStream for downloads, or File, byte [], String, or InputStream for
* uploads.
*/
void setData(Object data);
/**
* @return InputStream, if downloading, or whatever was set during {@link #setData(Object)}
*/
Object getData();
/**
* generate an MD5 Hash for the current data.
* <p/>
* <h2>Note</h2>
* <p/>
* If this is an InputStream, it will be converted to a byte array first.
*
* @throws IOException
* if there is a problem generating the hash.
*/
void generateMD5() throws IOException;
void setContentLength(long contentLength);
/**
* Returns the total size of the downloaded object, or the chunk that's available.
* <p/>
* Chunking is only used when org.jclouds.http.GetOptions is called with options like tail,
* range, or startAt.
*
* @return the length in bytes that can be be obtained from {@link #getData()}
* @see org.jclouds.http.HttpHeaders#CONTENT_LENGTH
* @see GetObjectOptions
*/
Long getContentLength();
/**
* @return System and User metadata relevant to this object.
*/
MutableObjectInfoWithMetadata getInfo();
Multimap<String, String> getAllHeaders();
void setAllHeaders(Multimap<String, String> allHeaders);
}

View File

@ -40,7 +40,6 @@ public class ContainerMetadata implements Comparable<ContainerMetadata> {
} }
public ContainerMetadata(String name, long count, long bytes) { public ContainerMetadata(String name, long count, long bytes) {
this();
setName(name); setName(name);
setBytes(bytes); setBytes(bytes);
setCount(count); setCount(count);

View File

@ -21,24 +21,33 @@
* under the License. * under the License.
* ==================================================================== * ====================================================================
*/ */
package org.jclouds.rackspace.cloudfiles.functions; package org.jclouds.rackspace.cloudfiles.domain;
import javax.inject.Inject; import java.util.Map;
import org.jclouds.blobstore.domain.Blob; import org.jclouds.rackspace.cloudfiles.domain.internal.MutableObjectInfoWithMetadataImpl;
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders; import org.joda.time.DateTime;
import com.google.inject.ImplementedBy;
/** /**
* Parses response headers and creates a new Rackspace Blob from them and the HTTP content.
* *
* @see ParseSystemAndUserMetadataFromHeaders
* @author Adrian Cole * @author Adrian Cole
*
*/ */
public class ParseBlobFromHeadersAndHttpContent extends @ImplementedBy(MutableObjectInfoWithMetadataImpl.class)
org.jclouds.blobstore.functions.ParseBlobFromHeadersAndHttpContent { public interface MutableObjectInfoWithMetadata extends ObjectInfo {
@Inject
public ParseBlobFromHeadersAndHttpContent(ParseObjectMetadataFromHeaders metadataParser, void setName(String name);
Blob.Factory blobFactory) {
super(metadataParser, blobFactory); void setHash(byte[] hash);
}
void setBytes(long bytes);
void setLastModified(DateTime lastModified);
void setContentType(String contentType);
Map<String, String> getMetadata();
} }

View File

@ -0,0 +1,41 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.rackspace.cloudfiles.domain;
import org.joda.time.DateTime;
/**
*
* @author Adrian Cole
*
*/
public interface ObjectInfo extends Comparable<ObjectInfo> {
String getName();
byte [] getHash();
Long getBytes();
String getContentType();
DateTime getLastModified();
}

View File

@ -0,0 +1,121 @@
package org.jclouds.rackspace.cloudfiles.domain.internal;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.io.InputStream;
import javax.inject.Inject;
import org.jclouds.blobstore.domain.MD5InputStreamResult;
import org.jclouds.blobstore.functions.CalculateSize;
import org.jclouds.blobstore.functions.GenerateMD5;
import org.jclouds.blobstore.functions.GenerateMD5Result;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
/**
* Default Implementation of {@link CFObject}.
*
* @author Adrian Cole
*/
public class CFObjectImpl implements CFObject, Comparable<CFObject> {
private final GenerateMD5Result generateMD5Result;
private final GenerateMD5 generateMD5;
private final CalculateSize calculateSize;
private final MutableObjectInfoWithMetadata info;
private Object data;
private Multimap<String, String> allHeaders = HashMultimap.create();
private Long contentLength;
@Inject
public CFObjectImpl(GenerateMD5Result generateMD5Result, GenerateMD5 generateMD5,
CalculateSize calculateSize, MutableObjectInfoWithMetadata info) {
this.generateMD5Result = generateMD5Result;
this.generateMD5 = generateMD5;
this.calculateSize = calculateSize;
this.info = info;
}
/**
* {@inheritDoc}
*/
public void generateMD5() {
checkState(data != null, "data");
if (data instanceof InputStream) {
MD5InputStreamResult result = generateMD5Result.apply((InputStream) data);
getInfo().setHash(result.md5);
setContentLength(result.length);
setData(result.data);
} else {
getInfo().setHash(generateMD5.apply(data));
}
}
/**
* {@inheritDoc}
*/
public Object getData() {
return data;
}
/**
* {@inheritDoc}
*/
public void setData(Object data) {
this.data = checkNotNull(data, "data");
if (getContentLength() == null) {
Long size = calculateSize.apply(data);
if (size != null)
this.setContentLength(size);
}
}
/**
* {@inheritDoc}
*/
public Long getContentLength() {
return contentLength;
}
/**
* {@inheritDoc}
*/
public void setContentLength(long contentLength) {
this.contentLength = contentLength;
}
/**
* {@inheritDoc}
*/
public MutableObjectInfoWithMetadata getInfo() {
return info;
}
/**
* {@inheritDoc}
*/
public Multimap<String, String> getAllHeaders() {
return allHeaders;
}
/**
* {@inheritDoc}
*/
public void setAllHeaders(Multimap<String, String> allHeaders) {
this.allHeaders = checkNotNull(allHeaders, "allHeaders");
}
/**
* {@inheritDoc}
*/
public int compareTo(CFObject o) {
if (getInfo().getName() == null)
return -1;
return (this == o) ? 0 : getInfo().getName().compareTo(o.getInfo().getName());
}
}

View File

@ -0,0 +1,151 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.rackspace.cloudfiles.domain.internal;
import java.util.Arrays;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import org.jclouds.rackspace.cloudfiles.domain.ObjectInfo;
import org.joda.time.DateTime;
import com.google.common.collect.Maps;
/**
*
* @author Adrian Cole
*
*/
public class MutableObjectInfoWithMetadataImpl implements MutableObjectInfoWithMetadata {
private String name;
private Long bytes;
private byte[] hash;
private String contentType = MediaType.APPLICATION_OCTET_STREAM;
private DateTime lastModified;
private final Map<String, String> metadata = Maps.newHashMap();
public Map<String, String> getMetadata() {
return metadata;
}
public void setBytes(long bytes) {
this.bytes = bytes;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public void setHash(byte[] hash) {
this.hash = hash;
}
public void setName(String name) {
this.name = name;
}
public Long getBytes() {
return bytes;
}
public String getContentType() {
return contentType;
}
public byte[] getHash() {
return hash;
}
public DateTime getLastModified() {
return lastModified;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((bytes == null) ? 0 : bytes.hashCode());
result = prime * result + ((contentType == null) ? 0 : contentType.hashCode());
result = prime * result + Arrays.hashCode(hash);
result = prime * result + ((lastModified == null) ? 0 : lastModified.hashCode());
result = prime * result + ((metadata == null) ? 0 : metadata.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MutableObjectInfoWithMetadataImpl other = (MutableObjectInfoWithMetadataImpl) obj;
if (bytes == null) {
if (other.bytes != null)
return false;
} else if (!bytes.equals(other.bytes))
return false;
if (contentType == null) {
if (other.contentType != null)
return false;
} else if (!contentType.equals(other.contentType))
return false;
if (!Arrays.equals(hash, other.hash))
return false;
if (lastModified == null) {
if (other.lastModified != null)
return false;
} else if (!lastModified.equals(other.lastModified))
return false;
if (metadata == null) {
if (other.metadata != null)
return false;
} else if (!metadata.equals(other.metadata))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public String getName() {
return name;
}
public int compareTo(ObjectInfo o) {
return (this == o) ? 0 : getName().compareTo(o.getName());
}
public void setLastModified(DateTime lastModified) {
this.lastModified = lastModified;
}
}

View File

@ -0,0 +1,17 @@
package org.jclouds.rackspace.cloudfiles.functions;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import com.google.common.base.Function;
/**
*
* @author Adrian Cole
*/
public class ObjectName implements Function<Object, String> {
public String apply(Object from) {
return ((CFObject) from).getInfo().getName();
}
}

View File

@ -0,0 +1,101 @@
/**
*
* Copyright (C) 2009 Cloud Conscious, LLC. <info@cloudconscious.com>
*
* ====================================================================
* 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.rackspace.cloudfiles.functions;
import javax.inject.Inject;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
import org.jclouds.http.HttpException;
import org.jclouds.http.HttpResponse;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
/**
* Parses response headers and creates a new CFObject from them and the HTTP content.
*
* @see ParseMetadataFromHeaders
* @author Adrian Cole
*/
public class ParseObjectFromHeadersAndHttpContent implements Function<HttpResponse, CFObject>,
InvocationContext {
private final ParseObjectMetadataFromHeaders infoParser;
private final CFObject.Factory objectProvider;
@Inject
public ParseObjectFromHeadersAndHttpContent(ParseObjectMetadataFromHeaders infoParser,
CFObject.Factory objectProvider) {
this.infoParser = infoParser;
this.objectProvider = objectProvider;
}
/**
* First, calls {@link ParseSystemAndUserMetadataFromHeaders}.
*
* Then, sets the object size based on the Content-Length header and adds the content to the
* {@link CFObject} result.
*
* @throws org.jclouds.http.HttpException
*/
public CFObject apply(HttpResponse from) {
CFObject object = objectProvider.create(infoParser.apply(from));
addAllHeadersTo(from, object);
object.setData(from.getContent());
attemptToParseSizeAndRangeFromHeaders(from, object);
return object;
}
@VisibleForTesting
void attemptToParseSizeAndRangeFromHeaders(HttpResponse from, CFObject object)
throws HttpException {
String contentLength = from.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH);
String contentRange = from.getFirstHeaderOrNull("Content-Range");
if (contentLength != null) {
object.setContentLength(Long.parseLong(contentLength));
}
if (contentRange == null && contentLength != null) {
object.getInfo().setBytes(object.getContentLength());
} else if (contentRange != null) {
object.getInfo().setBytes(
Long.parseLong(contentRange.substring(contentRange.lastIndexOf('/') + 1)));
}
}
@VisibleForTesting
void addAllHeadersTo(HttpResponse from, CFObject object) {
object.getAllHeaders().putAll(from.getHeaders());
}
public void setContext(GeneratedHttpRequest<?> request) {
infoParser.setContext(request);
}
}

View File

@ -33,14 +33,12 @@ import java.lang.reflect.Type;
import java.util.SortedSet; import java.util.SortedSet;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.BoundedSortedSet; import org.jclouds.blobstore.domain.BoundedSortedSet;
import org.jclouds.blobstore.domain.MutableBlobMetadata;
import org.jclouds.blobstore.domain.internal.BoundedTreeSet; import org.jclouds.blobstore.domain.internal.BoundedTreeSet;
import org.jclouds.http.HttpUtils; import org.jclouds.http.HttpUtils;
import org.jclouds.http.functions.ParseJson; import org.jclouds.http.functions.ParseJson;
import org.jclouds.rackspace.cloudfiles.domain.ObjectInfo;
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions; import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
import org.jclouds.rest.InvocationContext; import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.rest.internal.GeneratedHttpRequest;
@ -53,39 +51,103 @@ import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
/** /**
* This parses {@link BlobMetadata} from a gson string. * This parses {@link ObjectInfo} from a gson string.
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
public class ParseBlobMetadataListFromJsonResponse extends public class ParseObjectInfoListFromJsonResponse extends ParseJson<BoundedSortedSet<ObjectInfo>>
ParseJson<BoundedSortedSet<BlobMetadata>> implements InvocationContext { implements InvocationContext {
private final Provider<MutableBlobMetadata> metadataFactory;
private GeneratedHttpRequest<?> request; private GeneratedHttpRequest<?> request;
@Inject @Inject
public ParseBlobMetadataListFromJsonResponse(Provider<MutableBlobMetadata> metadataFactory, public ParseObjectInfoListFromJsonResponse(Gson gson) {
Gson gson) {
super(gson); super(gson);
this.metadataFactory = metadataFactory;
} }
public static class CloudFilesMetadata implements Comparable<CloudFilesMetadata> { public static class ObjectInfoImpl implements ObjectInfo {
public CloudFilesMetadata() {
}
String name; String name;
String hash; String hash;
long bytes; long bytes;
String content_type; String content_type;
DateTime last_modified; DateTime last_modified;
public int compareTo(CloudFilesMetadata o) { public int compareTo(ObjectInfoImpl o) {
return (this == o) ? 0 : name.compareTo(o.name); return (this == o) ? 0 : name.compareTo(o.name);
} }
public Long getBytes() {
return bytes;
}
public String getContentType() {
return content_type;
}
public byte[] getHash() {
return HttpUtils.fromHexString(hash);
}
public DateTime getLastModified() {
return last_modified;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (bytes ^ (bytes >>> 32));
result = prime * result + ((content_type == null) ? 0 : content_type.hashCode());
result = prime * result + ((hash == null) ? 0 : hash.hashCode());
result = prime * result + ((last_modified == null) ? 0 : last_modified.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ObjectInfoImpl other = (ObjectInfoImpl) obj;
if (bytes != other.bytes)
return false;
if (content_type == null) {
if (other.content_type != null)
return false;
} else if (!content_type.equals(other.content_type))
return false;
if (hash == null) {
if (other.hash != null)
return false;
} else if (!hash.equals(other.hash))
return false;
if (last_modified == null) {
if (other.last_modified != null)
return false;
} else if (!last_modified.equals(other.last_modified))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public int compareTo(ObjectInfo o) {
return (this == o) ? 0 : getName().compareTo(o.getName());
}
} }
public BoundedSortedSet<BlobMetadata> apply(InputStream stream) { public BoundedSortedSet<ObjectInfo> apply(InputStream stream) {
checkState(request != null, "request should be initialized at this point"); checkState(request != null, "request should be initialized at this point");
checkState(request.getArgs() != null, "request.getArgs() should be initialized at this point"); checkState(request.getArgs() != null, "request.getArgs() should be initialized at this point");
checkArgument(request.getArgs()[0] instanceof String, "arg[0] must be a container name"); checkArgument(request.getArgs()[0] instanceof String, "arg[0] must be a container name");
@ -94,28 +156,21 @@ public class ParseBlobMetadataListFromJsonResponse extends
ListContainerOptions[] optionsList = (ListContainerOptions[]) request.getArgs()[1]; ListContainerOptions[] optionsList = (ListContainerOptions[]) request.getArgs()[1];
ListContainerOptions options = optionsList.length > 0 ? optionsList[0] ListContainerOptions options = optionsList.length > 0 ? optionsList[0]
: ListContainerOptions.NONE; : ListContainerOptions.NONE;
Type listType = new TypeToken<SortedSet<CloudFilesMetadata>>() { Type listType = new TypeToken<SortedSet<ObjectInfoImpl>>() {
}.getType(); }.getType();
try { try {
SortedSet<CloudFilesMetadata> list = gson.fromJson(new InputStreamReader(stream, "UTF-8"), SortedSet<ObjectInfoImpl> list = gson.fromJson(new InputStreamReader(stream, "UTF-8"),
listType); listType);
SortedSet<BlobMetadata> returnVal = Sets.newTreeSet(Iterables.transform(list, SortedSet<ObjectInfo> returnVal = Sets.newTreeSet(Iterables.transform(list,
new Function<CloudFilesMetadata, BlobMetadata>() { new Function<ObjectInfoImpl, ObjectInfo>() {
public BlobMetadata apply(CloudFilesMetadata from) { public ObjectInfo apply(ObjectInfoImpl from) {
MutableBlobMetadata metadata = metadataFactory.get(); return from;
metadata.setName(from.name);
metadata.setSize(from.bytes);
metadata.setLastModified(from.last_modified);
metadata.setContentType(from.content_type);
metadata.setETag(from.hash);
metadata.setContentMD5(HttpUtils.fromHexString(from.hash));
return metadata;
} }
})); }));
boolean truncated = options.getMaxResults() == returnVal.size(); boolean truncated = options.getMaxResults() == returnVal.size();
String marker = truncated ? returnVal.last().getName() : null; String marker = truncated ? returnVal.last().getName() : null;
return new BoundedTreeSet<BlobMetadata>(returnVal, options.getPath(), marker, options return new BoundedTreeSet<ObjectInfo>(returnVal, options.getPath(), marker, options
.getMaxResults(), truncated); .getMaxResults(), truncated);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {

View File

@ -24,43 +24,50 @@
package org.jclouds.rackspace.cloudfiles.functions; package org.jclouds.rackspace.cloudfiles.functions;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import org.jclouds.blobstore.domain.MutableBlobMetadata; import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders; import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpUtils; import org.jclouds.http.HttpUtils;
import org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants; import org.jclouds.rackspace.cloudfiles.blobstore.functions.BlobToObjectInfo;
import org.jclouds.util.DateService; import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function;
/** /**
* This parses @{link {@link MutableObjectInfoWithMetadata} from HTTP headers.
*
* @author Adrian Cole * @author Adrian Cole
*/ */
public class ParseObjectMetadataFromHeaders extends ParseSystemAndUserMetadataFromHeaders { public class ParseObjectMetadataFromHeaders implements
Function<HttpResponse, MutableObjectInfoWithMetadata>, InvocationContext {
private final ParseSystemAndUserMetadataFromHeaders blobMetadataParser;
private final BlobToObjectInfo blobToObjectInfo;
@Inject @Inject
public ParseObjectMetadataFromHeaders(Provider<MutableBlobMetadata> metadataFactory, public ParseObjectMetadataFromHeaders(ParseSystemAndUserMetadataFromHeaders blobMetadataParser,
DateService dateParser, BlobToObjectInfo blobToObjectMetadata) {
@Named(CloudFilesConstants.PROPERTY_CLOUDFILES_METADATA_PREFIX) String metadataPrefix) { this.blobMetadataParser = blobMetadataParser;
super(metadataFactory, dateParser, metadataPrefix); this.blobToObjectInfo = blobToObjectMetadata;
} }
@VisibleForTesting
/** /**
* ETag == Content-MD5 * parses the http response headers to create a new {@link MutableObjectInfoWithMetadata} object.
*/ */
protected void addETagTo(HttpResponse from, MutableBlobMetadata metadata) { public MutableObjectInfoWithMetadata apply(HttpResponse from) {
super.addETagTo(from, metadata); BlobMetadata base = blobMetadataParser.apply(from);
if (metadata.getETag() == null) { MutableObjectInfoWithMetadata to = blobToObjectInfo.apply(base);
// etag comes back incorrect case String eTagHeader = from.getFirstHeaderOrNull("Etag");
String eTagHeader = from.getFirstHeaderOrNull("Etag"); if (eTagHeader != null) {
if (eTagHeader != null) { to.setHash(HttpUtils.fromHexString(eTagHeader.replaceAll("\"", "")));
metadata.setETag(eTagHeader);
}
} }
metadata.setContentMD5(HttpUtils.fromHexString(metadata.getETag())); return to;
} }
public void setContext(GeneratedHttpRequest<?> request) {
blobMetadataParser.setContext(request);
}
} }

View File

@ -41,8 +41,6 @@ import java.util.concurrent.TimeoutException;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.jclouds.blobstore.ContainerNotFoundException; import org.jclouds.blobstore.ContainerNotFoundException;
import org.jclouds.blobstore.KeyNotFoundException; import org.jclouds.blobstore.KeyNotFoundException;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.BoundedSortedSet; import org.jclouds.blobstore.domain.BoundedSortedSet;
import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest; import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
import org.jclouds.http.HttpResponseException; import org.jclouds.http.HttpResponseException;
@ -50,8 +48,11 @@ import org.jclouds.http.HttpUtils;
import org.jclouds.http.options.GetOptions; import org.jclouds.http.options.GetOptions;
import org.jclouds.logging.log4j.config.Log4JLoggingModule; import org.jclouds.logging.log4j.config.Log4JLoggingModule;
import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata; import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import org.jclouds.rackspace.cloudfiles.domain.ContainerCDNMetadata; import org.jclouds.rackspace.cloudfiles.domain.ContainerCDNMetadata;
import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata; import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata;
import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import org.jclouds.rackspace.cloudfiles.domain.ObjectInfo;
import org.jclouds.rackspace.cloudfiles.options.ListCdnContainerOptions; import org.jclouds.rackspace.cloudfiles.options.ListCdnContainerOptions;
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions; import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
import org.testng.annotations.BeforeGroups; import org.testng.annotations.BeforeGroups;
@ -283,10 +284,10 @@ public class CloudFilesClientLiveTest {
String data = "foo"; String data = "foo";
connection.putObject(containerName, newBlob(data, "foo")).get(10, TimeUnit.SECONDS); connection.putObject(containerName, newCFObject(data, "foo")).get(10, TimeUnit.SECONDS);
connection.putObject(containerName, newBlob(data, "path/bar")).get(10, TimeUnit.SECONDS); connection.putObject(containerName, newCFObject(data, "path/bar")).get(10, TimeUnit.SECONDS);
BoundedSortedSet<BlobMetadata> container = connection.listObjects(containerName, BoundedSortedSet<ObjectInfo> container = connection.listObjects(containerName,
underPath("")).get(10, TimeUnit.SECONDS); underPath("")).get(10, TimeUnit.SECONDS);
assert !container.isTruncated(); assert !container.isTruncated();
assertEquals(container.size(), 1); assertEquals(container.size(), 1);
@ -314,36 +315,36 @@ public class CloudFilesClientLiveTest {
// Test PUT with string data, ETag hash, and a piece of metadata // Test PUT with string data, ETag hash, and a piece of metadata
String data = "Here is my data"; String data = "Here is my data";
String key = "object"; String key = "object";
Blob object = newBlob(data, key); CFObject object = newCFObject(data, key);
byte[] md5 = object.getMetadata().getContentMD5(); byte[] md5 = object.getInfo().getHash();
String newEtag = connection.putObject(containerName, object).get(10, TimeUnit.SECONDS); String newEtag = connection.putObject(containerName, object).get(10, TimeUnit.SECONDS);
assertEquals(HttpUtils.toHexString(md5), HttpUtils.toHexString(object.getMetadata() assertEquals(HttpUtils.toHexString(md5), HttpUtils.toHexString(object.getInfo()
.getContentMD5())); .getHash()));
// Test HEAD of missing object // Test HEAD of missing object
try { try {
connection.getObjectMetadata(containerName, "non-existent-object"); connection.getObjectInfo(containerName, "non-existent-object");
assert false; assert false;
} catch (KeyNotFoundException e) { } catch (KeyNotFoundException e) {
} }
// Test HEAD of object // Test HEAD of object
BlobMetadata metadata = connection.getObjectMetadata(containerName, object.getMetadata() MutableObjectInfoWithMetadata metadata = connection.getObjectInfo(containerName, object.getInfo()
.getName()); .getName());
// TODO assertEquals(metadata.getName(), object.getMetadata().getName()); // TODO assertEquals(metadata.getName(), object.getMetadata().getName());
assertEquals(metadata.getSize(), new Long(data.length())); assertEquals(metadata.getBytes(), new Long(data.length()));
assertEquals(metadata.getContentType(), "text/plain"); assertEquals(metadata.getContentType(), "text/plain");
assertEquals(HttpUtils.toHexString(md5), HttpUtils.toHexString(object.getMetadata() assertEquals(HttpUtils.toHexString(md5), HttpUtils.toHexString(object.getInfo()
.getContentMD5())); .getHash()));
assertEquals(metadata.getETag(), newEtag); assertEquals(metadata.getHash(), HttpUtils.fromHexString(newEtag));
assertEquals(metadata.getUserMetadata().entrySet().size(), 1); assertEquals(metadata.getMetadata().entrySet().size(), 1);
assertEquals(metadata.getUserMetadata().get("metadata"), "metadata-value"); assertEquals(metadata.getMetadata().get("metadata"), "metadata-value");
// // Test POST to update object's metadata // // Test POST to update object's metadata
Map<String, String> userMetadata = Maps.newHashMap(); Map<String, String> userMetadata = Maps.newHashMap();
userMetadata.put("New-Metadata-1", "value-1"); userMetadata.put("New-Metadata-1", "value-1");
userMetadata.put("New-Metadata-2", "value-2"); userMetadata.put("New-Metadata-2", "value-2");
assertTrue(connection.setObjectMetadata(containerName, object.getMetadata().getName(), assertTrue(connection.setObjectMetadata(containerName, object.getInfo().getName(),
userMetadata)); userMetadata));
// Test GET of missing object // Test GET of missing object
@ -353,23 +354,23 @@ public class CloudFilesClientLiveTest {
} catch (KeyNotFoundException e) { } catch (KeyNotFoundException e) {
} }
// Test GET of object (including updated metadata) // Test GET of object (including updated metadata)
Blob getBlob = connection.getObject(containerName, object.getMetadata().getName()).get(120, CFObject getBlob = connection.getObject(containerName, object.getInfo().getName()).get(120,
TimeUnit.SECONDS); TimeUnit.SECONDS);
assertEquals(IOUtils.toString((InputStream) getBlob.getData()), data); assertEquals(IOUtils.toString((InputStream) getBlob.getData()), data);
// TODO assertEquals(getBlob.getName(), object.getMetadata().getName()); // TODO assertEquals(getBlob.getName(), object.getMetadata().getName());
assertEquals(getBlob.getContentLength(), new Long(data.length())); assertEquals(getBlob.getContentLength(), new Long(data.length()));
assertEquals(getBlob.getMetadata().getContentType(), "text/plain"); assertEquals(getBlob.getInfo().getContentType(), "text/plain");
assertEquals(HttpUtils.toHexString(md5), HttpUtils.toHexString(getBlob.getMetadata() assertEquals(HttpUtils.toHexString(md5), HttpUtils.toHexString(getBlob.getInfo()
.getContentMD5())); .getHash()));
assertEquals(newEtag, getBlob.getMetadata().getETag()); assertEquals(HttpUtils.fromHexString(newEtag), getBlob.getInfo().getHash());
assertEquals(getBlob.getMetadata().getUserMetadata().entrySet().size(), 2); assertEquals(getBlob.getInfo().getMetadata().entrySet().size(), 2);
assertEquals(getBlob.getMetadata().getUserMetadata().get("new-metadata-1"), "value-1"); assertEquals(getBlob.getInfo().getMetadata().get("new-metadata-1"), "value-1");
assertEquals(getBlob.getMetadata().getUserMetadata().get("new-metadata-2"), "value-2"); assertEquals(getBlob.getInfo().getMetadata().get("new-metadata-2"), "value-2");
// Test PUT with invalid ETag (as if object's data was corrupted in transit) // Test PUT with invalid ETag (as if object's data was corrupted in transit)
String correctEtag = newEtag; String correctEtag = newEtag;
String incorrectEtag = "0" + correctEtag.substring(1); String incorrectEtag = "0" + correctEtag.substring(1);
object.getMetadata().setETag(incorrectEtag); object.getInfo().setHash(HttpUtils.fromHexString(incorrectEtag));
try { try {
connection.putObject(containerName, object).get(10, TimeUnit.SECONDS); connection.putObject(containerName, object).get(10, TimeUnit.SECONDS);
} catch (Throwable e) { } catch (Throwable e) {
@ -379,17 +380,17 @@ public class CloudFilesClientLiveTest {
// Test PUT chunked/streamed upload with data of "unknown" length // Test PUT chunked/streamed upload with data of "unknown" length
ByteArrayInputStream bais = new ByteArrayInputStream(data.getBytes("UTF-8")); ByteArrayInputStream bais = new ByteArrayInputStream(data.getBytes("UTF-8"));
Blob blob = connection.newBlob(); CFObject blob = connection.newCFObject();
blob.getMetadata().setName("chunked-object"); blob.getInfo().setName("chunked-object");
object.setData(bais); blob.setData(bais);
newEtag = connection.putObject(containerName, object).get(10, TimeUnit.SECONDS); newEtag = connection.putObject(containerName, blob).get(10, TimeUnit.SECONDS);
assertEquals(HttpUtils.toHexString(md5), HttpUtils.toHexString(getBlob.getMetadata() assertEquals(HttpUtils.toHexString(md5), HttpUtils.toHexString(getBlob.getInfo()
.getContentMD5())); .getHash()));
// Test GET with options // Test GET with options
// Non-matching ETag // Non-matching ETag
try { try {
connection.getObject(containerName, object.getMetadata().getName(), connection.getObject(containerName, object.getInfo().getName(),
GetOptions.Builder.ifETagDoesntMatch(newEtag)).get(120, TimeUnit.SECONDS); GetOptions.Builder.ifETagDoesntMatch(newEtag)).get(120, TimeUnit.SECONDS);
} catch (Exception e) { } catch (Exception e) {
assertEquals(e.getCause().getClass(), HttpResponseException.class); assertEquals(e.getCause().getClass(), HttpResponseException.class);
@ -397,10 +398,10 @@ public class CloudFilesClientLiveTest {
} }
// Matching ETag // Matching ETag
getBlob = connection.getObject(containerName, object.getMetadata().getName(), getBlob = connection.getObject(containerName, object.getInfo().getName(),
GetOptions.Builder.ifETagMatches(newEtag)).get(120, TimeUnit.SECONDS); GetOptions.Builder.ifETagMatches(newEtag)).get(120, TimeUnit.SECONDS);
assertEquals(getBlob.getMetadata().getETag(), newEtag); assertEquals(getBlob.getInfo().getHash(), HttpUtils.fromHexString(newEtag));
getBlob = connection.getObject(containerName, object.getMetadata().getName(), getBlob = connection.getObject(containerName, object.getInfo().getName(),
GetOptions.Builder.startAt(8)).get(120, TimeUnit.SECONDS); GetOptions.Builder.startAt(8)).get(120, TimeUnit.SECONDS);
assertEquals(IOUtils.toString((InputStream) getBlob.getData()), data.substring(8)); assertEquals(IOUtils.toString((InputStream) getBlob.getData()), data.substring(8));
@ -410,14 +411,13 @@ public class CloudFilesClientLiveTest {
assertTrue(connection.deleteContainerIfEmpty(containerName).get(10, TimeUnit.SECONDS)); assertTrue(connection.deleteContainerIfEmpty(containerName).get(10, TimeUnit.SECONDS));
} }
private Blob newBlob(String data, String key) throws IOException { private CFObject newCFObject(String data, String key) throws IOException {
Blob object = connection.newBlob(); CFObject object = connection.newCFObject();
object.getMetadata().setName(key); object.getInfo().setName(key);
object.setData(data); object.setData(data);
object.setContentLength(data.length());
object.generateMD5(); object.generateMD5();
object.getMetadata().setContentType("text/plain"); object.getInfo().setContentType("text/plain");
object.getMetadata().getUserMetadata().put("Metadata", "metadata-value"); object.getInfo().getMetadata().put("Metadata", "metadata-value");
return object; return object;
} }

View File

@ -31,40 +31,54 @@ import java.net.URI;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import org.jclouds.blobstore.config.BlobStoreObjectModule;
import org.jclouds.blobstore.domain.Blob; import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.Blob.Factory; import org.jclouds.blobstore.domain.Blob.Factory;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.rackspace.cloudfiles.CloudFilesContextBuilder;
import org.jclouds.rackspace.cloudfiles.CloudFilesPropertiesBuilder;
import org.jclouds.rackspace.cloudfiles.blobstore.functions.BlobToObject;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.inject.Guice; import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
/** /**
* Tests parsing of a request * Tests parsing of a request
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
@Test(testName = "cloudfiles.BindCFObjectAsEntityTest") @Test(testName = "cloudfiles.BindCFObjectToEntityTest")
public class BindCFObjectAsEntityTest { public class BindCFObjectToEntityTest {
private Factory blobProvider; private Factory blobProvider;
private Provider<BindCFObjectToEntity> binderProvider;
private BlobToObject blob2Object;
public BindCFObjectAsEntityTest() { public BindCFObjectToEntityTest() {
blobProvider = Guice.createInjector(new BlobStoreObjectModule()).getInstance( Injector injector = new CloudFilesContextBuilder(new CloudFilesPropertiesBuilder("id",
Blob.Factory.class); "secret").build()).buildInjector();
blobProvider = injector.getInstance(Blob.Factory.class);
binderProvider = injector.getInstance(Key
.get(new TypeLiteral<Provider<BindCFObjectToEntity>>() {
}));
blob2Object = injector.getInstance(BlobToObject.class);
} }
public Blob testBlob() { public CFObject testBlob() {
Blob TEST_BLOB = blobProvider.create(null); Blob TEST_BLOB = blobProvider.create(null);
TEST_BLOB.getMetadata().setName("hello"); TEST_BLOB.getMetadata().setName("hello");
TEST_BLOB.setData("hello"); TEST_BLOB.setData("hello");
TEST_BLOB.getMetadata().setContentType(MediaType.TEXT_PLAIN); TEST_BLOB.getMetadata().setContentType(MediaType.TEXT_PLAIN);
return TEST_BLOB; return blob2Object.apply(TEST_BLOB);
} }
public void testNormal() throws IOException { public void testNormal() throws IOException {
BindCFObjectAsEntity binder = new BindCFObjectAsEntity("test"); BindCFObjectToEntity binder = binderProvider.get();
HttpRequest request = new HttpRequest("GET", URI.create("http://localhost:8001")); HttpRequest request = new HttpRequest("GET", URI.create("http://localhost:8001"));
binder.bindToRequest(request, testBlob()); binder.bindToRequest(request, testBlob());
@ -76,9 +90,9 @@ public class BindCFObjectAsEntityTest {
public void testMD5InHex() throws IOException { public void testMD5InHex() throws IOException {
BindCFObjectAsEntity binder = new BindCFObjectAsEntity("test"); BindCFObjectToEntity binder = binderProvider.get();
Blob blob = testBlob(); CFObject blob = testBlob();
blob.generateMD5(); blob.generateMD5();
HttpRequest request = new HttpRequest("GET", URI.create("http://localhost:8001")); HttpRequest request = new HttpRequest("GET", URI.create("http://localhost:8001"));
binder.bindToRequest(request, blob); binder.bindToRequest(request, blob);

View File

@ -31,10 +31,9 @@ import static org.testng.Assert.assertEquals;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.MutableBlobMetadata;
import org.jclouds.http.HttpUtils;
import org.jclouds.http.functions.config.ParserModule; import org.jclouds.http.functions.config.ParserModule;
import org.jclouds.rackspace.cloudfiles.domain.ObjectInfo;
import org.jclouds.rackspace.cloudfiles.functions.ParseObjectInfoListFromJsonResponse.ObjectInfoImpl;
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions; import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.rest.internal.GeneratedHttpRequest;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -56,30 +55,28 @@ public class ParseBlobMetadataListFromJsonResponseTest {
public void testApplyInputStream() { public void testApplyInputStream() {
InputStream is = getClass().getResourceAsStream("/test_list_container.json"); InputStream is = getClass().getResourceAsStream("/test_list_container.json");
List<BlobMetadata> expects = Lists.newArrayList(); List<ObjectInfo> expects = Lists.newArrayList();
MutableBlobMetadata one = i.getInstance(MutableBlobMetadata.class); ObjectInfoImpl one = i.getInstance(ObjectInfoImpl.class);
one.setName("test_obj_1"); one.name = "test_obj_1";
one.setETag("4281c348eaf83e70ddce0e07221c3d28"); one.hash = "4281c348eaf83e70ddce0e07221c3d28";
one.setContentMD5(HttpUtils.fromHexString(one.getETag())); one.bytes = 14l;
one.setSize(14l); one.content_type = "application/octet-stream";
one.setContentType("application/octet-stream"); one.last_modified = new DateTime("2009-02-03T05:26:32.612278");
one.setLastModified(new DateTime("2009-02-03T05:26:32.612278"));
expects.add(one); expects.add(one);
MutableBlobMetadata two = i.getInstance(MutableBlobMetadata.class); ObjectInfoImpl two = i.getInstance(ObjectInfoImpl.class);
two.setName("test_obj_2"); two.name = ("test_obj_2");
two.setETag("b039efe731ad111bc1b0ef221c3849d0"); two.hash = ("b039efe731ad111bc1b0ef221c3849d0");
two.setContentMD5(HttpUtils.fromHexString(two.getETag())); two.bytes = (64l);
two.setSize(64l); two.content_type = ("application/octet-stream");
two.setContentType("application/octet-stream"); two.last_modified =(new DateTime("2009-02-03T05:26:32.612278"));
two.setLastModified(new DateTime("2009-02-03T05:26:32.612278"));
expects.add(two); expects.add(two);
GeneratedHttpRequest<?> request = createMock(GeneratedHttpRequest.class); GeneratedHttpRequest<?> request = createMock(GeneratedHttpRequest.class);
ListContainerOptions options = new ListContainerOptions(); ListContainerOptions options = new ListContainerOptions();
expect(request.getArgs()).andReturn( expect(request.getArgs()).andReturn(
new Object[] { "containter", new ListContainerOptions[] { options } }).atLeastOnce(); new Object[] { "containter", new ListContainerOptions[] { options } }).atLeastOnce();
replay(request); replay(request);
ParseBlobMetadataListFromJsonResponse parser = i ParseObjectInfoListFromJsonResponse parser = i
.getInstance(ParseBlobMetadataListFromJsonResponse.class); .getInstance(ParseObjectInfoListFromJsonResponse.class);
parser.setContext(request); parser.setContext(request);
assertEquals(parser.apply(is), expects); assertEquals(parser.apply(is), expects);
} }

View File

@ -23,12 +23,18 @@
*/ */
package org.jclouds.rackspace.cloudfiles.functions; package org.jclouds.rackspace.cloudfiles.functions;
import static org.easymock.EasyMock.expect;
import static org.easymock.classextension.EasyMock.createMock;
import static org.easymock.classextension.EasyMock.replay;
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
import org.jclouds.blobstore.domain.MutableBlobMetadata; import java.net.URI;
import org.jclouds.blobstore.reference.BlobStoreConstants;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.http.functions.config.ParserModule; import org.jclouds.http.functions.config.ParserModule;
import org.jclouds.rackspace.cloudfiles.reference.CloudFilesConstants; import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import org.jclouds.rest.internal.GeneratedHttpRequest;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
@ -48,17 +54,24 @@ public class ParseObjectMetadataFromHeadersTest {
@Override @Override
protected void configure() { protected void configure() {
bindConstant().annotatedWith( bindConstant().annotatedWith(
Jsr330.named(CloudFilesConstants.PROPERTY_CLOUDFILES_METADATA_PREFIX)).to("sdf"); Jsr330.named(BlobStoreConstants.PROPERTY_USER_METADATA_PREFIX)).to("sdf");
} }
}); });
public void testEtagCaseIssue() { public void testEtagCaseIssue() {
ParseObjectMetadataFromHeaders parser = i.getInstance(ParseObjectMetadataFromHeaders.class); ParseObjectMetadataFromHeaders parser = i.getInstance(ParseObjectMetadataFromHeaders.class);
MutableBlobMetadata md = i.getInstance(MutableBlobMetadata.class); GeneratedHttpRequest<?> request = createMock(GeneratedHttpRequest.class);
expect(request.getEndpoint()).andReturn(URI.create("http://localhost/test")).atLeastOnce();
replay(request);
parser.setContext(request);
HttpResponse response = new HttpResponse(); HttpResponse response = new HttpResponse();
response.getHeaders().put("Content-Type", "text/plain");
response.getHeaders().put("Last-Modified", "Fri, 12 Jun 2007 13:40:18 GMT");
response.getHeaders().put("Content-Length", "0");
response.getHeaders().put("Etag", "feb1"); response.getHeaders().put("Etag", "feb1");
parser.addETagTo(response, md); MutableObjectInfoWithMetadata md = parser.apply(response);
assertNotNull(md.getETag()); assertNotNull(md.getHash());
} }
} }

View File

@ -27,14 +27,15 @@ import java.util.Map;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.BoundedSortedSet; import org.jclouds.blobstore.domain.BoundedSortedSet;
import org.jclouds.http.options.GetOptions; import org.jclouds.http.options.GetOptions;
import org.jclouds.rackspace.cloudfiles.CloudFilesClient; import org.jclouds.rackspace.cloudfiles.CloudFilesClient;
import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata; import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata;
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
import org.jclouds.rackspace.cloudfiles.domain.ContainerCDNMetadata; import org.jclouds.rackspace.cloudfiles.domain.ContainerCDNMetadata;
import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata; import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata;
import org.jclouds.rackspace.cloudfiles.domain.MutableObjectInfoWithMetadata;
import org.jclouds.rackspace.cloudfiles.domain.ObjectInfo;
import org.jclouds.rackspace.cloudfiles.options.ListCdnContainerOptions; import org.jclouds.rackspace.cloudfiles.options.ListCdnContainerOptions;
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions; import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
@ -77,11 +78,11 @@ public class StubCloudFilesClient implements CloudFilesClient {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public Future<Blob> getObject(String container, String key, GetOptions... options) { public Future<CFObject> getObject(String container, String key, GetOptions... options) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public BlobMetadata getObjectMetadata(String container, String key) { public MutableObjectInfoWithMetadata getObjectInfo(String container, String key) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -93,16 +94,12 @@ public class StubCloudFilesClient implements CloudFilesClient {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public Future<BoundedSortedSet<BlobMetadata>> listObjects(String container, public Future<BoundedSortedSet<ObjectInfo>> listObjects(String container,
ListContainerOptions... options) { ListContainerOptions... options) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public Blob newBlob() { public Future<String> putObject(String container, CFObject object) {
throw new UnsupportedOperationException();
}
public Future<String> putObject(String container, Blob object) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -118,4 +115,8 @@ public class StubCloudFilesClient implements CloudFilesClient {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public CFObject newCFObject() {
throw new UnsupportedOperationException();
}
} }