提交 a1cee2eb 编写于 作者: E Eric Denman

JSON request entity support

上级 08396f35
# Retrofit
Reusable Java and Android code from Square, Inc.
Reusable Java and Android code from Square, Inc.
Note that in order to make IntelliJ happy, you'll need to run an ant build (to download all the dependencies).
Modules:
+ IO: Utility classes for doing low-level java I/O.
+ Http: Abstracts away the messy logic of making network calls (depends on IO).
+ Core: Some interfaces and utilities used by the other retrofit modules.
+ Android: Contains two Android-specific utility classes: ShakeDetector (for detecting device
shakes) and QueueFile (for storing a queue on the android file-system).
Note that IntelliJ will complain about compilation errors until you run `ant` (to download all the
dependencies). This command also generates the .jar files that you'll want to include in your
application.
## Http Usage
Create an interface for your API. You can create as many of these interfaces as you like. For
each interface you create, calling `RestAdapter.service(MyInterface.class)` will create an
instance of that API handler, which you can then store and use throughout your application. An
example interface:
public interface DummyService {
// Produces a url like "foo/bar?id=idValue".
@GET("foo/bar")
void normalGet(@Named("id") String id, Callback<SimpleResponse> callback);
// Produces a url like "foo/idValue/bar?category=categoryValue".
@GET("foo/{id}/bar")
void getWithPathParam(@Named("id") String id, @Named("category") String category, Callback<SimpleResponse> callback);
// Produces a url like "foo/bar/idValue" and body like "id=idValue&body=bodyValue".
@POST("foo/bar/{id}")
void normalPost(@Named("id") String id, @Named("body") String body, Callback<SimpleResponse> callback);
// Produces a url like "foo/bar/idValue" and body generated by MyJsonObj.
@POST("foo/bar/{id}")
void singleEntityPost(@SingleEntity MyJsonObj card, @Named("id") String id, Callback<SimpleResponse> callback);
}
Note that each method _must_ have a Callback object at the end of the parameter list. This is how
your application will handle the results of your network calls: errors and successful responses are
both handled by the Callback interface.
If you want to use the @SingleEntity method of specifying request body (see singleEntityPost above),
your MyJsonObject will need to implement TypedBytes. For convenience, you can extend
\GsonRequestEntity if you're just trying to send a JSON string in the request body.
Also worth noting: for POST/PUT requests using default form encoding for the request entity (see
normalPost), any path parameters are also included in the request body. This is different from the
behavior of GET/DELETE, where path parameters are excluded from the query string.
\ No newline at end of file
// Copyright 2011 Square, Inc.
package retrofit.http;
import com.google.inject.name.Named;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.Set;
import java.util.UUID;
import junit.framework.TestCase;
import org.apache.http.HttpMessage;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import retrofit.core.Callback;
/** @author Eric Denman (edenman@squareup.com) */
public class HttpRequestBuilderTest extends TestCase {
public static final String API_URL = "http://taqueria.com/lengua/taco";
public static final Headers BLANK_HEADERS = new Headers() {
@Override public void setOn(HttpMessage message, String mimeType) {
}
};
public void testRegex() throws Exception {
expectParams("");
expectParams("foo");
expectParams("foo/bar");
expectParams("foo/bar/{taco}", "taco");
expectParams("foo/bar/{t}", "t");
expectParams("foo/bar/{taco}/or/{burrito}", "taco", "burrito");
expectParams("foo/bar/{taco}/or/{taco}", "taco");
expectParams("foo/bar/{taco-shell}", "taco-shell");
expectParams("foo/bar/{taco_shell}", "taco_shell");
}
private void expectParams(String path, String... expected) {
Set<String> calculated = HttpRequestBuilder.getPathParameters(path);
assertEquals(expected.length, calculated.size());
for (String val : expected) {
assertTrue(calculated.contains(val));
}
}
public void testNormalGet() throws Exception {
Method method =
MyService.class.getMethod("normalGet", String.class, Callback.class);
String expectedId = UUID.randomUUID().toString();
Object[] args = new Object[] {expectedId, new MyCallback()};
HttpUriRequest request = build(method, args);
assertTrue(request instanceof HttpGet);
HttpGet put = (HttpGet) request;
// Make sure the url param got translated.
final String uri = put.getURI().toString();
assertEquals(API_URL + "/foo/bar?id=" + expectedId, uri);
}
public void testGetWithPathParam() throws Exception {
Method method =
MyService.class.getMethod("getWithPathParam", String.class, String.class, Callback.class);
String expectedId = UUID.randomUUID().toString();
String category = UUID.randomUUID().toString();
Object[] args = new Object[] {expectedId, category, new MyCallback()};
HttpUriRequest request = build(method, args);
assertTrue(request instanceof HttpGet);
HttpGet put = (HttpGet) request;
// Make sure the url param got translated.
final String uri = put.getURI().toString();
assertEquals(API_URL + "/foo/" + expectedId + "/bar?category=" + category, uri);
}
public void testSingleEntityWithPathParams() throws Exception {
Method method =
MyService.class.getMethod("singleEntityPut", MyJsonObj.class, String.class, Callback.class);
String expectedId = UUID.randomUUID().toString();
String bodyText = UUID.randomUUID().toString();
Object[] args = new Object[] {new MyJsonObj(bodyText), expectedId, new MyCallback()};
HttpUriRequest request = build(method, args);
assertTrue(request instanceof HttpPut);
HttpPut put = (HttpPut) request;
// Make sure the url param got translated.
final String uri = put.getURI().toString();
assertEquals(API_URL + "/foo/bar/" + expectedId, uri);
// Make sure the request body has the json string.
ByteArrayOutputStream out = new ByteArrayOutputStream();
put.getEntity().writeTo(out);
final String requestBody = out.toString();
assertEquals("{\"bodyText\":\"" + bodyText + "\"}", requestBody);
}
public void testNormalPutWithPathParams() throws Exception {
Method method =
MyService.class.getMethod("normalPut", String.class, String.class, Callback.class);
String expectedId = UUID.randomUUID().toString();
String bodyText = UUID.randomUUID().toString();
Object[] args = new Object[] {expectedId, bodyText, new MyCallback()};
HttpUriRequest request = build(method, args);
assertTrue(request instanceof HttpPut);
HttpPut put = (HttpPut) request;
// Make sure the url param got translated.
final String uri = put.getURI().toString();
assertEquals(API_URL + "/foo/bar/" + expectedId, uri);
// Make sure the request body has the json string.
ByteArrayOutputStream out = new ByteArrayOutputStream();
put.getEntity().writeTo(out);
final String requestBody = out.toString();
assertEquals("id=" + expectedId + "&body=" + bodyText, requestBody);
}
public void testSingleEntityWithTooManyParams() throws Exception {
Method method =
MyService.class.getMethod("tooManyParams", MyJsonObj.class, String.class, String.class,
Callback.class);
String expectedId = UUID.randomUUID().toString();
String bodyText = UUID.randomUUID().toString();
Object[] args = new Object[] {new MyJsonObj(bodyText), expectedId, "EXTRA", new MyCallback()};
try {
build(method, args);
fail("Didn't throw exception with too many params");
} catch (IllegalArgumentException e) {
// Expected
}
}
public void testSingleEntityWithNoPathParam() throws Exception {
Method method =
MyService.class.getMethod("singleEntityNoPathParam", MyJsonObj.class, Callback.class);
String bodyText = UUID.randomUUID().toString();
Object[] args = new Object[] {new MyJsonObj(bodyText), new MyCallback()};
try {
build(method, args);
fail("Didn't throw exception with too few params");
} catch (IllegalArgumentException e) {
// Expected
}
}
public void testRegularWithNoPathParam() throws Exception {
Method method = MyService.class.getMethod("regularNoPathParam", String.class, Callback.class);
String otherParam = UUID.randomUUID().toString();
Object[] args = new Object[] {otherParam, new MyCallback()};
try {
build(method, args);
fail("Didn't throw exception with too few params");
} catch (IllegalArgumentException e) {
// Expected
}
}
@SuppressWarnings({"UnusedDeclaration"}) // Methods are accessed by reflection.
private static interface MyService {
@GET("foo/bar") void normalGet(@Named("id") String id, Callback<SimpleResponse> callback);
@GET("foo/{id}/bar")
void getWithPathParam(@Named("id") String id, @Named("category") String category,
Callback<SimpleResponse> callback);
@PUT("foo/bar/{id}") void singleEntityPut(@SingleEntity MyJsonObj card, @Named("id") String id,
Callback<SimpleResponse> callback);
@PUT("foo/bar/{id}") void normalPut(@Named("id") String id, @Named("body") String body,
Callback<SimpleResponse> callback);
@PUT("foo/bar/{id}") void tooManyParams(@SingleEntity MyJsonObj card, @Named("id") String id,
@Named("extra") String extraParam, Callback<SimpleResponse> callback);
@PUT("foo/bar/{id}")
void singleEntityNoPathParam(@SingleEntity MyJsonObj card, Callback<SimpleResponse> callback);
@PUT("foo/bar/{id}")
void regularNoPathParam(@Named("other") String other, Callback<SimpleResponse> callback);
}
private HttpUriRequest build(Method method, Object[] args) throws URISyntaxException {
return new HttpRequestBuilder().setMethod(method)
.setArgs(args)
.setApiUrl(API_URL)
.setHeaders(BLANK_HEADERS)
.build();
}
private static class MyJsonObj {
@SuppressWarnings({"UnusedDeclaration"}) // Accessed by json serialization.
private String bodyText;
public MyJsonObj(String bodyText) {
this.bodyText = bodyText;
}
}
private static class SimpleResponse {
}
private class MyCallback implements Callback<SimpleResponse> {
@Override public void preInvoke() {
}
@Override public void call(SimpleResponse simpleResponse) {
}
@Override public void sessionExpired() {
}
@Override public void networkError() {
}
@Override public void clientError(SimpleResponse response) {
}
@Override public void serverError(String message) {
}
@Override public void unexpectedError(Throwable t) {
}
}
}
......@@ -24,7 +24,6 @@ import org.easymock.IAnswer;
import org.junit.Before;
import retrofit.core.Callback;
import retrofit.core.MainThread;
import retrofit.internal.gson.Gson;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
......@@ -33,6 +32,7 @@ import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static retrofit.http.GsonProvider.gson;
import static retrofit.http.RestAdapter.service;
public class RestAdapterTest extends TestCase {
......@@ -260,7 +260,7 @@ public class RestAdapterTest extends TestCase {
expectExecution(mockMainThread); // For call()
expectSetOnWithRequest(requestClass, requestUrl);
Response response = new Response("some text");
expectResponseCalls(new Gson().toJson(response));
expectResponseCalls(gson().toJson(response));
expectHttpClientExecute();
expectCallbacks(response);
}
......@@ -294,7 +294,8 @@ public class RestAdapterTest extends TestCase {
private <T extends HttpUriRequest> void expectSetOnWithRequest(
final Class<T> expectedRequestClass, final String expectedUri) {
final Capture<HttpMessage> capture = new Capture<HttpMessage>();
mockHeaders.setOn(capture(capture));
final Capture<String> captureMime = new Capture<String>();
mockHeaders.setOn(capture(capture), capture(captureMime));
expectLastCall().andAnswer(new IAnswer<Object>() {
@Override public Object answer() throws Throwable {
T request = expectedRequestClass.cast(capture.getValue());
......
......@@ -10,7 +10,8 @@ import org.apache.http.StatusLine;
import org.apache.http.client.ResponseHandler;
import org.apache.http.entity.BufferedHttpEntity;
import retrofit.core.Callback;
import retrofit.internal.gson.Gson;
import static retrofit.http.GsonProvider.gson;
/**
* Support for response handlers that invoke {@link Callback}.
......@@ -138,7 +139,7 @@ public abstract class CallbackResponseHandler<T>
if (statusCode == BAD_GATEWAY || statusCode == GATEWAY_TIMEOUT
|| statusCode < 500) {
try {
ServerError serverError = new Gson().fromJson(body, ServerError.class);
ServerError serverError = gson().fromJson(body, ServerError.class);
if (serverError != null) return serverError.message;
} catch (Throwable t) {
// The server error takes precedence.
......
// Copyright 2011 Square, Inc.
package retrofit.http;
import retrofit.internal.gson.Gson;
/**
* Creating a Gson instance is relatively expensive (70ms on my MBP), and Gson is thread-safe.
* Create the instance once so we're not constantly creating them.
*
* @author Eric Denman (edenman@squareup.com)
*/
public class GsonProvider {
/** Lazily loads the Gson instance. */
private static class Holder {
static final Gson gson = new Gson();
}
public static Gson gson() {
return Holder.gson;
}
}
......@@ -7,9 +7,10 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.HttpEntity;
import retrofit.core.Callback;
import retrofit.internal.gson.Gson;
import retrofit.internal.gson.JsonParseException;
import static retrofit.http.GsonProvider.gson;
/**
* Converts JSON response to an object using Gson and then passes it to {@link
* Callback#call(T)}.
......@@ -46,7 +47,7 @@ class GsonResponseHandler<T> extends CallbackResponseHandler<T> {
* We derived type from Callback<T>, so we know we're safe.
*/
@SuppressWarnings("unchecked")
T t = (T) new Gson().fromJson(in, type);
T t = (T) gson().fromJson(in, type);
return t;
} catch (JsonParseException e) {
// The server returned us bad JSON!
......
......@@ -10,6 +10,6 @@ import org.apache.http.HttpMessage;
*/
public interface Headers {
/** Sets headers on the given message. */
public void setOn(HttpMessage message);
/** Sets headers on the given message, with the specified MIME type */
public void setOn(HttpMessage message, String mimeType);
}
......@@ -34,7 +34,7 @@ public enum HttpMethodType {
throws URISyntaxException {
URI uri = getParameterizedUri(builder);
HttpGet request = new HttpGet(uri);
builder.getHeaders().setOn(request);
builder.getHeaders().setOn(request, builder.getMimeType());
return request;
}
},
......@@ -45,7 +45,7 @@ public enum HttpMethodType {
URI uri = getUri(builder);
HttpPost request = new HttpPost(uri);
addParams(request, builder);
builder.getHeaders().setOn(request);
builder.getHeaders().setOn(request, builder.getMimeType());
return request;
}
},
......@@ -56,7 +56,7 @@ public enum HttpMethodType {
URI uri = getUri(builder);
HttpPut request = new HttpPut(uri);
addParams(request, builder);
builder.getHeaders().setOn(request);
builder.getHeaders().setOn(request, builder.getMimeType());
return request;
}
},
......@@ -66,7 +66,7 @@ public enum HttpMethodType {
throws URISyntaxException {
URI uri = getParameterizedUri(builder);
HttpDelete request = new HttpDelete(uri);
builder.getHeaders().setOn(request);
builder.getHeaders().setOn(request, builder.getMimeType());
return request;
}
};
......@@ -92,9 +92,8 @@ public enum HttpMethodType {
throws URISyntaxException {
List<NameValuePair> queryParams = builder.getParamList(false);
String queryString = URLEncodedUtils.format(queryParams, "UTF-8");
URI uri = URIUtils.createURI(builder.getScheme(), builder.getHost(), -1,
builder.getRelativePath(), queryString, null);
return uri;
return URIUtils.createURI(builder.getScheme(), builder.getHost(), -1, builder.getRelativePath(),
queryString, null);
}
/**
......@@ -111,7 +110,7 @@ public enum HttpMethodType {
method.getParameterAnnotations();
int count = parameterAnnotations.length - 1;
if (useMultipart(parameterTypes)) {
if (useMultipart(parameterTypes, parameterAnnotations)) {
MultipartEntity form = new MultipartEntity(
HttpMultipartMode.BROWSER_COMPATIBLE);
for (int i = 0; i < count; i++) {
......@@ -135,9 +134,15 @@ public enum HttpMethodType {
request.setEntity(form);
} else {
try {
List<NameValuePair> paramList = builder.getParamList(true);
if (builder.getSingleEntity() != null) {
final TypedBytesEntity entity = new TypedBytesEntity(builder.getSingleEntity());
request.setEntity(entity);
request.addHeader("Content-Type", entity.getMimeType().mimeName());
} else {
List<NameValuePair> paramList = builder.getParamList(true);
// TODO: Use specified encoding. (See CallbackResponseHandler et al)
request.setEntity(new UrlEncodedFormEntity(paramList, HTTP.UTF_8));
}
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
......@@ -145,9 +150,21 @@ public enum HttpMethodType {
}
/** Returns true if the parameters contain a file upload. */
private static boolean useMultipart(Class<?>[] parameterTypes) {
for (Class<?> parameterType : parameterTypes) {
if (TypedBytes.class.isAssignableFrom(parameterType)) return true;
private static boolean useMultipart(Class<?>[] parameterTypes, Annotation[][] parameterAnnotations) {
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
Annotation[] annotations = parameterAnnotations[i];
if (TypedBytes.class.isAssignableFrom(parameterType)
&& !hasSingleEntityAnnotation(annotations)) {
return true;
}
}
return false;
}
private static boolean hasSingleEntityAnnotation(Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(SingleEntity.class)) return true;
}
return false;
}
......
package retrofit.http;
import com.google.inject.name.Named;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicNameValuePair;
import retrofit.io.MimeType;
import retrofit.io.TypedBytes;
import static retrofit.http.GsonProvider.gson;
/**
* Builds HTTP requests from Java method invocations. Handles "path parameters"
......@@ -25,8 +34,7 @@ import org.apache.http.message.BasicNameValuePair;
* </ol>
*/
final class HttpRequestBuilder {
private static final Logger logger =
Logger.getLogger(HttpRequestBuilder.class.getName());
private static final Logger logger = Logger.getLogger(HttpRequestBuilder.class.getName());
private Method javaMethod;
private Object[] args;
......@@ -35,6 +43,7 @@ final class HttpRequestBuilder {
private Headers headers;
private List<NameValuePair> nonPathParams;
private RequestLine requestLine;
private TypedBytes singleEntity;
HttpRequestBuilder setMethod(Method method) {
this.javaMethod = method;
......@@ -47,12 +56,7 @@ final class HttpRequestBuilder {
}
String getRelativePath() {
return replacedRelativePath != null ? replacedRelativePath
: requestLine.getRelativePath();
}
private boolean hasPathParameters() {
return requestLine.getRelativePath().contains("{");
return replacedRelativePath != null ? replacedRelativePath : requestLine.getRelativePath();
}
HttpRequestBuilder setApiUrl(String apiUrl) {
......@@ -84,29 +88,24 @@ final class HttpRequestBuilder {
}
String getHost() {
String host = apiUrl.substring(
apiUrl.indexOf("://") + 3, apiUrl.length());
String host = apiUrl.substring(apiUrl.indexOf("://") + 3, apiUrl.length());
if (host.endsWith("/")) host = host.substring(0, host.length() - 1);
return host;
}
/**
* Converts all but the last method argument to a list of HTTP request
* parameters. If includePathParams is true, path parameters (like id in
* "/entity/{id}" will be included in this list.
* Converts all but the last method argument to a list of HTTP request parameters. If
* includePathParams is true, path parameters (like id in "/entity/{id}" will be included in this
* list.
*/
List<NameValuePair> getParamList(boolean includePathParams) {
if (includePathParams || nonPathParams == null) return createParamList();
return nonPathParams;
}
/**
* Converts all but the last method argument to a list of HTTP request
* parameters.
*/
/** Converts all but the last method argument to a list of HTTP request parameters. */
private List<NameValuePair> createParamList() {
Annotation[][] parameterAnnotations =
javaMethod.getParameterAnnotations();
Annotation[][] parameterAnnotations = javaMethod.getParameterAnnotations();
int count = parameterAnnotations.length - 1;
List<NameValuePair> params = new ArrayList<NameValuePair>(count);
......@@ -130,48 +129,107 @@ final class HttpRequestBuilder {
for (int i = 0; i < count; i++) {
Object arg = args[i];
if (arg == null) continue;
String name = getName(parameterAnnotations[i], javaMethod, i);
params.add(new BasicNameValuePair(name, String.valueOf(arg)));
for (Annotation annotation : parameterAnnotations[i]) {
final Class<? extends Annotation> type = annotation.annotationType();
if (type == Named.class) {
String name = getName(parameterAnnotations[i], javaMethod, i);
params.add(new BasicNameValuePair(name, String.valueOf(arg)));
} else if (type == SingleEntity.class) {
if (arg instanceof TypedBytes) { // Let the object specify its own entity representation.
singleEntity = (TypedBytes) arg;
} else { // Just an object: serialize it with json
singleEntity = new JsonTypedBytes(arg);
}
}
}
}
return params;
}
public TypedBytes getSingleEntity() {
return singleEntity;
}
/**
* If this builder has a custom mime-type for the request, this returns it.
*
* @return "Content-Type" string if present, null otherwise.
*/
public String getMimeType() {
return singleEntity == null ? null : singleEntity.mimeType().mimeName();
}
private BasicNameValuePair addPair(QueryParam queryParam) {
return new BasicNameValuePair(queryParam.name(), queryParam.value());
}
HttpUriRequest build() throws URISyntaxException {
// Alter parameter list if path parameters are present.
if (hasPathParameters()) {
List<NameValuePair> paramList = createParamList();
Set<String> pathParams = getPathParameters(requestLine.getRelativePath());
List<NameValuePair> paramList = createParamList();
if (!pathParams.isEmpty()) {
String replacedPath = requestLine.getRelativePath();
Iterator<NameValuePair> itor = paramList.iterator();
while (itor.hasNext()) {
NameValuePair pair = itor.next();
String paramName = pair.getName();
if (replacedPath.contains("{" + paramName + "}")) {
replacedPath = replacedPath.replaceAll(
"\\{" + paramName + "\\}", pair.getValue());
itor.remove();
for (String pathParam : pathParams) {
NameValuePair found = null;
for (NameValuePair param : paramList) {
if (param.getName().equals(pathParam)) {
found = param;
}
}
if (found != null) {
replacedPath = doReplace(replacedPath, found.getName(), found.getValue());
paramList.remove(found);
} else {
throw new IllegalArgumentException(
"Got pathParam " + pathParam + " that wasn't specified with @Named param.");
}
}
replacedRelativePath = replacedPath;
nonPathParams = paramList;
}
if (getSingleEntity() != null) {
// We're passing a JSON object as the main entity: paramList should only contain path
// parameter values.
if (!paramList.isEmpty()) {
throw new IllegalArgumentException("Found @Named param on single-entity request that "
+ "wasn't used for path substitution: this shouldn't be on the method.");
}
}
HttpUriRequest request = requestLine.getHttpMethod().createFrom(this);
if (logger.isLoggable(Level.FINE)) logger.fine("Request params: "+getParamList(true));
if (logger.isLoggable(Level.FINE)) logger.fine("Request params: " + getParamList(true));
return request;
}
private String doReplace(String replacedPath, String paramName, String newVal) {
replacedPath = replacedPath.replaceAll("\\{" + paramName + "\\}", newVal);
return replacedPath;
}
/**
* Gets the set of unique path params used in the given uri. If a param is used twice in the uri,
* it will only show up once in the set.
*
* @param path the path to search through.
* @return set of path params.
*/
static Set<String> getPathParameters(String path) {
Pattern p = Pattern.compile("\\{([a-z_-]*)\\}");
Matcher m = p.matcher(path);
Set<String> patterns = new HashSet<String>();
while (m.find()) {
patterns.add(m.group(1));
}
return patterns;
}
/** Gets the parameter name from the @Named annotation. */
static String getName(Annotation[] annotations, Method method,
int parameterIndex) {
return findAnnotation(annotations, Named.class, method,
parameterIndex).value();
static String getName(Annotation[] annotations, Method method, int parameterIndex) {
return findAnnotation(annotations, Named.class, method, parameterIndex).value();
}
/**
......@@ -179,15 +237,40 @@ final class HttpRequestBuilder {
*
* @throws IllegalArgumentException if the annotation isn't found
*/
private static <A extends Annotation> A findAnnotation(
Annotation[] annotations, Class<A> annotationType, Method method,
int parameterIndex) {
private static <A extends Annotation> A findAnnotation(Annotation[] annotations,
Class<A> annotationType, Method method, int parameterIndex) {
for (Annotation annotation : annotations) {
if (annotation.annotationType() == annotationType) {
return annotationType.cast(annotation);
}
}
throw new IllegalArgumentException(annotationType + " missing on"
+ " parameter #" + parameterIndex + " of " + method + ".");
throw new IllegalArgumentException(
annotationType + " missing on" + " parameter #" + parameterIndex + " of " + method + ".");
}
private class JsonTypedBytes implements TypedBytes {
private Object obj;
public JsonTypedBytes(Object obj) {
this.obj = obj;
}
@Override public MimeType mimeType() {
return MimeType.JSON;
}
@Override public int length() {
return toJson().length();
}
@Override public void writeTo(OutputStream out) throws IOException {
final String str = toJson();
// TODO use requested encoding?
out.write(str.getBytes("UTF-8"));
}
protected String toJson() {
return gson().toJson(obj);
}
}
}
\ No newline at end of file
// Copyright 2011 Square, Inc.
package retrofit.http;
/**
* Use this annotation on a service method param when you want to directly control the request body
* of a POST/PUT request (instead of sending in as request parameters or form-style request
* body). If the value of the parameter implements TypedBytes, the request body will be written
* exactly as specified by the TypedBytes.writeTo object. If it doesn't implement TypedBytes, the
* object will be serialized into JSON and the result will be set directly as the request body.
*
* @author Eric Denman (edenman@squareup.com)
*/
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER)
public @interface SingleEntity {
}
// Copyright 2011 Square, Inc.
package retrofit.http;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.http.entity.AbstractHttpEntity;
import retrofit.io.MimeType;
import retrofit.io.TypedBytes;
/**
* Container class for when you want to pass an entire TypedBytes as a http request entity.
*
* @author Eric Denman (edenman@squareup.com)
*/
public class TypedBytesEntity extends AbstractHttpEntity {
private TypedBytes typedBytes;
public TypedBytesEntity(TypedBytes typedBytes) {
this.typedBytes = typedBytes;
}
@Override public boolean isRepeatable() {
return true;
}
@Override public long getContentLength() {
return typedBytes.length();
}
@Override public InputStream getContent() throws IOException, IllegalStateException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
typedBytes.writeTo(out);
return new ByteArrayInputStream(out.toByteArray());
}
@Override public void writeTo(OutputStream out) throws IOException {
typedBytes.writeTo(out);
}
@Override public boolean isStreaming() {
return false;
}
public MimeType getMimeType() {
return typedBytes.mimeType();
}
}
......@@ -8,6 +8,7 @@ package retrofit.io;
*/
public enum MimeType {
JSON("application/json", "json"),
GIF("image/gif", "gif"),
PNG("image/png", "png"),
JPEG("image/jpeg", "jpg");
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册