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.S3Bucket;
|
||||||
import org.jclouds.aws.s3.domain.S3Object;
|
import org.jclouds.aws.s3.domain.S3Object;
|
||||||
import org.jclouds.aws.s3.filters.RequestAuthorizeSignature;
|
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.ParseMetadataFromHeaders;
|
||||||
import org.jclouds.aws.s3.functions.ParseObjectFromHeadersAndHttpContent;
|
import org.jclouds.aws.s3.functions.ParseObjectFromHeadersAndHttpContent;
|
||||||
import org.jclouds.aws.s3.functions.ReturnFalseOn404;
|
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.CopyObjectHandler;
|
||||||
import org.jclouds.aws.s3.xml.ListAllMyBucketsHandler;
|
import org.jclouds.aws.s3.xml.ListAllMyBucketsHandler;
|
||||||
import org.jclouds.aws.s3.xml.ListBucketHandler;
|
import org.jclouds.aws.s3.xml.ListBucketHandler;
|
||||||
|
import org.jclouds.http.functions.ParseETagHeader;
|
||||||
import org.jclouds.http.options.GetOptions;
|
import org.jclouds.http.options.GetOptions;
|
||||||
import org.jclouds.rest.EntityParam;
|
import org.jclouds.rest.EntityParam;
|
||||||
import org.jclouds.rest.ExceptionParser;
|
import org.jclouds.rest.ExceptionParser;
|
||||||
|
|
|
@ -21,12 +21,11 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
* ====================================================================
|
* ====================================================================
|
||||||
*/
|
*/
|
||||||
package org.jclouds.aws.s3.functions;
|
package org.jclouds.http.functions;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.jclouds.aws.s3.reference.S3Headers;
|
|
||||||
import org.jclouds.http.HttpException;
|
import org.jclouds.http.HttpException;
|
||||||
import org.jclouds.http.HttpResponse;
|
import org.jclouds.http.HttpResponse;
|
||||||
import org.jclouds.http.HttpUtils;
|
import org.jclouds.http.HttpUtils;
|
||||||
|
@ -34,7 +33,7 @@ import org.jclouds.http.HttpUtils;
|
||||||
import com.google.common.base.Function;
|
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
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
|
@ -44,6 +43,10 @@ public class ParseETagHeader implements Function<HttpResponse, byte[]> {
|
||||||
IOUtils.closeQuietly(from.getContent());
|
IOUtils.closeQuietly(from.getContent());
|
||||||
|
|
||||||
String eTag = from.getFirstHeaderOrNull(HttpHeaders.ETAG);
|
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) {
|
if (eTag != null) {
|
||||||
return HttpUtils.fromHexString(eTag.replaceAll("\"", ""));
|
return HttpUtils.fromHexString(eTag.replaceAll("\"", ""));
|
||||||
}
|
}
|
|
@ -126,8 +126,13 @@ public class JavaUrlHttpCommandExecutorService extends
|
||||||
connection.setInstanceFollowRedirects(false);
|
connection.setInstanceFollowRedirects(false);
|
||||||
connection.setRequestMethod(request.getMethod().toString());
|
connection.setRequestMethod(request.getMethod().toString());
|
||||||
for (String header : request.getHeaders().keySet()) {
|
for (String header : request.getHeaders().keySet()) {
|
||||||
for (String value : request.getHeaders().get(header))
|
for (String value : request.getHeaders().get(header)) {
|
||||||
connection.setRequestProperty(header, value);
|
connection.setRequestProperty(header, value);
|
||||||
|
|
||||||
|
if ("Transfer-Encoding".equals(header) && "chunked".equals(value)) {
|
||||||
|
connection.setChunkedStreamingMode(8192);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
connection.setRequestProperty(HttpHeaders.HOST, request.getEndpoint().getHost());
|
connection.setRequestProperty(HttpHeaders.HOST, request.getEndpoint().getHost());
|
||||||
if (request.getEntity() != null) {
|
if (request.getEntity() != null) {
|
||||||
|
|
|
@ -34,15 +34,21 @@ import javax.ws.rs.PUT;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
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.AccountMetadata;
|
||||||
|
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
|
||||||
import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata;
|
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.ParseAccountMetadataResponseFromHeaders;
|
||||||
import org.jclouds.rackspace.cloudfiles.functions.ParseContainerListFromGsonResponse;
|
import org.jclouds.rackspace.cloudfiles.functions.ParseContainerListFromGsonResponse;
|
||||||
import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn204FalseOtherwise;
|
import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn204FalseOtherwise;
|
||||||
import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn404FalseOtherwise;
|
import org.jclouds.rackspace.cloudfiles.functions.ReturnTrueOn404FalseOtherwise;
|
||||||
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
|
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
|
||||||
import org.jclouds.rackspace.filters.AuthenticateRequest;
|
import org.jclouds.rackspace.filters.AuthenticateRequest;
|
||||||
|
import org.jclouds.rest.EntityParam;
|
||||||
import org.jclouds.rest.ExceptionParser;
|
import org.jclouds.rest.ExceptionParser;
|
||||||
|
import org.jclouds.rest.PathParamParser;
|
||||||
import org.jclouds.rest.Query;
|
import org.jclouds.rest.Query;
|
||||||
import org.jclouds.rest.RequestFilters;
|
import org.jclouds.rest.RequestFilters;
|
||||||
import org.jclouds.rest.ResponseParser;
|
import org.jclouds.rest.ResponseParser;
|
||||||
|
@ -89,4 +95,18 @@ public interface CloudFilesConnection {
|
||||||
@Path("{container}")
|
@Path("{container}")
|
||||||
boolean deleteContainerIfEmpty(@PathParam("container") String 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 CDN_USER_AGENT_ACL = "X-User-Agent-ACL";
|
||||||
public static final String CONTAINER_BYTES_USED = "X-Container-Bytes-Used";
|
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 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.assertTrue;
|
||||||
import static org.testng.Assert.fail;
|
import static org.testng.Assert.fail;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.util.List;
|
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.AccountMetadata;
|
||||||
|
import org.jclouds.rackspace.cloudfiles.domain.CFObject;
|
||||||
import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata;
|
import org.jclouds.rackspace.cloudfiles.domain.ContainerMetadata;
|
||||||
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
|
import org.jclouds.rackspace.cloudfiles.options.ListContainerOptions;
|
||||||
|
import org.jclouds.rackspace.cloudfiles.reference.CloudFilesHeaders;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,4 +141,49 @@ public class CloudFilesConnectionLiveTest {
|
||||||
assertTrue(connection.deleteContainerIfEmpty(containerName2));
|
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