Issue 191: started cookbook support; refactored multipart form code

This commit is contained in:
Adrian Cole 2010-06-11 19:02:19 -07:00
parent bb3de69eb8
commit 306bb0ebde
27 changed files with 170 additions and 63 deletions

View File

@ -25,7 +25,7 @@ import com.google.inject.internal.Nullable;
/**
* Amazon Atmos is designed to store objects. Objects are stored in buckets and consist of a
* {@link ObjectMetadataAtmosObject#getContent() value}, a {@link ObjectMetadata#getKey key},
* {@link ObjectMetadataAtmosObject#getInput() value}, a {@link ObjectMetadata#getKey key},
* {@link ObjectMetadata#getUserMetadata() metadata}, and an access control policy.
*
* @author Adrian Cole

View File

@ -46,7 +46,7 @@ public class MutableContentMetadata {
* 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 #getContent()}
* @return the length in bytes that can be be obtained from {@link #getInput()}
* @see org.jclouds.http.HttpHeaders#CONTENT_LENGTH
* @see GetObjectOptions
*/

View File

@ -25,7 +25,7 @@ import com.google.inject.internal.Nullable;
/**
* Amazon S3 is designed to store objects. Objects are stored in buckets and consist of a
* {@link ObjectMetadataS3Object#getContent() value}, a {@link ObjectMetadata#getKey key},
* {@link ObjectMetadataS3Object#getInput() value}, a {@link ObjectMetadata#getKey key},
* {@link ObjectMetadata#getUserMetadata() metadata}, and an access control policy.
*
* @author Adrian Cole

View File

@ -27,7 +27,7 @@ import com.google.common.collect.Multimap;
/**
* Amazon S3 is designed to store objects. Objects are stored in buckets and consist of a
* {@link ObjectPropertiesBlob#getContent() value}, a {@link ObjectProperties#getKey key},
* {@link ObjectPropertiesBlob#getInput() value}, a {@link ObjectProperties#getKey key},
* {@link ObjectProperties#getUserProperties() metadata}, and an access control policy.
*
* @author Adrian Cole

View File

@ -577,7 +577,7 @@ public class TransientAsyncBlobStore extends BaseAsyncBlobStore {
if (options.getRanges() != null && options.getRanges().size() > 0) {
byte[] data;
try {
data = ByteStreams.toByteArray(returnVal.getPayload().getContent());
data = ByteStreams.toByteArray(returnVal.getPayload().getInput());
} catch (IOException e) {
return immediateFailedFuture(new RuntimeException(e));
}

View File

@ -41,9 +41,9 @@ public class BindBlobToMultipartForm implements Binder {
.getMetadata().getContentType());
MultipartForm form = new MultipartForm(BOUNDARY, part);
request.setPayload(form.getData());
request.setPayload(form.getInput());
request.getHeaders().put(HttpHeaders.CONTENT_TYPE,
"multipart/form-data; boundary=" + BOUNDARY);
request.getHeaders().put(HttpHeaders.CONTENT_LENGTH, form.getSize() + "");
request.getHeaders().put(HttpHeaders.CONTENT_LENGTH, form.calculateSize() + "");
}
}

View File

@ -33,7 +33,7 @@ import com.google.common.collect.Multimap;
/**
* Value type for an HTTP Blob service. Blobs are stored in {@link StorageMetadata containers} and consist
* of a {@link org.jclouds.blobstore.domain.Value#getContent() value}, a {@link Blob#getKey key and
* of a {@link org.jclouds.blobstore.domain.Value#getInput() value}, a {@link Blob#getKey key and
*
* @link Blob.Metadata#getUserMetadata() metadata}
*

View File

@ -23,10 +23,12 @@
*/
package org.jclouds.chef;
import java.io.File;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.POST;
@ -43,6 +45,7 @@ import org.jclouds.chef.functions.ParseKeySetFromJson;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.ExceptionParser;
import org.jclouds.rest.annotations.PartParam;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404;
@ -70,7 +73,23 @@ public interface ChefAsyncClient {
ListenableFuture<String> listCookbooks();
/**
* @see ChefClient#createClientanization
* @see ChefClient#createCookbook(String,File)
*/
@POST
@Path("cookbooks")
ListenableFuture<String> createCookbook(@FormParam("name") String name,
@PartParam(name = "file", contentType = MediaType.APPLICATION_OCTET_STREAM) File content);
/**
* @see ChefClient#createCookbook(String,byte[])
*/
@POST
@Path("cookbooks")
ListenableFuture<String> createCookbook(@FormParam("name") String name,
@PartParam(name = "file", contentType = MediaType.APPLICATION_OCTET_STREAM) byte[] content);
/**
* @see ChefClient#createClient
*/
@POST
@Path("clients")

View File

@ -41,6 +41,8 @@
*/
package org.jclouds.chef;
import java.io.File;
import java.io.InputStream;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@ -60,6 +62,10 @@ import org.jclouds.rest.AuthorizationException;
public interface ChefClient {
String listCookbooks();
String createCookbook(String name, File content);
String createCookbook(String name, byte[] content);
/**
* creates a new client
*

View File

@ -152,9 +152,9 @@ public class SignedHeaderAuth implements HttpRequestFilter {
if (payload == null)
return emptyStringHash;
checkArgument(payload != null, "payload was null");
checkArgument(payload.isRepeatable(), "payload must be repeatable");
checkArgument(payload.isRepeatable(), "payload must be repeatable: " + payload);
try {
return encryptionService.sha1Base64(Utils.toStringAndClose(payload.getContent()));
return encryptionService.sha1Base64(Utils.toStringAndClose(payload.getInput()));
} catch (Exception e) {
Throwables.propagateIfPossible(e);
throw new HttpException("error creating sigature for payload: " + payload, e);

View File

@ -26,8 +26,10 @@ package org.jclouds.chef;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.testng.Assert.assertNotNull;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Set;
@ -38,6 +40,7 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.google.common.base.Charsets;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;
/**
@ -108,6 +111,31 @@ public class ChefClientLiveTest {
}
@Test(dependsOnMethods = "testGenerateKeyForClient")
public void testCreateCookbooks() throws Exception {
InputStream in = null;
try {
in = URI
.create(
"https://s3.amazonaws.com/opscode-community/cookbook_versions/tarballs/194/original/java.tar.gz")
.toURL().openStream();
byte[] content = ByteStreams.toByteArray(in);
System.err.println(clientConnection.getApi().createCookbook("java-bytearray", content));
File file = File.createTempFile("foo", "bar");
Files.write(content, file);
file.deleteOnExit();
System.err.println(clientConnection.getApi().createCookbook("java-file", file));
} finally {
if (in != null)
in.close();
}
}
@Test(dependsOnMethods = "testCreateCookbooks")
public void testListCookbooks() throws Exception {
System.err.println(clientConnection.getApi().listCookbooks());
}

View File

@ -159,8 +159,8 @@ public class HttpRequest extends HttpMessage {
}
private void closeContentIfPresent() {
if (getPayload() != null && getPayload().getContent() != null) {
Closeables.closeQuietly(getPayload().getContent());
if (getPayload() != null && getPayload().getInput() != null) {
Closeables.closeQuietly(getPayload().getInput());
}
}

View File

@ -19,6 +19,7 @@
package org.jclouds.http;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import java.io.IOException;
import java.io.InputStream;
@ -30,59 +31,70 @@ import javax.annotation.Nullable;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.http.payloads.FilePayload;
import org.jclouds.util.InputStreamChain;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
import com.google.common.io.InputSupplier;
/**
*
* @author Adrian Cole
*/
public class MultipartForm {
public class MultipartForm implements Payload {
private static final String rn = "\r\n";
private static final String dd = "--";
private final InputStreamChain chain;
private final InputSupplier<? extends InputStream> chain;
private long size;
private boolean isRepeatable;
private boolean written;
public MultipartForm(String boundary, Part... parts) {
this(boundary, Lists.newArrayList(parts));
}
@SuppressWarnings("unchecked")
public MultipartForm(String boundary, Iterable<? extends Part> parts) {
String boundaryrn = boundary + rn;
chain = new InputStreamChain();
isRepeatable = true;
InputSupplier<? extends InputStream> chain = ByteStreams.join();
for (Part part : parts) {
addHeaders(boundaryrn, part);
addData(part);
if (!part.isRepeatable())
isRepeatable = false;
size += part.calculateSize();
chain = ByteStreams.join(chain, addLengthAndReturnHeaders(boundaryrn, part), part,
addLengthAndReturnRn());
}
addFooter(boundary);
chain = ByteStreams.join(chain, addLengthAndReturnFooter(boundary));
this.chain = chain;
}
private void addData(Part part) {
chain.addInputStream(part.getContent());
chain.addAsInputStream(rn);
size += part.calculateSize() + rn.length();
private InputSupplier<? extends InputStream> addLengthAndReturnRn() {
size += rn.length();
return ByteStreams.newInputStreamSupplier(rn.getBytes());
}
private void addHeaders(String boundaryrn, Part part) {
private InputSupplier<? extends InputStream> addLengthAndReturnHeaders(String boundaryrn,
Part part) {
StringBuilder builder = new StringBuilder(dd).append(boundaryrn);
for (Entry<String, String> entry : part.getHeaders().entries()) {
String header = String.format("%s: %s%s", entry.getKey(), entry.getValue(), rn);
builder.append(header);
}
builder.append(rn);
chain.addAsInputStream(builder.toString());
size += builder.length();
return ByteStreams.newInputStreamSupplier(builder.toString().getBytes());
}
private void addFooter(String boundary) {
private InputSupplier<? extends InputStream> addLengthAndReturnFooter(String boundary) {
String end = dd + boundary + dd + rn;
chain.addAsInputStream(end);
size += end.length();
return ByteStreams.newInputStreamSupplier(end.getBytes());
}
public MultipartForm(Part... parts) {
@ -147,13 +159,13 @@ public class MultipartForm {
}
@Override
public InputStream getContent() {
return delegate.getContent();
public InputStream getInput() {
return delegate.getInput();
}
@Override
public Object getRawContent() {
return delegate.getContent();
return delegate.getInput();
}
@Override
@ -198,11 +210,47 @@ public class MultipartForm {
}
}
public long getSize() {
@Override
public Long calculateSize() {
return size;
}
public InputStream getData() {
return chain;
@Override
public InputStream getInput() {
try {
return chain.getInput();
} catch (IOException e) {
Throwables.propagate(e);
return null;
}
}
@Override
public Object getRawContent() {
return getInput();
}
@Override
public boolean isRepeatable() {
return isRepeatable;
}
@Override
public void writeTo(OutputStream outstream) throws IOException {
checkState(!written || !isRepeatable,
"InputStreams can only be writted to an outputstream once");
written = true;
InputStream in = getInput();
try {
ByteStreams.copy(getInput(), outstream);
} finally {
Closeables.closeQuietly(in);
}
}
@Override
public String toString() {
return "MultipartForm [chain=" + chain + ", isRepeatable=" + isRepeatable + ", size=" + size
+ ", written=" + written + "]";
}
}

View File

@ -22,18 +22,20 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.google.common.io.InputSupplier;
/**
* @author Adrian Cole
*/
public interface Payload {
public interface Payload extends InputSupplier<InputStream> {
/**
*  Creates a new InputStream object of the payload.
*/
InputStream getContent();
InputStream getInput();
/**
* Payload in its original form.
* Payload in its original form.
*/
Object getRawContent();

View File

@ -57,7 +57,7 @@ public abstract class BasePayloadEnclosingImpl implements PayloadEnclosing {
checkState(payload != null, "payload");
if (payload instanceof InputStreamPayload) {
MD5InputStreamResult result = encryptionService
.generateMD5Result(((InputStreamPayload) payload).getContent());
.generateMD5Result(((InputStreamPayload) payload).getInput());
setContentMD5(result.md5);
setContentLength(result.length);
setPayload(result.data);
@ -73,7 +73,7 @@ public abstract class BasePayloadEnclosingImpl implements PayloadEnclosing {
*/
@Override
public InputStream getContent() {
return payload != null ? payload.getContent() : null;
return payload != null ? payload.getInput() : null;
}
/**

View File

@ -49,7 +49,7 @@ public class ByteArrayPayload implements Payload {
* {@inheritDoc}
*/
@Override
public InputStream getContent() {
public InputStream getInput() {
return new ByteArrayInputStream(content);
}

View File

@ -52,7 +52,7 @@ public class FilePayload implements Payload {
* {@inheritDoc}
*/
@Override
public InputStream getContent() {
public InputStream getInput() {
try {
return new FileInputStream(content);
} catch (FileNotFoundException e) {
@ -73,7 +73,7 @@ public class FilePayload implements Payload {
*/
@Override
public void writeTo(OutputStream outstream) throws IOException {
InputStream in = getContent();
InputStream in = getInput();
try {
Files.copy(content, outstream);
} finally {

View File

@ -49,7 +49,7 @@ public class InputStreamPayload implements Payload {
* {@inheritDoc}
*/
@Override
public InputStream getContent() {
public InputStream getInput() {
return content;
}
@ -68,9 +68,9 @@ public class InputStreamPayload implements Payload {
public void writeTo(OutputStream outstream) throws IOException {
checkState(!written, "InputStreams can only be writted to an outputstream once");
written = true;
InputStream in = getContent();
InputStream in = getInput();
try {
ByteStreams.copy(getContent(), outstream);
ByteStreams.copy(getInput(), outstream);
} finally {
Closeables.closeQuietly(in);
}

View File

@ -47,7 +47,7 @@ public class StringPayload implements Payload {
* {@inheritDoc}
*/
@Override
public InputStream getContent() {
public InputStream getInput() {
return Utils.toInputStream(content);
}

View File

@ -426,11 +426,11 @@ public class RestAnnotationProcessor<T> {
}
MultipartForm form = new MultipartForm(BOUNDARY, parts);
request.setPayload(form.getData());
request.setPayload(form);
request.getHeaders().put(HttpHeaders.CONTENT_TYPE,
"multipart/form-data; boundary=" + BOUNDARY);
request.getHeaders().put(HttpHeaders.CONTENT_LENGTH, form.getSize() + "");
request.getHeaders().put(HttpHeaders.CONTENT_LENGTH, form.calculateSize() + "");
} else if (formParams.size() > 0) {
if (headers.get(HttpHeaders.CONTENT_TYPE) != null)

View File

@ -55,8 +55,8 @@ public class MultipartFormTest {
MultipartForm multipartForm = new MultipartForm(boundary, newPart("hello"));
assertEquals(Utils.toStringAndClose(multipartForm.getData()), expects);
assertEquals(multipartForm.getSize(), 199);
assertEquals(Utils.toStringAndClose(multipartForm.getInput()), expects);
assertEquals(multipartForm.calculateSize(), new Long(199));
}
public static class MockFilePayload extends FilePayload {
@ -82,8 +82,8 @@ public class MultipartFormTest {
}
@Override
public InputStream getContent() {
return realPayload.getContent();
public InputStream getInput() {
return realPayload.getInput();
}
@Override
@ -125,8 +125,12 @@ public class MultipartFormTest {
MultipartForm multipartForm = new MultipartForm(boundary, newPart("hello"),
newPart("goodbye"));
assertEquals(Utils.toStringAndClose(multipartForm.getData()), expects);
assertEquals(multipartForm.getSize(), 352);
assertEquals(Utils.toStringAndClose(multipartForm.getInput()), expects);
// test repeatable
assert multipartForm.isRepeatable();
assertEquals(Utils.toStringAndClose(multipartForm.getInput()), expects);
assertEquals(multipartForm.calculateSize(), new Long(352));
}
}

View File

@ -77,7 +77,7 @@ public abstract class RestClientTest<T> {
if (httpMethod.getPayload() == null) {
assertNull(toMatch);
} else {
String payload = Utils.toStringAndClose(httpMethod.getPayload().getContent());
String payload = Utils.toStringAndClose(httpMethod.getPayload().getInput());
assertEquals(payload, toMatch);
}
}

View File

@ -2013,7 +2013,7 @@ public class RestAnnotationProcessorTest {
if (httpMethod.getPayload() == null) {
assertNull(toMatch);
} else {
String payload = Utils.toStringAndClose(httpMethod.getPayload().getContent());
String payload = Utils.toStringAndClose(httpMethod.getPayload().getInput());
assertEquals(payload, toMatch);
}
}

View File

@ -120,7 +120,7 @@ public class AsyncGaeHttpCommandExecutorService implements HttpCommandExecutorSe
String string = ((StringPayload) content).getRawContent();
request.setPayload(string.getBytes());
} else if (content instanceof InputStreamPayload || content instanceof FilePayload) {
InputStream i = content.getContent();
InputStream i = content.getInput();
try {
try {
request.setPayload(ByteStreams.toByteArray(i));

View File

@ -90,7 +90,7 @@ public class GaeHttpCommandExecutorService extends BaseHttpCommandExecutorServic
String string = ((StringPayload) content).getRawContent();
request.setPayload(string.getBytes());
} else if (content instanceof InputStreamPayload || content instanceof FilePayload) {
InputStream i = content.getContent();
InputStream i = content.getInput();
try {
request.setPayload(ByteStreams.toByteArray(i));
} finally {

View File

@ -30,7 +30,7 @@ public class BindDataToPayload implements Binder {
public void bindToRequest(HttpRequest request, Object payload) {
PCSFile object = (PCSFile) payload;
request.setPayload(checkNotNull(object.getPayload().getContent(), "object.getContent()"));
request.setPayload(checkNotNull(object.getPayload().getInput(), "object.getContent()"));
request.getHeaders().put(HttpHeaders.CONTENT_LENGTH, object.getContentLength() + "");
}
}

View File

@ -79,7 +79,7 @@ public class BindCFObjectToPayloadTest {
HttpRequest request = new HttpRequest("GET", URI.create("http://localhost:8001"));
binder.bindToRequest(request, testBlob());
assertEquals(Utils.toStringAndClose(request.getPayload().getContent()), "hello");
assertEquals(Utils.toStringAndClose(request.getPayload().getInput()), "hello");
assertEquals(request.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH), 5 + "");
assertEquals(request.getFirstHeaderOrNull(HttpHeaders.CONTENT_TYPE), MediaType.TEXT_PLAIN);
}
@ -93,7 +93,7 @@ public class BindCFObjectToPayloadTest {
HttpRequest request = new HttpRequest("GET", URI.create("http://localhost:8001"));
binder.bindToRequest(request, blob);
assertEquals(Utils.toStringAndClose(request.getPayload().getContent()), "hello");
assertEquals(Utils.toStringAndClose(request.getPayload().getInput()), "hello");
assertEquals(request.getFirstHeaderOrNull(HttpHeaders.CONTENT_LENGTH), 5 + "");
assertEquals(request.getFirstHeaderOrNull(HttpHeaders.ETAG),
"5d41402abc4b2a76b9719d911017c592");