/*
* Copyright 2013 Netflix, 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 feign;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URI;
import javax.inject.Inject;
import javax.inject.Provider;
import feign.Request.Options;
import feign.codec.Decoder;
import feign.codec.ErrorDecoder;
import static feign.FeignException.errorExecuting;
import static feign.FeignException.errorReading;
import static feign.Util.LOCATION;
import static feign.Util.checkNotNull;
import static feign.Util.firstOrNull;
final class MethodHandler {
/**
* Those using guava will implement as {@code Function}.
*/
static interface BuildTemplateFromArgs {
public RequestTemplate apply(Object[] argv);
}
static class Factory {
private final Client client;
private final Provider retryer;
private final Wire wire;
@Inject Factory(Client client, Provider retryer, Wire wire) {
this.client = checkNotNull(client, "client");
this.retryer = checkNotNull(retryer, "retryer");
this.wire = checkNotNull(wire, "wire");
}
public MethodHandler create(Target> target, MethodMetadata md,
BuildTemplateFromArgs buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) {
return new MethodHandler(target, client, retryer, wire, md, buildTemplateFromArgs, options, decoder, errorDecoder);
}
}
private final MethodMetadata metadata;
private final Target> target;
private final Client client;
private final Provider retryer;
private final Wire wire;
private final BuildTemplateFromArgs buildTemplateFromArgs;
private final Options options;
private final Decoder decoder;
private final ErrorDecoder errorDecoder;
// cannot inject wildcards in dagger
@SuppressWarnings("rawtypes")
private MethodHandler(Target target, Client client, Provider retryer, Wire wire, MethodMetadata metadata,
BuildTemplateFromArgs buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) {
this.target = checkNotNull(target, "target");
this.client = checkNotNull(client, "client for %s", target);
this.retryer = checkNotNull(retryer, "retryer for %s", target);
this.wire = checkNotNull(wire, "wire for %s", target);
this.metadata = checkNotNull(metadata, "metadata for %s", target);
this.buildTemplateFromArgs = checkNotNull(buildTemplateFromArgs, "metadata for %s", target);
this.options = checkNotNull(options, "options for %s", target);
this.decoder = checkNotNull(decoder, "decoder for %s", target);
this.errorDecoder = checkNotNull(errorDecoder, "errorDecoder for %s", target);
}
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.apply(argv);
Retryer retryer = this.retryer.get();
while (true) {
try {
return executeAndDecode(metadata.configKey(), template, metadata.returnType());
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
continue;
}
}
}
public Object executeAndDecode(String configKey, RequestTemplate template, Type returnType)
throws Throwable {
// create the request from a mutable copy of the input template.
Request request = target.apply(new RequestTemplate(template));
wire.wireRequest(target, request);
Response response = execute(request);
try {
response = wire.wireAndRebufferResponse(target, response);
if (response.status() >= 200 && response.status() < 300) {
if (returnType.equals(Response.class)) {
return response;
} else if (returnType == URI.class && response.body() == null) {
String location = firstOrNull(response.headers(), LOCATION);
if (location != null)
return URI.create(location);
} else if (returnType == void.class) {
return null;
}
return decoder.decode(configKey, response, returnType);
} else {
return errorDecoder.decode(configKey, response, returnType);
}
} catch (Throwable e) {
ensureBodyClosed(response);
if (IOException.class.isInstance(e))
throw errorReading(request, response, IOException.class.cast(e));
throw e;
}
}
private void ensureBodyClosed(Response response) {
if (response.body() != null) {
try {
response.body().close();
} catch (IOException ignored) { // NOPMD
}
}
}
private Response execute(Request request) {
try {
return client.execute(request, options);
} catch (IOException e) {
throw errorExecuting(request, e);
}
}
}