mirror of https://github.com/apache/jclouds.git
Issue 75: Implemented object PUT and DELETE.
git-svn-id: http://jclouds.googlecode.com/svn/trunk@1634 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
parent
0c4b0203a8
commit
b25f3fbed7
|
@ -40,7 +40,6 @@ import org.jclouds.aws.s3.domain.AccessControlList;
|
|||
import org.jclouds.aws.s3.domain.S3Bucket;
|
||||
import org.jclouds.aws.s3.domain.S3Object;
|
||||
import org.jclouds.aws.s3.filters.RequestAuthorizeSignature;
|
||||
import org.jclouds.aws.s3.functions.ParseETagHeader;
|
||||
import org.jclouds.aws.s3.functions.ParseMetadataFromHeaders;
|
||||
import org.jclouds.aws.s3.functions.ParseObjectFromHeadersAndHttpContent;
|
||||
import org.jclouds.aws.s3.functions.ReturnFalseOn404;
|
||||
|
@ -60,6 +59,7 @@ import org.jclouds.aws.s3.xml.AccessControlListHandler;
|
|||
import org.jclouds.aws.s3.xml.CopyObjectHandler;
|
||||
import org.jclouds.aws.s3.xml.ListAllMyBucketsHandler;
|
||||
import org.jclouds.aws.s3.xml.ListBucketHandler;
|
||||
import org.jclouds.http.functions.ParseETagHeader;
|
||||
import org.jclouds.http.options.GetOptions;
|
||||
import org.jclouds.rest.EntityParam;
|
||||
import org.jclouds.rest.ExceptionParser;
|
||||
|
|
|
@ -21,12 +21,11 @@
|
|||
* under the License.
|
||||
* ====================================================================
|
||||
*/
|
||||
package org.jclouds.aws.s3.functions;
|
||||
package org.jclouds.http.functions;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.jclouds.aws.s3.reference.S3Headers;
|
||||
import org.jclouds.http.HttpException;
|
||||
import org.jclouds.http.HttpResponse;
|
||||
import org.jclouds.http.HttpUtils;
|
||||
|
@ -34,7 +33,7 @@ import org.jclouds.http.HttpUtils;
|
|||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* Parses an MD5 checksum from the header {@link S3Headers#ETAG}.
|
||||
* Parses an MD5 checksum from the header {@link HttpHeaders#ETAG}.
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
|
@ -44,6 +43,10 @@ public class ParseETagHeader implements Function<HttpResponse, byte[]> {
|
|||
IOUtils.closeQuietly(from.getContent());
|
||||
|
||||
String eTag = from.getFirstHeaderOrNull(HttpHeaders.ETAG);
|
||||
if (eTag == null) {
|
||||
// TODO: Cloud Files sends incorrectly cased ETag header... Remove this when fixed.
|
||||
eTag = from.getFirstHeaderOrNull("Etag");
|
||||
}
|
||||
if (eTag != null) {
|
||||
return HttpUtils.fromHexString(eTag.replaceAll("\"", ""));
|
||||
}
|
|
@ -126,9 +126,14 @@ public class JavaUrlHttpCommandExecutorService extends
|
|||
connection.setInstanceFollowRedirects(false);
|
||||
connection.setRequestMethod(request.getMethod().toString());
|
||||
for (String header : request.getHeaders().keySet()) {
|
||||
for (String value : request.getHeaders().get(header))
|
||||
for (String value : request.getHeaders().get(header)) {
|
||||
connection.setRequestProperty(header, value);
|
||||
}
|
||||
|
||||
if ("Transfer-Encoding".equals(header) && "chunked".equals(value)) {
|
||||
connection.setChunkedStreamingMode(8192);
|
||||
}
|
||||
}
|
||||
}
|
||||
connection.setRequestProperty(HttpHeaders.HOST, request.getEndpoint().getHost());
|
||||
if (request.getEntity() != null) {
|
||||
OutputStream out = connection.getOutputStream();
|
||||
|
|
|
@ -34,15 +34,21 @@ import javax.ws.rs.PUT;
|
|||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
|
||||
import org.jclouds.http.functions.ParseETagHeader;
|
||||
import org.jclouds.rackspace.cloudfiles.binders.CFObjectBinder;
|
||||
import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata;
|
||||
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
|
||||
import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata;
|
||||
import org.jclouds.rackspace.cloudfiles.functions.CFObjectKey;
|
||||
import org.jclouds.rackspace.cloudfiles.functions.ParseAccountMetadataResponseFromHeaders;
|
||||
import org.jclouds.rackspace.cloudfiles.functions.ParseContainerListFromGsonResponse;
|
||||
import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn204FalseOtherwise;
|
||||
import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn404FalseOtherwise;
|
||||
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
|
||||
import org.jclouds.rackspace.filters.AuthenticateRequest;
|
||||
import org.jclouds.rest.EntityParam;
|
||||
import org.jclouds.rest.ExceptionParser;
|
||||
import org.jclouds.rest.PathParamParser;
|
||||
import org.jclouds.rest.Query;
|
||||
import org.jclouds.rest.RequestFilters;
|
||||
import org.jclouds.rest.ResponseParser;
|
||||
|
@ -89,4 +95,18 @@ public interface CloudFilesConnection {
|
|||
@Path("{container}")
|
||||
boolean deleteContainerIfEmpty(@PathParam("container") String container);
|
||||
|
||||
@PUT
|
||||
@Path("{container}/{key}")
|
||||
@ResponseParser(ParseETagHeader.class)
|
||||
Future<byte[]> putObject(
|
||||
@PathParam("container") String container,
|
||||
@PathParam("key") @PathParamParser(CFObjectKey.class) @EntityParam(CFObjectBinder.class)
|
||||
CFObject object);
|
||||
|
||||
@DELETE
|
||||
@ResponseParser(ReturnTrueOn204FalseOtherwise.class)
|
||||
@ExceptionParser(ReturnTrueOn404FalseOtherwise.class)
|
||||
@Path("{container}/{key}")
|
||||
boolean deleteObject(@PathParam("container") String container, @PathParam("key") String key);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
*
|
||||
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.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.binders;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
|
||||
import org.jclouds.http.HttpRequest;
|
||||
import org.jclouds.http.HttpUtils;
|
||||
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
|
||||
import org.jclouds.rest.EntityBinder;
|
||||
|
||||
public class CFObjectBinder implements EntityBinder {
|
||||
|
||||
public void addEntityToRequest(Object entity, HttpRequest request) {
|
||||
CFObject object = (CFObject) entity;
|
||||
|
||||
request.setEntity(checkNotNull(object.getData(), "object.getData()"));
|
||||
|
||||
if (object.getMetadata().getSize() >= 0) {
|
||||
request.getHeaders().put(HttpHeaders.CONTENT_LENGTH, object.getMetadata().getSize() + "");
|
||||
} else {
|
||||
// Enable "chunked"/"streamed" data, where the size needn't be known in advance.
|
||||
request.getHeaders().put("Transfer-Encoding", "chunked");
|
||||
}
|
||||
|
||||
request.getHeaders()
|
||||
.put(
|
||||
HttpHeaders.CONTENT_TYPE,
|
||||
checkNotNull(object.getMetadata().getContentType(),
|
||||
"object.metadata.contentType()"));
|
||||
|
||||
if (object.getMetadata().getCacheControl() != null) {
|
||||
request.getHeaders()
|
||||
.put(HttpHeaders.CACHE_CONTROL, object.getMetadata().getCacheControl());
|
||||
}
|
||||
if (object.getMetadata().getContentDisposition() != null) {
|
||||
request.getHeaders().put("Content-Disposition",
|
||||
object.getMetadata().getContentDisposition());
|
||||
}
|
||||
if (object.getMetadata().getContentEncoding() != null) {
|
||||
request.getHeaders().put(HttpHeaders.CONTENT_ENCODING,
|
||||
object.getMetadata().getContentEncoding());
|
||||
}
|
||||
|
||||
if (object.getMetadata().getETag() != null) {
|
||||
try {
|
||||
String hexETag = HttpUtils.toHexString(object.getMetadata().getETag());
|
||||
request.getHeaders().put(HttpHeaders.ETAG, hexETag);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// TODO: Any sane way to recover? Should EntityBinder#addEntityToRequest throw errors?
|
||||
}
|
||||
}
|
||||
|
||||
request.getHeaders().putAll(object.getMetadata().getUserMetadata());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,418 @@
|
|||
/**
|
||||
*
|
||||
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.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 static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import org.jclouds.http.HttpUtils;
|
||||
import org.jclouds.http.HttpUtils.ETagInputStreamResult;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
/**
|
||||
* Rackspace Cloud Files is designed to store objects. Objects are stored in
|
||||
* {@link ContainerMetadata containers} and consist of a
|
||||
* {@link CFObject#getData() value}, a {@link CFObject#getKey() key}, and
|
||||
* {@link CFObject.Metadata#getUserMetadata() metadata}.
|
||||
*
|
||||
* @author Adrian Cole
|
||||
* @author James Murty
|
||||
*/
|
||||
public class CFObject {
|
||||
public static final CFObject NOT_FOUND = new CFObject(Metadata.NOT_FOUND);
|
||||
|
||||
private Object data;
|
||||
private final Metadata metadata;
|
||||
private long contentLength = -1;
|
||||
private String contentRange;
|
||||
|
||||
public CFObject(String key) {
|
||||
this(new Metadata(key));
|
||||
}
|
||||
|
||||
public CFObject(Metadata metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public CFObject(Metadata metadata, Object data) {
|
||||
this(metadata);
|
||||
setData(data);
|
||||
}
|
||||
|
||||
public CFObject(String key, Object data) {
|
||||
this(key);
|
||||
setData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* System and user Metadata for the {@link CFObject}.
|
||||
*
|
||||
* @author Adrian Cole
|
||||
*/
|
||||
public static class Metadata implements Comparable<Metadata> {
|
||||
public static final Metadata NOT_FOUND = new Metadata("NOT_FOUND");
|
||||
|
||||
// parsed during list, head, or get
|
||||
private String key;
|
||||
private byte[] eTag;
|
||||
private volatile long size = -1;
|
||||
|
||||
// only parsed during head or get
|
||||
private Multimap<String, String> allHeaders = HashMultimap.create();
|
||||
private Multimap<String, String> userMetadata = HashMultimap.create();
|
||||
private DateTime lastModified;
|
||||
private String dataType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
private String cacheControl;
|
||||
private String dataDisposition;
|
||||
private String dataEncoding;
|
||||
|
||||
public Metadata() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @see #getKey()
|
||||
*/
|
||||
public Metadata(String key) {
|
||||
setKey(key);
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
checkNotNull(key, "key");
|
||||
checkArgument(!key.startsWith("/"), "keys cannot start with /");
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Metadata");
|
||||
sb.append("{key='").append(key).append('\'');
|
||||
sb.append(", lastModified=").append(lastModified);
|
||||
sb.append(", eTag=").append(
|
||||
getETag() == null ? "null" : Arrays.asList(getETag()).toString());
|
||||
sb.append(", size=").append(size);
|
||||
sb.append(", dataType='").append(dataType).append('\'');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof Metadata))
|
||||
return false;
|
||||
|
||||
Metadata metadata = (Metadata) o;
|
||||
|
||||
if (size != metadata.size)
|
||||
return false;
|
||||
if (dataType != null ? !dataType.equals(metadata.dataType) : metadata.dataType != null)
|
||||
return false;
|
||||
if (!key.equals(metadata.key))
|
||||
return false;
|
||||
if (lastModified != null ? !lastModified.equals(metadata.lastModified)
|
||||
: metadata.lastModified != null)
|
||||
return false;
|
||||
if (!Arrays.equals(getETag(), metadata.getETag()))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = key.hashCode();
|
||||
result = 31 * result + (lastModified != null ? lastModified.hashCode() : 0);
|
||||
result = 31 * result + (getETag() != null ? Arrays.hashCode(getETag()) : 0);
|
||||
result = 31 * result + (int) (size ^ (size >>> 32));
|
||||
result = 31 * result + (dataType != null ? dataType.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key is the handle that you assign to an object that allows you retrieve it later. A key
|
||||
* is a sequence of Unicode characters whose UTF-8 encoding is at most 1024 bytes long. Each
|
||||
* object in a bucket must have a unique key.
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public DateTime getLastModified() {
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
public void setLastModified(DateTime lastModified) {
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* The size of the object, in bytes.
|
||||
*
|
||||
* @see <a href= "http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.13." />
|
||||
*/
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* A standard MIME type describing the format of the contents. If none is provided, the
|
||||
* default is binary/octet-stream.
|
||||
*
|
||||
* @see <a href= "http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.17." />
|
||||
*/
|
||||
public String getContentType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public void setContentType(String dataType) {
|
||||
this.dataType = dataType;
|
||||
}
|
||||
|
||||
public void setETag(byte[] eTag) {
|
||||
this.eTag = new byte[eTag.length];
|
||||
System.arraycopy(eTag, 0, this.eTag, 0, eTag.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the eTag value stored in the Etag header returned by the service.
|
||||
*/
|
||||
public byte[] getETag() {
|
||||
if (eTag != null) {
|
||||
byte[] retval = new byte[eTag.length];
|
||||
System.arraycopy(this.eTag, 0, retval, 0, eTag.length);
|
||||
return retval;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setUserMetadata(Multimap<String, String> userMetadata) {
|
||||
this.userMetadata = userMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any header starting with <code>X-Object-Meta-</code> is considered user metadata. It will
|
||||
* be stored with the object and returned when you retrieve the object. The total size of the
|
||||
* HTTP request, not including the body, must be less than 8 KB.
|
||||
*/
|
||||
public Multimap<String, String> getUserMetadata() {
|
||||
return userMetadata;
|
||||
}
|
||||
|
||||
public void setCacheControl(String cacheControl) {
|
||||
this.cacheControl = cacheControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to specify caching behavior along the request/reply chain.
|
||||
*
|
||||
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.9.
|
||||
*/
|
||||
public String getCacheControl() {
|
||||
return cacheControl;
|
||||
}
|
||||
|
||||
public void setContentDisposition(String dataDisposition) {
|
||||
this.dataDisposition = dataDisposition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies presentational information for the object.
|
||||
*
|
||||
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html?sec19.5.1."/>
|
||||
*/
|
||||
public String getContentDisposition() {
|
||||
return dataDisposition;
|
||||
}
|
||||
|
||||
public void setContentEncoding(String dataEncoding) {
|
||||
this.dataEncoding = dataEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies what content encodings have been applied to the object and thus what decoding
|
||||
* mechanisms must be applied in order to obtain the media-type referenced by the Content-Type
|
||||
* header field.
|
||||
*
|
||||
* @see <a href= "http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html?sec14.11" />
|
||||
*/
|
||||
public String getContentEncoding() {
|
||||
return dataEncoding;
|
||||
}
|
||||
|
||||
public void setAllHeaders(Multimap<String, String> allHeaders) {
|
||||
this.allHeaders = allHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all http response headers associated with this object
|
||||
*/
|
||||
public Multimap<String, String> getAllHeaders() {
|
||||
return allHeaders;
|
||||
}
|
||||
|
||||
public int compareTo(Metadata o) {
|
||||
return (this == o) ? 0 : getKey().compareTo(o.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Metadata#getKey()
|
||||
*/
|
||||
public String getKey() {
|
||||
return metadata.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets entity for the request or the content from the response.
|
||||
*
|
||||
* @param data
|
||||
* typically InputStream for downloads, or File, byte [], String, or InputStream for
|
||||
* uploads.
|
||||
*/
|
||||
public void setData(Object data) {
|
||||
this.data = checkNotNull(data, "data");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public void generateETag() throws IOException {
|
||||
checkState(data != null, "data");
|
||||
if (data instanceof InputStream) {
|
||||
ETagInputStreamResult result = HttpUtils.generateETagResult((InputStream) data);
|
||||
getMetadata().setSize(result.length);
|
||||
getMetadata().setETag(result.eTag);
|
||||
setData(result.data);
|
||||
} else {
|
||||
getMetadata().setETag(HttpUtils.eTag(data));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InputStream, if downloading, or whatever was set during {@link #setData(Object)}
|
||||
*/
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return System and User metadata relevant to this object.
|
||||
*/
|
||||
public Metadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("CFObject");
|
||||
sb.append("{metadata=").append(metadata);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof CFObject))
|
||||
return false;
|
||||
|
||||
CFObject cfObject = (CFObject) o;
|
||||
|
||||
if (data != null ? !data.equals(cfObject.data) : cfObject.data != null)
|
||||
return false;
|
||||
if (!metadata.equals(cfObject.metadata))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = data != null ? data.hashCode() : 0;
|
||||
result = 31 * result + metadata.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setContentLength(long contentLength) {
|
||||
this.contentLength = contentLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total size of the downloaded object, or the chunk that's available.
|
||||
* <p/>
|
||||
* Chunking is only used when
|
||||
* TODO: Does Cloud Files support content ranges?
|
||||
* 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
|
||||
*/
|
||||
public long getContentLength() {
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
public void setContentRange(String contentRange) {
|
||||
this.contentRange = contentRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is not-null, {@link #getContentLength() } will the size of chunk of the object
|
||||
* available via {@link #getData()}
|
||||
*
|
||||
* @see org.jclouds.http.HttpHeaders#CONTENT_RANGE
|
||||
* @see GetObjectOptions
|
||||
*/
|
||||
public String getContentRange() {
|
||||
return contentRange;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
*
|
||||
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.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 org.jclouds.rackspace.cloudfiles.domain.CFObject;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
public class CFObjectKey implements Function<Object, String> {
|
||||
|
||||
public String apply(Object from) {
|
||||
return ((CFObject) from).getKey();
|
||||
}
|
||||
|
||||
}
|
|
@ -42,5 +42,5 @@ public interface CloudFilesHeaders {
|
|||
public static final String CDN_USER_AGENT_ACL = "X-User-Agent-ACL";
|
||||
public static final String CONTAINER_BYTES_USED = "X-Container-Bytes-Used";
|
||||
public static final String CONTAINER_OBJECT_COUNT = "X-Container-Object-Count";
|
||||
|
||||
public static final String USER_METADATA_PREFIX = "X-Object-Meta-";
|
||||
}
|
|
@ -30,11 +30,17 @@ import static org.testng.Assert.assertNotNull;
|
|||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.jclouds.http.HttpResponseException;
|
||||
import org.jclouds.http.HttpUtils;
|
||||
import org.jclouds.rackspace.cloudfiles.domain.AccountMetadata;
|
||||
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
|
||||
import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata;
|
||||
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
|
||||
import org.jclouds.rackspace.cloudfiles.reference.CloudFilesHeaders;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/**
|
||||
|
@ -134,5 +140,50 @@ public class CloudFilesConnectionLiveTest {
|
|||
assertTrue(connection.deleteContainerIfEmpty(containerName1));
|
||||
assertTrue(connection.deleteContainerIfEmpty(containerName2));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPutAndDeleteObjects() throws Exception {
|
||||
CloudFilesConnection connection = CloudFilesContextBuilder.newBuilder(sysRackspaceUser,
|
||||
sysRackspaceKey).withJsonDebug().buildContext().getConnection();
|
||||
String containerName = bucketPrefix + ".testPutAndDeleteObjects";
|
||||
String data = "Here is my data";
|
||||
|
||||
assertTrue(connection.putContainer(containerName));
|
||||
|
||||
// Test with string data, ETag hash, and a piece of metadata
|
||||
CFObject object = new CFObject("object", data);
|
||||
object.setContentLength(data.length());
|
||||
object.generateETag();
|
||||
object.getMetadata().setContentType("text/plain");
|
||||
// TODO: Metadata values aren't being stored by CF, but the names are. Odd...
|
||||
object.getMetadata().getUserMetadata().put(
|
||||
CloudFilesHeaders.USER_METADATA_PREFIX + "metadata", "metadata-value");
|
||||
byte[] md5 = connection.putObject(containerName, object).get(10, TimeUnit.SECONDS);
|
||||
assertEquals(HttpUtils.toHexString(md5),
|
||||
HttpUtils.toHexString(object.getMetadata().getETag()));
|
||||
// TODO: Get and confirm data
|
||||
|
||||
// Test with invalid ETag (as if object's data was corrupted in transit)
|
||||
String correctEtag = HttpUtils.toHexString(object.getMetadata().getETag());
|
||||
String incorrectEtag = "0" + correctEtag.substring(1);
|
||||
object.getMetadata().setETag(HttpUtils.fromHexString(incorrectEtag));
|
||||
try {
|
||||
connection.putObject(containerName, object).get(10, TimeUnit.SECONDS);
|
||||
} catch (Throwable e) {
|
||||
assertEquals(e.getCause().getClass(), HttpResponseException.class);
|
||||
assertEquals(((HttpResponseException)e.getCause()).getResponse().getStatusCode(), 422);
|
||||
}
|
||||
|
||||
// Test chunked/streamed upload with data of "unknown" length
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(data.getBytes("UTF-8"));
|
||||
object = new CFObject("chunked-object", bais);
|
||||
md5 = connection.putObject(containerName, object).get(10, TimeUnit.SECONDS);
|
||||
assertEquals(HttpUtils.toHexString(md5), correctEtag);
|
||||
// TODO: Get and confirm data
|
||||
|
||||
assertTrue(connection.deleteObject(containerName, "object"));
|
||||
assertTrue(connection.deleteObject(containerName, "chunked-object"));
|
||||
assertTrue(connection.deleteContainerIfEmpty(containerName));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue