retrofit2.Retrofit Maven / Gradle / Ivy
Show all versions of retrofit Show documentation
/*
* Copyright (C) 2012 Square, Inc.
*
* Licensed 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 retrofit2;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.Header;
import retrofit2.http.Url;
import static retrofit2.Utils.checkNotNull;
/**
* Retrofit adapts a Java interface to HTTP calls by using annotations on the declared methods to
* define how requests are made. Create instances using {@linkplain Builder
* the builder} and pass your interface to {@link #create} to generate an implementation.
*
* For example,
*
{@code
* Retrofit retrofit = new Retrofit.Builder()
* .baseUrl("http://api.example.com")
* .addConverterFactory(GsonConverterFactory.create())
* .build();
*
* MyApi api = retrofit.create(MyApi.class);
* Response user = api.getUser().execute();
* }
*
* @author Bob Lee ([email protected])
* @author Jake Wharton ([email protected])
*/
public final class Retrofit {
private final Map methodHandlerCache = new LinkedHashMap<>();
private final okhttp3.Call.Factory callFactory;
private final BaseUrl baseUrl;
private final List converterFactories;
private final List adapterFactories;
private final Executor callbackExecutor;
private final boolean validateEagerly;
Retrofit(okhttp3.Call.Factory callFactory, BaseUrl baseUrl,
List converterFactories, List adapterFactories,
Executor callbackExecutor, boolean validateEagerly) {
this.callFactory = callFactory;
this.baseUrl = baseUrl;
this.converterFactories = converterFactories;
this.adapterFactories = adapterFactories;
this.callbackExecutor = callbackExecutor;
this.validateEagerly = validateEagerly;
}
/**
* Create an implementation of the API endpoints defined by the {@code service} interface.
*
* The relative path for a given method is obtained from an annotation on the method describing
* the request type. The built-in methods are {@link retrofit2.http.GET GET},
* {@link retrofit2.http.PUT PUT}, {@link retrofit2.http.POST POST}, {@link retrofit2.http.PATCH
* PATCH}, {@link retrofit2.http.HEAD HEAD}, {@link retrofit2.http.DELETE DELETE} and
* {@link retrofit2.http.OPTIONS OPTIONS}. You can use a custom HTTP method with
* {@link HTTP @HTTP}. For a dynamic URL, omit the path on the annotation and annotate the first
* parameter with {@link Url @Url}.
*
* Method parameters can be used to replace parts of the URL by annotating them with
* {@link retrofit2.http.Path @Path}. Replacement sections are denoted by an identifier
* surrounded by curly braces (e.g., "{foo}"). To add items to the query string of a URL use
* {@link retrofit2.http.Query @Query}.
*
* The body of a request is denoted by the {@link retrofit2.http.Body @Body} annotation. The
* object will be converted to request representation by one of the {@link Converter.Factory}
* instances. A {@link RequestBody} can also be used for a raw representation.
*
* Alternative request body formats are supported by method annotations and corresponding
* parameter annotations:
*
* - {@link retrofit2.http.FormUrlEncoded @FormUrlEncoded} - Form-encoded data with key-value
* pairs specified by the {@link retrofit2.http.Field @Field} parameter annotation.
*
- {@link retrofit2.http.Multipart @Multipart} - RFC 2388-compliant multi-part data with
* parts specified by the {@link retrofit2.http.Part @Part} parameter annotation.
*
*
* Additional static headers can be added for an endpoint using the
* {@link retrofit2.http.Headers @Headers} method annotation. For per-request control over a
* header annotate a parameter with {@link Header @Header}.
*
* By default, methods return a {@link Call} which represents the HTTP request. The generic
* parameter of the call is the response body type and will be converted by one of the
* {@link Converter.Factory} instances. {@link ResponseBody} can also be used for a raw
* representation. {@link Void} can be used if you do not care about the body contents.
*
* For example:
*
* public interface CategoryService {
* @POST("category/{cat}/")
* Call<List<Item>> categoryList(@Path("cat") String a, @Query("page") int b);
* }
*
*/
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public T create(final Class service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadMethodHandler(method).invoke(args);
}
});
}
private void eagerlyValidateMethods(Class service) {
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) {
if (!platform.isDefaultMethod(method)) {
loadMethodHandler(method);
}
}
}
MethodHandler loadMethodHandler(Method method) {
MethodHandler handler;
synchronized (methodHandlerCache) {
handler = methodHandlerCache.get(method);
if (handler == null) {
handler = MethodHandler.create(this, method);
methodHandlerCache.put(method, handler);
}
}
return handler;
}
/**
* The factory used to create {@linkplain okhttp3.Call OkHttp calls} for sending a HTTP requests.
* Typically an instance of {@link OkHttpClient}.
*/
public okhttp3.Call.Factory callFactory() {
return callFactory;
}
public BaseUrl baseUrl() {
return baseUrl;
}
public List callAdapterFactories() {
return Collections.unmodifiableList(adapterFactories);
}
/**
* Returns the {@link CallAdapter} for {@code returnType} from the available {@linkplain
* #callAdapterFactories() factories}.
*
* @throws IllegalArgumentException if no call adapter available for {@code type}.
*/
public CallAdapter callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}
/**
* Returns the {@link CallAdapter} for {@code returnType} from the available {@linkplain
* #callAdapterFactories() factories} except {@code skipPast}.
*
* @throws IllegalArgumentException if no call adapter available for {@code type}.
*/
public CallAdapter nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
checkNotNull(returnType, "returnType == null");
checkNotNull(annotations, "annotations == null");
int start = adapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = adapterFactories.size(); i < count; i++) {
CallAdapter adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}
StringBuilder builder = new StringBuilder("Could not locate call adapter for ")
.append(returnType)
.append(".\n");
if (skipPast != null) {
builder.append(" Skipped:");
for (int i = 0; i < start; i++) {
builder.append("\n * ").append(adapterFactories.get(i).getClass().getName());
}
builder.append('\n');
}
builder.append(" Tried:");
for (int i = start, count = adapterFactories.size(); i < count; i++) {
builder.append("\n * ").append(adapterFactories.get(i).getClass().getName());
}
throw new IllegalArgumentException(builder.toString());
}
/**
* TODO
*/
public List converterFactories() {
return Collections.unmodifiableList(converterFactories);
}
/**
* Returns a {@link Converter} for {@code type} to {@link RequestBody} from the available
* {@linkplain #converterFactories() factories}.
*
* @throws IllegalArgumentException if no converter available for {@code type}.
*/
public Converter requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {
return nextRequestBodyConverter(null, type, parameterAnnotations, methodAnnotations);
}
/**
* Returns a {@link Converter} for {@code type} to {@link RequestBody} from the available
* {@linkplain #converterFactories() factories} except {@code skipPast}.
*
* @throws IllegalArgumentException if no converter available for {@code type}.
*/
public Converter nextRequestBodyConverter(Converter.Factory skipPast,
Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {
checkNotNull(type, "type == null");
checkNotNull(parameterAnnotations, "parameterAnnotations == null");
checkNotNull(methodAnnotations, "methodAnnotations == null");
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter.Factory factory = converterFactories.get(i);
Converter converter =
factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter) converter;
}
}
StringBuilder builder = new StringBuilder("Could not locate RequestBody converter for ")
.append(type)
.append(".\n");
if (skipPast != null) {
builder.append(" Skipped:");
for (int i = 0; i < start; i++) {
builder.append("\n * ").append(converterFactories.get(i).getClass().getName());
}
builder.append('\n');
}
builder.append(" Tried:");
for (int i = start, count = converterFactories.size(); i < count; i++) {
builder.append("\n * ").append(converterFactories.get(i).getClass().getName());
}
throw new IllegalArgumentException(builder.toString());
}
/**
* Returns a {@link Converter} for {@link ResponseBody} to {@code type} from the available
* {@linkplain #converterFactories() factories}.
*
* @throws IllegalArgumentException if no converter available for {@code type}.
*/
public Converter responseBodyConverter(Type type, Annotation[] annotations) {
return nextResponseBodyConverter(null, type, annotations);
}
/**
* Returns a {@link Converter} for {@link ResponseBody} to {@code type} from the available
* {@linkplain #converterFactories() factories} except {@code skipPast}.
*
* @throws IllegalArgumentException if no converter available for {@code type}.
*/
public Converter nextResponseBodyConverter(Converter.Factory skipPast,
Type type, Annotation[] annotations) {
checkNotNull(type, "type == null");
checkNotNull(annotations, "annotations == null");
int start = converterFactories.indexOf(skipPast) + 1;
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter) converter;
}
}
StringBuilder builder = new StringBuilder("Could not locate ResponseBody converter for ")
.append(type)
.append(".\n");
if (skipPast != null) {
builder.append(" Skipped:");
for (int i = 0; i < start; i++) {
builder.append("\n * ").append(converterFactories.get(i).getClass().getName());
}
builder.append('\n');
}
builder.append(" Tried:");
for (int i = start, count = converterFactories.size(); i < count; i++) {
builder.append("\n * ").append(converterFactories.get(i).getClass().getName());
}
throw new IllegalArgumentException(builder.toString());
}
/**
* Returns a {@link Converter} for {@code type} to {@link String} from the available
* {@linkplain #converterFactories() factories}.
*/
public Converter stringConverter(Type type, Annotation[] annotations) {
checkNotNull(type, "type == null");
checkNotNull(annotations, "annotations == null");
for (int i = 0, count = converterFactories.size(); i < count; i++) {
Converter converter =
converterFactories.get(i).stringConverter(type, annotations, this);
if (converter != null) {
//noinspection unchecked
return (Converter) converter;
}
}
// Nothing matched. Resort to default converter which just calls toString().
//noinspection unchecked
return (Converter) BuiltInConverters.ToStringConverter.INSTANCE;
}
/** The executor used for {@link Callback} methods on a {@link Call}. */
public Executor callbackExecutor() {
return callbackExecutor;
}
/**
* Build a new {@link Retrofit}.
*
* Calling {@link #baseUrl} is required before calling {@link #build()}. All other methods
* are optional.
*/
public static final class Builder {
private okhttp3.Call.Factory callFactory;
private BaseUrl baseUrl;
private List converterFactories = new ArrayList<>();
private List adapterFactories = new ArrayList<>();
private Executor callbackExecutor;
private boolean validateEagerly;
public Builder() {
// Add the built-in converter factory first. This prevents overriding its behavior but also
// ensures correct behavior when using converters that consume all types.
converterFactories.add(new BuiltInConverters());
}
/**
* The HTTP client used for requests.
*
* This is a convenience method for calling {@link #callFactory}.
*
* Note: This method does not make a defensive copy of {@code client}. Changes to its
* settings will affect subsequent requests. Pass in a {@linkplain OkHttpClient#clone() cloned}
* instance to prevent this if desired.
*/
public Builder client(OkHttpClient client) {
return callFactory(checkNotNull(client, "client == null"));
}
/**
* Specify a custom call factory for creating {@link Call} instances.
*
* Note: Calling {@link #client} automatically sets this value.
*/
public Builder callFactory(okhttp3.Call.Factory factory) {
this.callFactory = checkNotNull(factory, "factory == null");
return this;
}
/**
* Set a fixed API base URL.
*
* @see #baseUrl(HttpUrl)
*/
public Builder baseUrl(String baseUrl) {
checkNotNull(baseUrl, "baseUrl == null");
HttpUrl httpUrl = HttpUrl.parse(baseUrl);
if (httpUrl == null) {
throw new IllegalArgumentException("Illegal URL: " + baseUrl);
}
return baseUrl(httpUrl);
}
/**
* Set a fixed API base URL.
*