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 static java.util.Collections.unmodifiableList;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
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;
/**
* 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,
*
*
* Retrofit retrofit = new Retrofit.Builder()
* .baseUrl("https://api.example.com/")
* .addConverterFactory(GsonConverterFactory.create())
* .build();
*
* MyApi api = retrofit.create(MyApi.class);
* Response<User> user = api.getUser().execute();
*
*
* @author Bob Lee ([email protected])
* @author Jake Wharton ([email protected])
*/
public final class Retrofit {
private final Map> serviceMethodCache = new ConcurrentHashMap<>();
final okhttp3.Call.Factory callFactory;
final HttpUrl baseUrl;
final List converterFactories;
final List callAdapterFactories;
final @Nullable Executor callbackExecutor;
final boolean validateEagerly;
Retrofit(
okhttp3.Call.Factory callFactory,
HttpUrl baseUrl,
List converterFactories,
List callAdapterFactories,
@Nullable Executor callbackExecutor,
boolean validateEagerly) {
this.callFactory = callFactory;
this.baseUrl = baseUrl;
this.converterFactories = converterFactories; // Copy+unmodifiable at call site.
this.callAdapterFactories = callAdapterFactories; // Copy+unmodifiable at call site.
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 multipart 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) {
validateServiceInterface(service);
return (T)
Proxy.newProxyInstance(
service.getClassLoader(),
new Class>[] {service},
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable 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);
}
args = args != null ? args : emptyArgs;
return platform.isDefaultMethod(method)
? platform.invokeDefaultMethod(method, service, proxy, args)
: loadServiceMethod(method).invoke(args);
}
});
}
private void validateServiceInterface(Class> service) {
if (!service.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
Deque> check = new ArrayDeque<>(1);
check.add(service);
while (!check.isEmpty()) {
Class> candidate = check.removeFirst();
if (candidate.getTypeParameters().length != 0) {
StringBuilder message =
new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());
if (candidate != service) {
message.append(" which is an interface of ").append(service.getName());
}
throw new IllegalArgumentException(message.toString());
}
Collections.addAll(check, candidate.getInterfaces());
}
if (validateEagerly) {
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) {
if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {
loadServiceMethod(method);
}
}
}
}
ServiceMethod> loadServiceMethod(Method method) {
ServiceMethod> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
/**
* 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;
}
/** The API base URL. */
public HttpUrl baseUrl() {
return baseUrl;
}
/**
* Returns a list of the factories tried when creating a {@linkplain #callAdapter(Type,
* Annotation[])} call adapter}.
*/
public List callAdapterFactories() {
return callAdapterFactories;
}
/**
* 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(
@Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {
Objects.requireNonNull(returnType, "returnType == null");
Objects.requireNonNull(annotations, "annotations == null");
int start = callAdapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
CallAdapter, ?> adapter = callAdapterFactories.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(callAdapterFactories.get(i).getClass().getName());
}
builder.append('\n');
}
builder.append(" Tried:");
for (int i = start, count = callAdapterFactories.size(); i < count; i++) {
builder.append("\n * ").append(callAdapterFactories.get(i).getClass().getName());
}
throw new IllegalArgumentException(builder.toString());
}
/**
* Returns an unmodifiable list of the factories tried when creating a {@linkplain
* #requestBodyConverter(Type, Annotation[], Annotation[]) request body converter}, a {@linkplain
* #responseBodyConverter(Type, Annotation[]) response body converter}, or a {@linkplain
* #stringConverter(Type, Annotation[]) string converter}.
*/
public List converterFactories() {
return 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(
@Nullable Converter.Factory skipPast,
Type type,
Annotation[] parameterAnnotations,
Annotation[] methodAnnotations) {
Objects.requireNonNull(type, "type == null");
Objects.requireNonNull(parameterAnnotations, "parameterAnnotations == null");
Objects.requireNonNull(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, RequestBody> 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(
@Nullable Converter.Factory skipPast, Type type, Annotation[] annotations) {
Objects.requireNonNull(type, "type == null");
Objects.requireNonNull(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) {
Objects.requireNonNull(type, "type == null");
Objects.requireNonNull(annotations, "annotations == null");
for (int i = 0, count = converterFactories.size(); i < count; i++) {
Converter, String> 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}. This may be {@code null}, in
* which case callbacks should be made synchronously on the background thread.
*/
public @Nullable Executor callbackExecutor() {
return callbackExecutor;
}
public Builder newBuilder() {
return new Builder(this);
}
/**
* 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 final Platform platform;
private @Nullable okhttp3.Call.Factory callFactory;
private @Nullable HttpUrl baseUrl;
private final List converterFactories = new ArrayList<>();
private final List callAdapterFactories = new ArrayList<>();
private @Nullable Executor callbackExecutor;
private boolean validateEagerly;
Builder(Platform platform) {
this.platform = platform;
}
public Builder() {
this(Platform.get());
}
Builder(Retrofit retrofit) {
platform = Platform.get();
callFactory = retrofit.callFactory;
baseUrl = retrofit.baseUrl;
// Do not add the default BuiltIntConverters and platform-aware converters added by build().
for (int i = 1,
size = retrofit.converterFactories.size() - platform.defaultConverterFactoriesSize();
i < size;
i++) {
converterFactories.add(retrofit.converterFactories.get(i));
}
// Do not add the default, platform-aware call adapters added by build().
for (int i = 0,
size =
retrofit.callAdapterFactories.size() - platform.defaultCallAdapterFactoriesSize();
i < size;
i++) {
callAdapterFactories.add(retrofit.callAdapterFactories.get(i));
}
callbackExecutor = retrofit.callbackExecutor;
validateEagerly = retrofit.validateEagerly;
}
/**
* The HTTP client used for requests.
*
* This is a convenience method for calling {@link #callFactory}.
*/
public Builder client(OkHttpClient client) {
return callFactory(Objects.requireNonNull(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 = Objects.requireNonNull(factory, "factory == null");
return this;
}
/**
* Set the API base URL.
*
* @see #baseUrl(HttpUrl)
*/
public Builder baseUrl(URL baseUrl) {
Objects.requireNonNull(baseUrl, "baseUrl == null");
return baseUrl(HttpUrl.get(baseUrl.toString()));
}
/**
* Set the API base URL.
*
* @see #baseUrl(HttpUrl)
*/
public Builder baseUrl(String baseUrl) {
Objects.requireNonNull(baseUrl, "baseUrl == null");
return baseUrl(HttpUrl.get(baseUrl));
}
/**
* Set the API base URL.
*
*