diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f11e712..aa2bd3a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### Version 8.0 +* Removes Dagger 1.x Dependency +* Removes support for parameters annotated with `javax.inject.@Named`. Use `feign.@Param` instead. + ### Version 7.1 * Introduces feign.@Param to annotate template parameters. Users must migrate from `javax.inject.@Named` to `feign.@Param` before updating to Feign 8.0. * Supports custom expansion via `@Param(value = "name", expander = CustomExpander.class)` diff --git a/README.md b/README.md index 1d91b4c7..985cc64a 100644 --- a/README.md +++ b/README.md @@ -50,35 +50,14 @@ interface Bank { Bank bank = Feign.builder().decoder(new AccountDecoder()).target(Bank.class, "https://api.examplebank.com"); ``` -For further flexibility, you can use Dagger modules directly. See the `Dagger` section for more details. - -### Request Interceptors -When you need to change all requests, regardless of their target, you'll want to configure a `RequestInterceptor`. -For example, if you are acting as an intermediary, you might want to propagate the `X-Forwarded-For` header. - -```java -static class ForwardedForInterceptor implements RequestInterceptor { - @Override public void apply(RequestTemplate template) { - template.header("X-Forwarded-For", "origin.host.com"); - } -} -... -Bank bank = Feign.builder().decoder(accountDecoder).requestInterceptor(new ForwardedForInterceptor()).target(Bank.class, "https://api.examplebank.com"); -``` - -Another common example of an interceptor would be authentication, such as using the built-in `BasicAuthRequestInterceptor`. - -```java -Bank bank = Feign.builder().decoder(accountDecoder).requestInterceptor(new BasicAuthRequestInterceptor(username, password)).target(Bank.class, "https://api.examplebank.com"); -``` - ### Multiple Interfaces Feign can produce multiple api interfaces. These are defined as `Target` (default `HardCodedTarget`), which allow for dynamic discovery and decoration of requests prior to execution. For example, the following pattern might decorate each request with the current url and auth token from the identity service. ```java -CloudDNS cloudDNS = Feign.builder().target(new CloudIdentityTarget(user, apiKey)); +Feign feign = Feign.builder().build(); +CloudDNS cloudDNS = feign.target(new CloudIdentityTarget(user, apiKey)); ``` You can find [several examples](https://github.com/Netflix/feign/tree/master/core/src/test/java/feign/examples) in the test tree. Do take time to look at them, as seeing is believing! @@ -87,7 +66,7 @@ You can find [several examples](https://github.com/Netflix/feign/tree/master/cor Feign intends to work well within Netflix and other Open Source communities. Modules are welcome to integrate with your favorite projects! ### Gson -[GsonModule](https://github.com/Netflix/feign/tree/master/gson) adds default encoders and decoders so you get get started with a JSON api. +[Gson](https://github.com/Netflix/feign/tree/master/gson) includes an encoder and decoder you can use with a JSON API. Add `GsonEncoder` and/or `GsonDecoder` to your `Feign.Builder` like so: @@ -100,7 +79,7 @@ GitHub github = Feign.builder() ``` ### Jackson -[JacksonModule](https://github.com/Netflix/feign/tree/master/jackson) adds an encoder and decoder you can use with a JSON API. +[Jackson](https://github.com/Netflix/feign/tree/master/jackson) includes an encoder and decoder you can use with a JSON API. Add `JacksonEncoder` and/or `JacksonDecoder` to your `Feign.Builder` like so: @@ -124,7 +103,7 @@ api = Feign.builder() ``` ### JAXB -[JAXBModule](https://github.com/Netflix/feign/tree/master/jaxb) allows you to encode and decode XML using JAXB. +[JAXB](https://github.com/Netflix/feign/tree/master/jaxb) includes an encoder and decoder you can use with an XML API. Add `JAXBEncoder` and/or `JAXBDecoder` to your `Feign.Builder` like so: @@ -136,7 +115,7 @@ api = Feign.builder() ``` ### JAX-RS -[JAXRSModule](https://github.com/Netflix/feign/tree/master/jaxrs) overrides annotation processing to instead use standard ones supplied by the JAX-RS specification. This is currently targeted at the 1.1 spec. +[JAXRSContract](https://github.com/Netflix/feign/tree/master/jaxrs) overrides annotation processing to instead use standard ones supplied by the JAX-RS specification. This is currently targeted at the 1.1 spec. Here's the example above re-written to use JAX-RS: ```java @@ -147,7 +126,7 @@ interface GitHub { ``` ```java GitHub github = Feign.builder() - .contract(new JAXRSModule.JAXRSContract()) + .contract(new JAXRSContract()) .target(GitHub.class, "https://api.github.com"); ``` ### OkHttp @@ -162,11 +141,12 @@ GitHub github = Feign.builder() ``` ### Ribbon -[RibbonModule](https://github.com/Netflix/feign/tree/master/ribbon) overrides URL resolution of Feign's client, adding smart routing and resiliency capabilities provided by [Ribbon](https://github.com/Netflix/ribbon). +[RibbonClient](https://github.com/Netflix/feign/tree/master/ribbon) overrides URL resolution of Feign's client, adding smart routing and resiliency capabilities provided by [Ribbon](https://github.com/Netflix/ribbon). Integration requires you to pass your ribbon client name as the host part of the url, for example `myAppProd`. ```java -MyService api = Feign.create(MyService.class, "https://myAppProd", new RibbonModule()); +MyService api = Feign.builder().client(new RibbonClient()).target(MyService.class, "https://myAppProd"); + ``` ### SLF4J @@ -206,27 +186,45 @@ GitHub github = Feign.builder() .target(GitHub.class, "https://api.github.com"); ``` -### Advanced usage and Dagger -#### Dagger -Feign can be directly wired into Dagger which keeps things at compile time and Android friendly. As opposed to exposing builders for config, Feign intends users to embed their config in Dagger. +### Advanced usage -Where possible, Feign configuration uses normal Dagger conventions. For example, `RequestInterceptor` bindings are of `Provider.Type.SET`, meaning you can have multiple interceptors. Here's an example of multiple interceptor bindings. +#### Logging +You can log the http messages going to and from the target by setting up a `Logger`. Here's the easiest way to do that: ```java -@Provides(type = SET) RequestInterceptor forwardedForInterceptor() { - return new RequestInterceptor() { - @Override public void apply(RequestTemplate template) { - template.header("X-Forwarded-For", "origin.host.com"); - } - }; -} +GitHub github = Feign.builder() + .decoder(new GsonDecoder()) + .logger(new Logger.JavaLogger().appendToFile("logs/http.log")) + .logLevel(Logger.Level.FULL) + .target(GitHub.class, "https://api.github.com"); +``` -@Provides(type = SET) RequestInterceptor userAgentInterceptor() { - return new RequestInterceptor() { - @Override public void apply(RequestTemplate template) { - template.header("User-Agent", "My Cool Client"); - } - }; +The SLF4JLogger (see above) may also be of interest. + + +#### Request Interceptors +When you need to change all requests, regardless of their target, you'll want to configure a `RequestInterceptor`. +For example, if you are acting as an intermediary, you might want to propagate the `X-Forwarded-For` header. + +```java +static class ForwardedForInterceptor implements RequestInterceptor { + @Override public void apply(RequestTemplate template) { + template.header("X-Forwarded-For", "origin.host.com"); + } } +... +Bank bank = Feign.builder() + .decoder(accountDecoder) + .requestInterceptor(new ForwardedForInterceptor()) + .target(Bank.class, "https://api.examplebank.com"); +``` + +Another common example of an interceptor would be authentication, such as using the built-in `BasicAuthRequestInterceptor`. + +```java +Bank bank = Feign.builder() + .decoder(accountDecoder) + .requestInterceptor(new BasicAuthRequestInterceptor(username, password)) + .target(Bank.class, "https://api.examplebank.com"); ``` #### Custom Parameter Expansion @@ -237,15 +235,3 @@ for example formatting dates. ```java @RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date); ``` - -#### Logging -You can log the http messages going to and from the target by setting up a `Logger`. Here's the easiest way to do that: -```java -GitHub github = Feign.builder() - .decoder(new GsonDecoder()) - .logger(new Logger.JavaLogger().appendToFile("logs/http.log")) - .logLevel(Logger.Level.FULL) - .target(GitHub.class, "https://api.github.com"); -``` - -The SLF4JModule (see above) may also be of interest. diff --git a/build.gradle b/build.gradle index d8579d81..a4ccddd8 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,5 @@ subprojects { repositories { jcenter() } - apply from: rootProject.file('dagger.gradle') group = "com.netflix.${githubProjectName}" // TEMPLATE: Set to organization of project } diff --git a/core/build.gradle b/core/build.gradle index 9edfdcb7..8497abb7 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -3,9 +3,8 @@ apply plugin: 'java' sourceCompatibility = 1.6 dependencies { - testCompile 'com.google.code.gson:gson:2.2.4' - testCompile 'com.fasterxml.jackson.core:jackson-databind:2.2.2' testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:1.7.1' testCompile 'com.squareup.okhttp:mockwebserver:2.2.0' + testCompile 'com.google.code.gson:gson:2.2.4' // for example } diff --git a/core/src/main/java/feign/Client.java b/core/src/main/java/feign/Client.java index 6ba3796f..d881177b 100644 --- a/core/src/main/java/feign/Client.java +++ b/core/src/main/java/feign/Client.java @@ -26,12 +26,10 @@ import java.util.List; import java.util.Map; import java.util.zip.GZIPOutputStream; -import javax.inject.Inject; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; -import dagger.Lazy; import feign.Request.Options; import static feign.Util.CONTENT_ENCODING; @@ -55,10 +53,11 @@ public interface Client { Response execute(Request request, Options options) throws IOException; public static class Default implements Client { - private final Lazy sslContextFactory; - private final Lazy hostnameVerifier; + private final SSLSocketFactory sslContextFactory; + private final HostnameVerifier hostnameVerifier; - @Inject public Default(Lazy sslContextFactory, Lazy hostnameVerifier) { + /** Null parameters imply platform defaults. */ + public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) { this.sslContextFactory = sslContextFactory; this.hostnameVerifier = hostnameVerifier; } @@ -72,8 +71,12 @@ public interface Client { final HttpURLConnection connection = (HttpURLConnection) new URL(request.url()).openConnection(); if (connection instanceof HttpsURLConnection) { HttpsURLConnection sslCon = (HttpsURLConnection) connection; - sslCon.setSSLSocketFactory(sslContextFactory.get()); - sslCon.setHostnameVerifier(hostnameVerifier.get()); + if (sslContextFactory != null) { + sslCon.setSSLSocketFactory(sslContextFactory); + } + if (hostnameVerifier != null) { + sslCon.setHostnameVerifier(hostnameVerifier); + } } connection.setConnectTimeout(options.connectTimeoutMillis()); connection.setReadTimeout(options.readTimeoutMillis()); diff --git a/core/src/main/java/feign/Contract.java b/core/src/main/java/feign/Contract.java index 6be6e49e..931b5d14 100644 --- a/core/src/main/java/feign/Contract.java +++ b/core/src/main/java/feign/Contract.java @@ -15,13 +15,12 @@ */ package feign; -import java.util.LinkedHashMap; -import javax.inject.Named; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.net.URI; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -168,10 +167,9 @@ public interface Contract { boolean isHttpAnnotation = false; for (Annotation annotation : annotations) { Class annotationType = annotation.annotationType(); - if (annotationType == Param.class || annotationType == Named.class) { - String name = annotationType == Param.class ? ((Param) annotation).value() : ((Named) annotation).value(); - checkState(emptyToNull(name) != null, - "%s annotation was empty on param %s.", annotationType.getSimpleName(), paramIndex); + if (annotationType == Param.class) { + String name = ((Param) annotation).value(); + checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.", paramIndex); nameParam(data, name, paramIndex); if (annotationType == Param.class) { Class expander = ((Param) annotation).expander(); diff --git a/core/src/main/java/feign/Feign.java b/core/src/main/java/feign/Feign.java index cc5bd597..75318d71 100644 --- a/core/src/main/java/feign/Feign.java +++ b/core/src/main/java/feign/Feign.java @@ -15,25 +15,16 @@ */ package feign; - -import dagger.ObjectGraph; -import dagger.Provides; import feign.Logger.NoOpLogger; +import feign.ReflectiveFeign.ParseHandlersByName; import feign.Request.Options; import feign.Target.HardCodedTarget; import feign.codec.Decoder; import feign.codec.Encoder; import feign.codec.ErrorDecoder; - -import javax.inject.Inject; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; /** * Feign's purpose is to ease development against http apis that feign @@ -55,80 +46,6 @@ public abstract class Feign { return new Builder(); } - public static T create(Class apiType, String url, Object... modules) { - return create(new HardCodedTarget(apiType, url), modules); - } - - /** - * Shortcut to {@link #newInstance(Target) create} a single {@code targeted} - * http api using {@link ReflectiveFeign reflection}. - */ - public static T create(Target target, Object... modules) { - return create(modules).newInstance(target); - } - - /** - * Returns a {@link ReflectiveFeign reflective} factory for generating - * {@link Target targeted} http apis. - */ - public static Feign create(Object... modules) { - return ObjectGraph.create(modulesForGraph(modules).toArray()).get(Feign.class); - } - - - /** - * Returns an {@link ObjectGraph Dagger ObjectGraph} that can inject a - * {@link ReflectiveFeign reflective} Feign. - */ - public static ObjectGraph createObjectGraph(Object... modules) { - return ObjectGraph.create(modulesForGraph(modules).toArray()); - } - - @SuppressWarnings("rawtypes") - // incomplete as missing Encoder/Decoder - @dagger.Module(injects = {Feign.class, Builder.class}, complete = false, includes = ReflectiveFeign.Module.class) - public static class Defaults { - @Provides Contract contract() { - return new Contract.Default(); - } - - @Provides Logger.Level logLevel() { - return Logger.Level.NONE; - } - - @Provides Logger noOp() { - return new NoOpLogger(); - } - - @Provides Retryer retryer() { - return new Retryer.Default(); - } - - @Provides ErrorDecoder errorDecoder() { - return new ErrorDecoder.Default(); - } - - @Provides Options options() { - return new Options(); - } - - @Provides SSLSocketFactory sslSocketFactory() { - return SSLSocketFactory.class.cast(SSLSocketFactory.getDefault()); - } - - @Provides HostnameVerifier hostnameVerifier() { - return HttpsURLConnection.getDefaultHostnameVerifier(); - } - - @Provides Client httpClient(Client.Default client) { - return client; - } - - @Provides InvocationHandlerFactory invocationHandlerFactory() { - return new InvocationHandlerFactory.Default(); - } - } - /** *
* Configuration keys are formatted as unresolved modulesForGraph(Object... modules) { - List modulesForGraph = new ArrayList(2); - modulesForGraph.add(new Defaults()); - if (modules != null) - for (Object module : modules) - modulesForGraph.add(module); - return modulesForGraph; - } - - @dagger.Module(injects = Feign.class, includes = ReflectiveFeign.Module.class) public static class Builder { - private final Set requestInterceptors = new LinkedHashSet(); - @Inject Logger.Level logLevel; - @Inject Contract contract; - @Inject Client client; - @Inject Retryer retryer; - @Inject Logger logger; - Encoder encoder = new Encoder.Default(); - Decoder decoder = new Decoder.Default(); - @Inject ErrorDecoder errorDecoder; - @Inject Options options; - @Inject InvocationHandlerFactory invocationHandlerFactory; - - Builder() { - ObjectGraph.create(new Defaults()).inject(this); - } + private final List requestInterceptors = new ArrayList(); + private Logger.Level logLevel = Logger.Level.NONE; + private Contract contract = new Contract.Default(); + private Client client = new Client.Default(null, null); + private Retryer retryer = new Retryer.Default(); + private Logger logger = new NoOpLogger(); + private Encoder encoder = new Encoder.Default(); + private Decoder decoder = new Decoder.Default(); + private ErrorDecoder errorDecoder = new ErrorDecoder.Default(); + private Options options = new Options(); + private InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default(); public Builder logLevel(Logger.Level logLevel) { this.logLevel = logLevel; @@ -262,51 +165,15 @@ public abstract class Feign { } public T target(Target target) { - return ObjectGraph.create(this).get(Feign.class).newInstance(target); - } - - @Provides Logger.Level logLevel() { - return logLevel; - } - - @Provides Contract contract() { - return contract; - } - - @Provides Client client() { - return client; - } - - @Provides Retryer retryer() { - return retryer; - } - - @Provides Logger logger() { - return logger; - } - - @Provides Encoder encoder() { - return encoder; - } - - @Provides Decoder decoder() { - return decoder; - } - - @Provides ErrorDecoder errorDecoder() { - return errorDecoder; - } - - @Provides Options options() { - return options; - } - - @Provides(type = Provides.Type.SET_VALUES) Set requestInterceptors() { - return requestInterceptors; + return build().newInstance(target); } - @Provides InvocationHandlerFactory invocationHandlerFactory() { - return invocationHandlerFactory; + public Feign build() { + SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = + new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel); + ParseHandlersByName handlersByName = new ParseHandlersByName( contract, options, encoder, decoder, + errorDecoder, synchronousMethodHandlerFactory); + return new ReflectiveFeign(handlersByName, invocationHandlerFactory); } } } diff --git a/core/src/main/java/feign/InvocationHandlerFactory.java b/core/src/main/java/feign/InvocationHandlerFactory.java index cf808049..7dabf77d 100644 --- a/core/src/main/java/feign/InvocationHandlerFactory.java +++ b/core/src/main/java/feign/InvocationHandlerFactory.java @@ -21,6 +21,7 @@ import java.util.Map; /** Controls reflective method dispatch. */ public interface InvocationHandlerFactory { + /** Like {@link InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])}, except for a single method. */ interface MethodHandler { Object invoke(Object[] argv) throws Throwable; diff --git a/core/src/main/java/feign/ReflectiveFeign.java b/core/src/main/java/feign/ReflectiveFeign.java index 4b19c847..541bd5f6 100644 --- a/core/src/main/java/feign/ReflectiveFeign.java +++ b/core/src/main/java/feign/ReflectiveFeign.java @@ -15,7 +15,6 @@ */ package feign; -import dagger.Provides; import feign.InvocationHandlerFactory.MethodHandler; import feign.Param.Expander; import feign.Request.Options; @@ -24,28 +23,24 @@ import feign.codec.EncodeException; import feign.codec.Encoder; import feign.codec.ErrorDecoder; -import javax.inject.Inject; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import static feign.Util.checkArgument; import static feign.Util.checkNotNull; -@SuppressWarnings("rawtypes") public class ReflectiveFeign extends Feign { private final ParseHandlersByName targetToHandlersByName; private final InvocationHandlerFactory factory; - @Inject ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory) { + ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory) { this.targetToHandlersByName = targetToHandlersByName; this.factory = factory; } @@ -109,17 +104,6 @@ public class ReflectiveFeign extends Feign { } } - @dagger.Module(complete = false, injects = {Feign.class, SynchronousMethodHandler.Factory.class}, library = true) - public static class Module { - @Provides(type = Provides.Type.SET_VALUES) Set noRequestInterceptors() { - return Collections.emptySet(); - } - - @Provides Feign provideFeign(ReflectiveFeign in) { - return in; - } - } - static final class ParseHandlersByName { private final Contract contract; private final Options options; @@ -128,9 +112,8 @@ public class ReflectiveFeign extends Feign { private final ErrorDecoder errorDecoder; private final SynchronousMethodHandler.Factory factory; - @SuppressWarnings("unchecked") - @Inject ParseHandlersByName(Contract contract, Options options, Encoder encoder, Decoder decoder, - ErrorDecoder errorDecoder, SynchronousMethodHandler.Factory factory) { + ParseHandlersByName(Contract contract, Options options, Encoder encoder, Decoder decoder, + ErrorDecoder errorDecoder, SynchronousMethodHandler.Factory factory) { this.contract = contract; this.options = options; this.factory = factory; diff --git a/core/src/main/java/feign/RequestInterceptor.java b/core/src/main/java/feign/RequestInterceptor.java index 39b79c60..0c4ad016 100644 --- a/core/src/main/java/feign/RequestInterceptor.java +++ b/core/src/main/java/feign/RequestInterceptor.java @@ -33,19 +33,7 @@ package feign; *
*
Configuration
*
- * {@code RequestInterceptors} are configured via Dagger - * {@link dagger.Provides.Type#SET set} or - * {@link dagger.Provides.Type#SET_VALUES set values} - * {@link dagger.Provides provider} methods. - *
- *
- * For example: - *
- *
- * {@literal @}Provides(Type = SET) RequestInterceptor addTimestamp(TimestampInterceptor in) {
- * return in;
- * }
- * 
+ * {@code RequestInterceptors} are configured via {@link Feign.Builder#requestInterceptors}. *
*
Implementation notes
*
diff --git a/core/src/main/java/feign/SynchronousMethodHandler.java b/core/src/main/java/feign/SynchronousMethodHandler.java index 83c102da..764636a5 100644 --- a/core/src/main/java/feign/SynchronousMethodHandler.java +++ b/core/src/main/java/feign/SynchronousMethodHandler.java @@ -20,11 +20,8 @@ import feign.Request.Options; import feign.codec.DecodeException; import feign.codec.Decoder; import feign.codec.ErrorDecoder; - -import javax.inject.Inject; -import javax.inject.Provider; import java.io.IOException; -import java.util.Set; +import java.util.List; import java.util.concurrent.TimeUnit; import static feign.FeignException.errorExecuting; @@ -37,13 +34,13 @@ final class SynchronousMethodHandler implements MethodHandler { static class Factory { private final Client client; - private final Provider retryer; - private final Set requestInterceptors; + private final Retryer retryer; + private final List requestInterceptors; private final Logger logger; - private final Provider logLevel; + private final Logger.Level logLevel; - @Inject Factory(Client client, Provider retryer, Set requestInterceptors, - Logger logger, Provider logLevel) { + Factory(Client client, Retryer retryer, List requestInterceptors, + Logger logger, Logger.Level logLevel) { this.client = checkNotNull(client, "client"); this.retryer = checkNotNull(retryer, "retryer"); this.requestInterceptors = checkNotNull(requestInterceptors, "requestInterceptors"); @@ -61,18 +58,18 @@ final class SynchronousMethodHandler implements MethodHandler { private final MethodMetadata metadata; private final Target target; private final Client client; - private final Provider retryer; - private final Set requestInterceptors; + private final Retryer retryer; + private final List requestInterceptors; private final Logger logger; - private final Provider logLevel; + private final Logger.Level logLevel; private final RequestTemplate.Factory buildTemplateFromArgs; private final Options options; private final Decoder decoder; private final ErrorDecoder errorDecoder; - private SynchronousMethodHandler(Target target, Client client, Provider retryer, - Set requestInterceptors, Logger logger, - Provider logLevel, MethodMetadata metadata, + private SynchronousMethodHandler(Target target, Client client, Retryer retryer, + List requestInterceptors, Logger logger, + Logger.Level logLevel, MethodMetadata metadata, RequestTemplate.Factory buildTemplateFromArgs, Options options, Decoder decoder, ErrorDecoder errorDecoder) { this.target = checkNotNull(target, "target"); @@ -90,14 +87,14 @@ final class SynchronousMethodHandler implements MethodHandler { @Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); - Retryer retryer = this.retryer.get(); + Retryer retryer = this.retryer; while (true) { try { return executeAndDecode(template); } catch (RetryableException e) { retryer.continueOrPropagate(e); - if (logLevel.get() != Logger.Level.NONE) { - logger.logRetry(metadata.configKey(), logLevel.get()); + if (logLevel != Logger.Level.NONE) { + logger.logRetry(metadata.configKey(), logLevel); } continue; } @@ -107,8 +104,8 @@ final class SynchronousMethodHandler implements MethodHandler { Object executeAndDecode(RequestTemplate template) throws Throwable { Request request = targetRequest(template); - if (logLevel.get() != Logger.Level.NONE) { - logger.logRequest(metadata.configKey(), logLevel.get(), request); + if (logLevel != Logger.Level.NONE) { + logger.logRequest(metadata.configKey(), logLevel, request); } Response response; @@ -116,16 +113,16 @@ final class SynchronousMethodHandler implements MethodHandler { try { response = client.execute(request, options); } catch (IOException e) { - if (logLevel.get() != Logger.Level.NONE) { - logger.logIOException(metadata.configKey(), logLevel.get(), e, elapsedTime(start)); + if (logLevel != Logger.Level.NONE) { + logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); } throw errorExecuting(request, e); } long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); try { - if (logLevel.get() != Logger.Level.NONE) { - response = logger.logAndRebufferResponse(metadata.configKey(), logLevel.get(), response, elapsedTime); + if (logLevel != Logger.Level.NONE) { + response = logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime); } if (response.status() >= 200 && response.status() < 300) { if (Response.class == metadata.returnType()) { @@ -144,8 +141,8 @@ final class SynchronousMethodHandler implements MethodHandler { throw errorDecoder.decode(metadata.configKey(), response); } } catch (IOException e) { - if (logLevel.get() != Logger.Level.NONE) { - logger.logIOException(metadata.configKey(), logLevel.get(), e, elapsedTime); + if (logLevel != Logger.Level.NONE) { + logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime); } throw errorReading(request, response, e); } finally { diff --git a/core/src/test/java/feign/DefaultContractTest.java b/core/src/test/java/feign/DefaultContractTest.java index 1906e75b..739d9884 100644 --- a/core/src/test/java/feign/DefaultContractTest.java +++ b/core/src/test/java/feign/DefaultContractTest.java @@ -19,7 +19,6 @@ import com.google.gson.reflect.TypeToken; import java.net.URI; import java.util.Date; import java.util.List; -import javax.inject.Named; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -264,70 +263,4 @@ public class DefaultContractTest { assertThat(md.indexToExpanderClass()) .containsExactly(entry(0, DateToMillis.class)); } - - // TODO: remove all of below in 8.x - - interface WithPathAndQueryParamsAnnotatedWithNamed { - @RequestLine("GET /domains/{domainId}/records?name={name}&type={type}") - Response recordsByNameAndType(@Named("domainId") int id, @Named("name") String nameFilter, - @Named("type") String typeFilter); - } - - @Test public void pathAndQueryParamsAnnotatedWithNamed() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(WithPathAndQueryParamsAnnotatedWithNamed.class.getDeclaredMethod - ("recordsByNameAndType", int.class, String.class, String.class)); - - assertThat(md.template()) - .hasQueries(entry("name", asList("{name}")), entry("type", asList("{type}"))); - - assertThat(md.indexToName()).containsExactly( - entry(0, asList("domainId")), - entry(1, asList("name")), - entry(2, asList("type")) - ); - } - - interface FormParamsAnnotatedWithNamed { - @RequestLine("POST /") - @Body("%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D") - void login( - @Named("customer_name") String customer, - @Named("user_name") String user, @Named("password") String password); - } - - @Test public void bodyWithTemplateAnnotatedWithNamed() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(FormParamsAnnotatedWithNamed.class.getDeclaredMethod("login", String.class, - String.class, String.class)); - - assertThat(md.template()) - .hasBodyTemplate("%7B\"customer_name\": \"{customer_name}\", \"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D"); - } - - @Test public void formParamsAnnotatedWithNamedParseIntoIndexToName() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(FormParamsAnnotatedWithNamed.class.getDeclaredMethod("login", String.class, - String.class, String.class)); - - assertThat(md.formParams()) - .containsExactly("customer_name", "user_name", "password"); - - assertThat(md.indexToName()).containsExactly( - entry(0, asList("customer_name")), - entry(1, asList("user_name")), - entry(2, asList("password")) - ); - } - - interface HeaderParamsAnnotatedWithNamed { - @RequestLine("POST /") - @Headers("Auth-Token: {Auth-Token}") void logout(@Named("Auth-Token") String token); - } - - @Test public void headerParamsAnnotatedWithNamedParseIntoIndexToName() throws Exception { - MethodMetadata md = contract.parseAndValidatateMetadata(HeaderParamsAnnotatedWithNamed.class.getDeclaredMethod("logout", String.class)); - - assertThat(md.template()).hasHeaders(entry("Auth-Token", asList("{Auth-Token}"))); - - assertThat(md.indexToName()) - .containsExactly(entry(0, asList("Auth-Token"))); - } } diff --git a/core/src/test/java/feign/FeignTest.java b/core/src/test/java/feign/FeignTest.java index fbf0f20e..939190a5 100644 --- a/core/src/test/java/feign/FeignTest.java +++ b/core/src/test/java/feign/FeignTest.java @@ -19,8 +19,6 @@ import com.google.gson.Gson; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.SocketPolicy; import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; -import dagger.Module; -import dagger.Provides; import feign.Target.HardCodedTarget; import feign.codec.Decoder; import feign.codec.Encoder; @@ -33,19 +31,15 @@ import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; -import javax.inject.Singleton; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static dagger.Provides.Type.SET; import static feign.Util.UTF_8; import static feign.assertj.MockWebServerAssertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -// unbound wildcards are not currently injectable in dagger. -@SuppressWarnings("rawtypes") public class FeignTest { @Rule public final ExpectedException thrown = ExpectedException.none(); @Rule public final MockWebServerRule server = new MockWebServerRule(); @@ -78,32 +72,12 @@ public class FeignTest { return String.valueOf(((Date) value).getTime()); } } - - @dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) - static class Module { - @Provides Decoder defaultDecoder() { - return new Decoder.Default(); - } - - @Provides Encoder defaultEncoder() { - return new Encoder() { - @Override public void encode(Object object, RequestTemplate template) { - if (object instanceof Map) { - template.body(new Gson().toJson(object)); - } else { - template.body(object.toString()); - } - } - }; - } - } } @Test public void iterableQueryParams() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); - TestInterface api = - Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module()); + TestInterface api = new TestInterfaceBuilder().target("http://localhost:" + server.getPort()); api.queryParams("user", Arrays.asList("apple", "pear")); @@ -119,12 +93,10 @@ public class FeignTest { @RequestLine("POST /") void binaryRequestBody(byte[] contents); } - @Test - public void postTemplateParamsResolve() throws IOException, InterruptedException { + @Test public void postTemplateParamsResolve() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); - - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new TestInterface.Module()); + + TestInterface api = new TestInterfaceBuilder().target("http://localhost:" + server.getPort()); api.login("netflix", "denominator", "password"); @@ -132,24 +104,20 @@ public class FeignTest { .hasBody("{\"customer_name\": \"netflix\", \"user_name\": \"denominator\", \"password\": \"password\"}"); } - @Test - public void responseCoercesToStringBody() throws IOException, InterruptedException { + @Test public void responseCoercesToStringBody() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new TestInterface.Module()); + TestInterface api = new TestInterfaceBuilder().target("http://localhost:" + server.getPort()); Response response = api.response(); assertTrue(response.body().isRepeatable()); assertEquals("foo", response.body().toString()); } - @Test - public void postFormParams() throws IOException, InterruptedException { + @Test public void postFormParams() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new TestInterface.Module()); + TestInterface api = new TestInterfaceBuilder().target("http://localhost:" + server.getPort()); api.form("netflix", "denominator", "password"); @@ -157,12 +125,10 @@ public class FeignTest { .hasBody("{\"customer_name\":\"netflix\",\"user_name\":\"denominator\",\"password\":\"password\"}"); } - @Test - public void postBodyParam() throws IOException, InterruptedException { + @Test public void postBodyParam() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new TestInterface.Module()); + TestInterface api = new TestInterfaceBuilder().target("http://localhost:" + server.getPort()); api.body(Arrays.asList("netflix", "denominator", "password")); @@ -171,12 +137,10 @@ public class FeignTest { .hasBody("[netflix, denominator, password]"); } - @Test - public void postGZIPEncodedBodyParam() throws IOException, InterruptedException { + @Test public void postGZIPEncodedBodyParam() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new TestInterface.Module()); + TestInterface api = new TestInterfaceBuilder().target("http://localhost:" + server.getPort()); api.gzipBody(Arrays.asList("netflix", "denominator", "password")); @@ -185,23 +149,18 @@ public class FeignTest { .hasGzippedBody("[netflix, denominator, password]".getBytes(UTF_8)); } - @Module(library = true) static class ForwardedForInterceptor implements RequestInterceptor { - @Provides(type = SET) RequestInterceptor provideThis() { - return this; - } - @Override public void apply(RequestTemplate template) { template.header("X-Forwarded-For", "origin.host.com"); } } - @Test - public void singleInterceptor() throws IOException, InterruptedException { + @Test public void singleInterceptor() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); - - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new TestInterface.Module(), new ForwardedForInterceptor()); + + TestInterface api = new TestInterfaceBuilder() + .requestInterceptor(new ForwardedForInterceptor()) + .target("http://localhost:" + server.getPort()); api.post(); @@ -209,35 +168,29 @@ public class FeignTest { .hasHeaders("X-Forwarded-For: origin.host.com"); } - @Module(library = true) static class UserAgentInterceptor implements RequestInterceptor { - @Provides(type = SET) RequestInterceptor provideThis() { - return this; - } - @Override public void apply(RequestTemplate template) { template.header("User-Agent", "Feign"); } } - @Test - public void multipleInterceptor() throws IOException, InterruptedException { + @Test public void multipleInterceptor() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("foo")); - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new TestInterface.Module(), new ForwardedForInterceptor(), new UserAgentInterceptor()); + TestInterface api = new TestInterfaceBuilder() + .requestInterceptor(new ForwardedForInterceptor()) + .requestInterceptor(new UserAgentInterceptor()) + .target("http://localhost:" + server.getPort()); api.post(); - assertThat(server.takeRequest()) - .hasHeaders("X-Forwarded-For: origin.host.com", "User-Agent: Feign"); + assertThat(server.takeRequest()).hasHeaders("X-Forwarded-For: origin.host.com", "User-Agent: Feign"); } @Test public void customExpander() throws Exception { server.enqueue(new MockResponse()); - TestInterface api = - Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new TestInterface.Module()); + TestInterface api = new TestInterfaceBuilder().target("http://localhost:" + server.getPort()); api.expand(new Date(1234l)); @@ -251,30 +204,21 @@ public class FeignTest { Feign.configKey(TestInterface.class.getDeclaredMethod("uriParam", String.class, URI.class, String.class))); } - @dagger.Module(overrides = true, library = true, includes = TestInterface.Module.class) - static class IllegalArgumentExceptionOn404 { - @Provides @Singleton ErrorDecoder errorDecoder() { - return new ErrorDecoder.Default() { - - @Override - public Exception decode(String methodKey, Response response) { - if (response.status() == 404) - return new IllegalArgumentException("zone not found"); - return super.decode(methodKey, response); - } - - }; + static class IllegalArgumentExceptionOn404 extends ErrorDecoder.Default { + @Override public Exception decode(String methodKey, Response response) { + if (response.status() == 404) return new IllegalArgumentException("zone not found"); + return super.decode(methodKey, response); } } - @Test - public void canOverrideErrorDecoder() throws IOException, InterruptedException { + @Test public void canOverrideErrorDecoder() throws IOException, InterruptedException { server.enqueue(new MockResponse().setResponseCode(404).setBody("foo")); thrown.expect(IllegalArgumentException.class); thrown.expectMessage("zone not found"); - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new IllegalArgumentExceptionOn404()); + TestInterface api = new TestInterfaceBuilder() + .errorDecoder(new IllegalArgumentExceptionOn404()) + .target("http://localhost:" + server.getPort()); api.post(); } @@ -283,83 +227,58 @@ public class FeignTest { server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)); server.enqueue(new MockResponse().setBody("success!")); - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new TestInterface.Module()); + TestInterface api = new TestInterfaceBuilder().target("http://localhost:" + server.getPort()); api.post(); assertEquals(2, server.getRequestCount()); } - @dagger.Module(overrides = true, library = true, includes = TestInterface.Module.class) - static class DecodeFail { - @Provides Decoder decoder() { - return new Decoder() { - @Override - public Object decode(Response response, Type type) { - return "fail"; - } - }; - } - } - @Test public void overrideTypeSpecificDecoder() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("success!")); - - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new DecodeFail()); + + TestInterface api = new TestInterfaceBuilder() + .decoder(new Decoder() { + @Override public Object decode(Response response, Type type) { + return "fail"; + } + }).target("http://localhost:" + server.getPort()); assertEquals(api.post(), "fail"); } - @dagger.Module(overrides = true, library = true, includes = TestInterface.Module.class) - static class RetryableExceptionOnRetry { - @Provides Decoder decoder() { - return new StringDecoder() { - @Override - public Object decode(Response response, Type type) throws IOException, FeignException { - String string = super.decode(response, type).toString(); - if ("retry!".equals(string)) - throw new RetryableException(string, null); - return string; - } - }; - } - } - /** * when you must parse a 2xx status to determine if the operation succeeded or not. */ - public void retryableExceptionInDecoder() throws IOException, InterruptedException { + @Test public void retryableExceptionInDecoder() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("retry!")); server.enqueue(new MockResponse().setBody("success!")); - - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), - new RetryableExceptionOnRetry()); + + TestInterface api = new TestInterfaceBuilder() + .decoder(new StringDecoder() { + @Override public Object decode(Response response, Type type) throws IOException { + String string = super.decode(response, type).toString(); + if ("retry!".equals(string)) throw new RetryableException(string, null); + return string; + } + }).target("http://localhost:" + server.getPort()); assertEquals(api.post(), "success!"); assertEquals(2, server.getRequestCount()); } - @dagger.Module(overrides = true, library = true, includes = TestInterface.Module.class) - static class IOEOnDecode { - @Provides Decoder decoder() { - return new Decoder() { - @Override - public Object decode(Response response, Type type) throws IOException { - throw new IOException("error reading response"); - } - }; - } - } - @Test - public void doesntRetryAfterResponseIsSent() throws IOException, InterruptedException { + @Test public void doesntRetryAfterResponseIsSent() throws IOException, InterruptedException { server.enqueue(new MockResponse().setBody("success!")); thrown.expect(FeignException.class); thrown.expectMessage("error reading response POST http://"); - TestInterface api = Feign.create(TestInterface.class, "http://localhost:" + server.getPort(), new IOEOnDecode()); + TestInterface api = new TestInterfaceBuilder() + .decoder(new Decoder() { + @Override public Object decode(Response response, Type type) throws IOException { + throw new IOException("error reading response"); + } + }).target("http://localhost:" + server.getPort()); try { api.post(); @@ -424,4 +343,42 @@ public class FeignTest { assertThat(server.takeRequest()) .hasBody(expectedRequest); } + + static final class TestInterfaceBuilder { + private final Feign.Builder delegate = new Feign.Builder() + .decoder(new Decoder.Default()) + .encoder(new Encoder() { + @Override public void encode(Object object, RequestTemplate template) { + if (object instanceof Map) { + template.body(new Gson().toJson(object)); + } else { + template.body(object.toString()); + } + } + }); + + TestInterfaceBuilder requestInterceptor(RequestInterceptor requestInterceptor) { + delegate.requestInterceptor(requestInterceptor); + return this; + } + + TestInterfaceBuilder client(Client client) { + delegate.client(client); + return this; + } + + TestInterfaceBuilder decoder(Decoder decoder) { + delegate.decoder(decoder); + return this; + } + + TestInterfaceBuilder errorDecoder(ErrorDecoder errorDecoder) { + delegate.errorDecoder(errorDecoder); + return this; + } + + TestInterface target(String url) { + return delegate.target(TestInterface.class, url); + } + } } diff --git a/core/src/test/java/feign/LoggerTest.java b/core/src/test/java/feign/LoggerTest.java index 8748fc27..69aff9aa 100644 --- a/core/src/test/java/feign/LoggerTest.java +++ b/core/src/test/java/feign/LoggerTest.java @@ -192,7 +192,6 @@ public class LoggerTest { } @Test public void unknownHostEmits() throws IOException, InterruptedException { - SendsStuff api = Feign.builder() .logger(logger) .logLevel(logLevel) @@ -232,7 +231,6 @@ public class LoggerTest { } @Test public void retryEmits() throws IOException, InterruptedException { - thrown.expect(FeignException.class); SendsStuff api = Feign.builder() diff --git a/core/src/test/java/feign/client/DefaultClientTest.java b/core/src/test/java/feign/client/DefaultClientTest.java index c100375c..ba59b0a0 100644 --- a/core/src/test/java/feign/client/DefaultClientTest.java +++ b/core/src/test/java/feign/client/DefaultClientTest.java @@ -18,7 +18,6 @@ package feign.client; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.SocketPolicy; import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; -import dagger.Lazy; import feign.Client; import feign.Feign; import feign.FeignException; @@ -29,9 +28,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.ProtocolException; import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocketFactory; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -98,15 +95,7 @@ public class DefaultClientTest { api.patch(); } - Client trustSSLSockets = new Client.Default(new Lazy() { - @Override public SSLSocketFactory get() { - return TrustingSSLSocketFactory.get(); - } - }, new Lazy() { - @Override public HostnameVerifier get() { - return HttpsURLConnection.getDefaultHostnameVerifier(); - } - }); + Client trustSSLSockets = new Client.Default(TrustingSSLSocketFactory.get(), null); @Test public void canOverrideSSLSocketFactory() throws IOException, InterruptedException { server.get().useHttps(TrustingSSLSocketFactory.get("localhost"), false); @@ -119,18 +108,9 @@ public class DefaultClientTest { api.post("foo"); } - Client disableHostnameVerification = new Client.Default(new Lazy() { - @Override public SSLSocketFactory get() { - return TrustingSSLSocketFactory.get(); - } - }, new Lazy() { - @Override public HostnameVerifier get() { - return new HostnameVerifier() { - @Override - public boolean verify(String s, SSLSession sslSession) { - return true; - } - }; + Client disableHostnameVerification = new Client.Default(TrustingSSLSocketFactory.get(), new HostnameVerifier() { + @Override public boolean verify(String s, SSLSession sslSession) { + return true; } }); diff --git a/core/src/test/java/feign/client/TrustingSSLSocketFactory.java b/core/src/test/java/feign/client/TrustingSSLSocketFactory.java index adbddcb6..b67225bb 100644 --- a/core/src/test/java/feign/client/TrustingSSLSocketFactory.java +++ b/core/src/test/java/feign/client/TrustingSSLSocketFactory.java @@ -36,8 +36,6 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; -import static com.google.common.base.Throwables.propagate; - /** * Used for ssl tests to simplify setup. */ @@ -69,7 +67,7 @@ final class TrustingSSLSocketFactory extends SSLSocketFactory implements X509Tru sc.init(new KeyManager[]{this}, new TrustManager[]{this}, new SecureRandom()); this.delegate = sc.getSocketFactory(); } catch (Exception e) { - throw propagate(e); + throw new RuntimeException(e); } this.serverAlias = serverAlias; if (serverAlias.isEmpty()) { @@ -82,7 +80,7 @@ final class TrustingSSLSocketFactory extends SSLSocketFactory implements X509Tru Certificate[] rawChain = keyStore.getCertificateChain(serverAlias); this.certificateChain = Arrays.copyOf(rawChain, rawChain.length, X509Certificate[].class); } catch (Exception e) { - throw propagate(e); + throw new RuntimeException(e); } } } diff --git a/core/src/test/java/feign/examples/GitHubExample.java b/core/src/test/java/feign/examples/GitHubExample.java index 7b0d1910..71d7b04f 100644 --- a/core/src/test/java/feign/examples/GitHubExample.java +++ b/core/src/test/java/feign/examples/GitHubExample.java @@ -48,9 +48,9 @@ public class GitHubExample { public static void main(String... args) { GitHub github = Feign.builder() + .decoder(new GsonDecoder()) .logger(new Logger.ErrorLogger()) .logLevel(Logger.Level.BASIC) - .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); System.out.println("Let's fetch and print a list of the contributors to this library."); diff --git a/dagger.gradle b/dagger.gradle deleted file mode 100644 index 3217a6e3..00000000 --- a/dagger.gradle +++ /dev/null @@ -1,178 +0,0 @@ -// Manages classpath and IDE annotation processing config for dagger. -// -// setup: -// Add the following to your root build.gradle -// -// apply plugin: 'idea' -// subprojects { -// apply from: rootProject.file('dagger.gradle') -// } -// -// do not use gradle integration of the ide. instead generate and import like so: -// -// ./gradlew clean cleanEclipse cleanIdea eclipse idea -// -// known limitations: -// as output folders include generated classes, you may need to run clean a few times. -// incompatible with android plugin as it applies the java plugin -// unnecessarily applies both eclipse and idea plugins even if you don't use them -// suffers from the normal non-IDE eclipse integration where nested projects don't import properly. -// change your structure to flattened to avoid this. -// -// deprecated by: https://github.com/Netflix/gradle-template/issues/8 -// -// original design: cfieber -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' - -if (!project.hasProperty('daggerVersion')) { - ext { - daggerVersion = "1.2.2" - } -} - -configurations { - daggerCompiler { - visible false - } -} - -configurations.all { - resolutionStrategy { - eachDependency { DependencyResolveDetails details -> - if (details.requested.group == 'com.squareup.dagger') { - details.useVersion daggerVersion - } - } - } -} - -def annotationGeneratedSources = file('.generated/src') -def annotationGeneratedTestSources = file('.generated/test') - -task prepareAnnotationGeneratedSourceDirs(overwrite: true) << { - annotationGeneratedSources.mkdirs() - annotationGeneratedTestSources.mkdirs() - sourceSets*.java.srcDirs*.each { it.mkdirs() } - sourceSets*.resources.srcDirs*.each { it.mkdirs() } -} - -sourceSets { - main { - java { - compileClasspath += configurations.daggerCompiler - } - } - test { - java { - compileClasspath += configurations.daggerCompiler - } - } -} - -dependencies { - compile "com.squareup.dagger:dagger:${project.daggerVersion}" - daggerCompiler "com.squareup.dagger:dagger-compiler:${project.daggerVersion}" -} - -rootProject.idea.project.ipr.withXml { projectXml -> - projectXml.asNode().component.find { it.@name == 'CompilerConfiguration' }.annotationProcessing[0].replaceNode { - annotationProcessing { - profile(default: true, name: 'Default', enabled: true) { - sourceOutputDir name: relativePath(annotationGeneratedSources) - sourceTestOutputDir name: relativePath(annotationGeneratedTestSources) - outputRelativeToContentRoot value: true - processorPath useClasspath: true - } - } - } -} - -tasks.ideaModule.dependsOn(prepareAnnotationGeneratedSourceDirs) - -idea.module { - scopes.PROVIDED.plus += [project.configurations.daggerCompiler] - iml.withXml { xml-> - def moduleSource = xml.asNode().component.find { it.@name = 'NewModuleRootManager' }.content[0] - moduleSource.appendNode('sourceFolder', [url: "file://\$MODULE_DIR\$/${relativePath(annotationGeneratedSources)}", isTestSource: false]) - moduleSource.appendNode('sourceFolder', [url: "file://\$MODULE_DIR\$/${relativePath(annotationGeneratedTestSources)}", isTestSource: true]) - } -} - -tasks.eclipseClasspath.dependsOn(prepareAnnotationGeneratedSourceDirs) - -eclipse.classpath { - plusConfigurations += [project.configurations.daggerCompiler] -} - -tasks.eclipseClasspath { - doLast { - eclipse.classpath.file.withXml { - it.asNode().children()[0] + { - classpathentry(kind: 'src', path: relativePath(annotationGeneratedSources)) { - attributes { - attribute name: 'optional', value: true - } - } - } - } - } -} - -// http://forums.gradle.org/gradle/topics/eclipse_generated_files_should_be_put_in_the_same_place_as_the_gradle_generated_files -Map pathMappings = [:]; -SourceSetContainer sourceSets = project.sourceSets; -sourceSets.each { SourceSet sourceSet -> - String relativeJavaOutputDirectory = project.relativePath(sourceSet.output.classesDir); - String relativeResourceOutputDirectory = project.relativePath(sourceSet.output.resourcesDir); - sourceSet.java.getSrcDirTrees().each { DirectoryTree sourceDirectory -> - String relativeSrcPath = project.relativePath(sourceDirectory.dir.absolutePath); - - pathMappings[relativeSrcPath] = relativeJavaOutputDirectory; - } - sourceSet.resources.getSrcDirTrees().each { DirectoryTree resourceDirectory -> - String relativeResourcePath = project.relativePath(resourceDirectory.dir.absolutePath); - - pathMappings[relativeResourcePath] = relativeResourceOutputDirectory; - } -} - -project.eclipse.classpath.file { - whenMerged { classpath -> - classpath.entries.findAll { entry -> - return entry.kind == 'src'; - }.each { entry -> - if(pathMappings.containsKey(entry.path)) { - entry.output = pathMappings[entry.path]; - } - } - } -} - -eclipse.jdt.file.withProperties { props -> - props.setProperty('org.eclipse.jdt.core.compiler.processAnnotations', 'enabled') -} - -tasks.eclipseJdt { - doFirst { - def aptPrefs = file('.settings/org.eclipse.jdt.apt.core.prefs') - aptPrefs.parentFile.mkdirs() - - aptPrefs.text = """\ - eclipse.preferences.version=1 - org.eclipse.jdt.apt.aptEnabled=true - org.eclipse.jdt.apt.genSrcDir=${relativePath(annotationGeneratedSources)} - org.eclipse.jdt.apt.reconcileEnabled=true - """.stripIndent() - - file('.factorypath').withWriter { - new groovy.xml.MarkupBuilder(it).'factorypath' { - project.configurations.daggerCompiler.files.each { dep -> - 'factorypathentry' kind: 'EXTJAR', id: dep.absolutePath, enabled: true, runInBatchMode: false - } - } - } - } -} - diff --git a/gson/README.md b/gson/README.md index bc6a4768..37c05e0c 100644 --- a/gson/README.md +++ b/gson/README.md @@ -11,9 +11,3 @@ GitHub github = Feign.builder() .decoder(new GsonDecoder()) .target(GitHub.class, "https://api.github.com"); ``` - -Or add them to your Dagger object graph like so: - -```java -GitHub github = Feign.create(GitHub.class, "https://api.github.com", new GsonModule()); -``` diff --git a/gson/src/main/java/feign/gson/GsonCodec.java b/gson/src/main/java/feign/gson/GsonCodec.java deleted file mode 100644 index b6ef12be..00000000 --- a/gson/src/main/java/feign/gson/GsonCodec.java +++ /dev/null @@ -1,37 +0,0 @@ -package feign.gson; - -import com.google.gson.Gson; -import feign.RequestTemplate; -import feign.Response; -import feign.codec.Decoder; -import feign.codec.Encoder; - -import javax.inject.Inject; -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * @deprecated use {@link GsonEncoder} and {@link GsonDecoder} instead - */ -@Deprecated -public class GsonCodec implements Encoder, Decoder { - private final GsonEncoder encoder; - private final GsonDecoder decoder; - - public GsonCodec() { - this(new Gson()); - } - - @Inject public GsonCodec(Gson gson) { - this.encoder = new GsonEncoder(gson); - this.decoder = new GsonDecoder(gson); - } - - @Override public void encode(Object object, RequestTemplate template) { - encoder.encode(object, template); - } - - @Override public Object decode(Response response, Type type) throws IOException { - return decoder.decode(response, type); - } -} diff --git a/gson/src/main/java/feign/gson/GsonDecoder.java b/gson/src/main/java/feign/gson/GsonDecoder.java index 66df54ea..0a01cc95 100644 --- a/gson/src/main/java/feign/gson/GsonDecoder.java +++ b/gson/src/main/java/feign/gson/GsonDecoder.java @@ -17,20 +17,25 @@ package feign.gson; import com.google.gson.Gson; import com.google.gson.JsonIOException; +import com.google.gson.TypeAdapter; import feign.Response; import feign.codec.Decoder; - import java.io.IOException; import java.io.Reader; import java.lang.reflect.Type; +import java.util.Collections; import static feign.Util.ensureClosed; public class GsonDecoder implements Decoder { private final Gson gson; + public GsonDecoder(Iterable> adapters) { + this(GsonFactory.create(adapters)); + } + public GsonDecoder() { - this(new Gson()); + this(Collections.>emptyList()); } public GsonDecoder(Gson gson) { diff --git a/gson/src/main/java/feign/gson/GsonEncoder.java b/gson/src/main/java/feign/gson/GsonEncoder.java index 4bee8df5..57e1b54c 100644 --- a/gson/src/main/java/feign/gson/GsonEncoder.java +++ b/gson/src/main/java/feign/gson/GsonEncoder.java @@ -16,14 +16,20 @@ package feign.gson; import com.google.gson.Gson; +import com.google.gson.TypeAdapter; import feign.RequestTemplate; import feign.codec.Encoder; +import java.util.Collections; public class GsonEncoder implements Encoder { private final Gson gson; + public GsonEncoder(Iterable> adapters) { + this(GsonFactory.create(adapters)); + } + public GsonEncoder() { - this(new Gson()); + this(Collections.>emptyList()); } public GsonEncoder(Gson gson) { diff --git a/gson/src/main/java/feign/gson/GsonFactory.java b/gson/src/main/java/feign/gson/GsonFactory.java new file mode 100644 index 00000000..7685b96b --- /dev/null +++ b/gson/src/main/java/feign/gson/GsonFactory.java @@ -0,0 +1,46 @@ +/* + * 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.gson; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.util.Map; + +import static feign.Util.resolveLastTypeParameter; + +final class GsonFactory { + + /** + * Registers type adapters by implicit type. Adds one to read numbers in a + * {@code Map} as Integers. + */ + static Gson create(Iterable> adapters) { + GsonBuilder builder = new GsonBuilder().setPrettyPrinting(); + builder.registerTypeAdapter(new TypeToken>() { + }.getType(), new DoubleToIntMapTypeAdapter()); + for (TypeAdapter adapter : adapters) { + Type type = resolveLastTypeParameter(adapter.getClass(), TypeAdapter.class); + builder.registerTypeAdapter(type, adapter); + } + return builder.create(); + } + + private GsonFactory() { + } +} diff --git a/gson/src/main/java/feign/gson/GsonModule.java b/gson/src/main/java/feign/gson/GsonModule.java deleted file mode 100644 index 79093101..00000000 --- a/gson/src/main/java/feign/gson/GsonModule.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.gson; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.TypeAdapter; -import dagger.Provides; -import feign.Feign; -import feign.codec.Decoder; -import feign.codec.Encoder; - -import javax.inject.Singleton; -import java.lang.reflect.Type; -import java.util.Collections; -import java.util.Set; - -import static feign.Util.resolveLastTypeParameter; - -/** - *

Custom type adapters

- *
- * In order to specify custom json parsing, - * {@code Gson} supports {@link TypeAdapter type adapters}. This module adds one - * to read numbers in a {@code Map} as Integers. You can - * customize further by adding additional set bindings to the raw type - * {@code TypeAdapter}. - *

- *
- * Here's an example of adding a custom json type adapter. - *

- *

- * @Provides(type = Provides.Type.SET)
- * TypeAdapter upperZone() {
- *     return new TypeAdapter<Zone>() {
- *
- *         @Override
- *         public void write(JsonWriter out, Zone value) throws IOException {
- *             throw new IllegalArgumentException();
- *         }
- *
- *         @Override
- *         public Zone read(JsonReader in) throws IOException {
- *             in.beginObject();
- *             Zone zone = new Zone();
- *             while (in.hasNext()) {
- *                 zone.put(in.nextName(), in.nextString().toUpperCase());
- *             }
- *             in.endObject();
- *             return zone;
- *         }
- *     };
- * }
- * 
- */ -@dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) -public final class GsonModule { - - @Provides Encoder encoder(Gson gson) { - return new GsonEncoder(gson); - } - - @Provides Decoder decoder(Gson gson) { - return new GsonDecoder(gson); - } - - @Provides @Singleton Gson gson(Set adapters) { - GsonBuilder builder = new GsonBuilder().setPrettyPrinting(); - for (TypeAdapter adapter : adapters) { - Type type = resolveLastTypeParameter(adapter.getClass(), TypeAdapter.class); - builder.registerTypeAdapter(type, adapter); - } - return builder.create(); - } - - @Provides(type = Provides.Type.SET_VALUES) Set noDefaultTypeAdapters() { - return Collections.emptySet(); - } -} diff --git a/gson/src/test/java/feign/gson/GsonModuleTest.java b/gson/src/test/java/feign/gson/GsonCodecTest.java similarity index 58% rename from gson/src/test/java/feign/gson/GsonModuleTest.java rename to gson/src/test/java/feign/gson/GsonCodecTest.java index bf6e1ada..dab2824d 100644 --- a/gson/src/test/java/feign/gson/GsonModuleTest.java +++ b/gson/src/test/java/feign/gson/GsonCodecTest.java @@ -19,13 +19,8 @@ import com.google.gson.TypeAdapter; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -import dagger.Module; -import dagger.ObjectGraph; -import dagger.Provides; import feign.RequestTemplate; import feign.Response; -import feign.codec.Decoder; -import feign.codec.Encoder; import java.io.IOException; import java.util.Arrays; import java.util.Collection; @@ -34,7 +29,6 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.inject.Inject; import org.junit.Test; import static feign.Util.UTF_8; @@ -42,35 +36,14 @@ import static feign.assertj.FeignAssertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -public class GsonModuleTest { - @Module(includes = GsonModule.class, injects = EncoderAndDecoderBindings.class) - static class EncoderAndDecoderBindings { - @Inject Encoder encoder; - @Inject Decoder decoder; - } - - @Test public void providesEncoderDecoder() throws Exception { - EncoderAndDecoderBindings bindings = new EncoderAndDecoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - - assertEquals(GsonEncoder.class, bindings.encoder.getClass()); - assertEquals(GsonDecoder.class, bindings.decoder.getClass()); - } - - @Module(includes = GsonModule.class, injects = EncoderBindings.class) - static class EncoderBindings { - @Inject Encoder encoder; - } +public class GsonCodecTest { @Test public void encodesMapObjectNumericalValuesAsInteger() throws Exception { - EncoderBindings bindings = new EncoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - Map map = new LinkedHashMap(); map.put("foo", 1); RequestTemplate template = new RequestTemplate(); - bindings.encoder.encode(map, template); + new GsonEncoder().encode(map, template); assertThat(template).hasBody("" // + "{\n" // @@ -78,17 +51,24 @@ public class GsonModuleTest { + "}"); } - @Test public void encodesFormParams() throws Exception { + @Test public void decodesMapObjectNumericalValuesAsInteger() throws Exception { + Map map = new LinkedHashMap(); + map.put("foo", 1); - EncoderBindings bindings = new EncoderBindings(); - ObjectGraph.create(bindings).inject(bindings); + Response response = + Response.create(200, "OK", Collections.>emptyMap(), "{\"foo\": 1}", UTF_8); + assertEquals(new GsonDecoder().decode(response, new TypeToken>() { + }.getType()), map); + } + + @Test public void encodesFormParams() throws Exception { Map form = new LinkedHashMap(); form.put("foo", 1); form.put("bar", Arrays.asList(2, 3)); RequestTemplate template = new RequestTemplate(); - bindings.encoder.encode(form, template); + new GsonEncoder().encode(form, template); assertThat(template).hasBody("" // + "{\n" // @@ -118,14 +98,7 @@ public class GsonModuleTest { private static final long serialVersionUID = 1L; } - @Module(includes = GsonModule.class, injects = DecoderBindings.class) - static class DecoderBindings { - @Inject Decoder decoder; - } - @Test public void decodes() throws Exception { - DecoderBindings bindings = new DecoderBindings(); - ObjectGraph.create(bindings).inject(bindings); List zones = new LinkedList(); zones.add(new Zone("denominator.io.")); @@ -133,16 +106,13 @@ public class GsonModuleTest { Response response = Response.create(200, "OK", Collections.>emptyMap(), zonesJson, UTF_8); - assertEquals(zones, bindings.decoder.decode(response, new TypeToken>() { + assertEquals(zones, new GsonDecoder().decode(response, new TypeToken>() { }.getType())); } @Test public void nullBodyDecodesToNull() throws Exception { - DecoderBindings bindings = new DecoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - Response response = Response.create(204, "OK", Collections.>emptyMap(), (byte[]) null); - assertNull(bindings.decoder.decode(response, String.class)); + assertNull(new GsonDecoder().decode(response, String.class)); } private String zonesJson = ""// @@ -156,33 +126,25 @@ public class GsonModuleTest { + " }\n"// + "]\n"; - @Module(includes = GsonModule.class, injects = CustomTypeAdapter.class) - static class CustomTypeAdapter { - @Provides(type = Provides.Type.SET) TypeAdapter upperZone() { - return new TypeAdapter() { - - @Override public void write(JsonWriter out, Zone value) throws IOException { - throw new IllegalArgumentException(); - } - - @Override public Zone read(JsonReader in) throws IOException { - in.beginObject(); - Zone zone = new Zone(); - while (in.hasNext()) { - zone.put(in.nextName(), in.nextString().toUpperCase()); - } - in.endObject(); - return zone; - } - }; + final TypeAdapter upperZone = new TypeAdapter() { + + @Override public void write(JsonWriter out, Zone value) throws IOException { + throw new IllegalArgumentException(); } - @Inject Decoder decoder; - } + @Override public Zone read(JsonReader in) throws IOException { + in.beginObject(); + Zone zone = new Zone(); + while (in.hasNext()) { + zone.put(in.nextName(), in.nextString().toUpperCase()); + } + in.endObject(); + return zone; + } + }; @Test public void customDecoder() throws Exception { - CustomTypeAdapter bindings = new CustomTypeAdapter(); - ObjectGraph.create(bindings).inject(bindings); + GsonDecoder decoder = new GsonDecoder(Arrays.>asList(upperZone)); List zones = new LinkedList(); zones.add(new Zone("DENOMINATOR.IO.")); @@ -190,7 +152,7 @@ public class GsonModuleTest { Response response = Response.create(200, "OK", Collections.>emptyMap(), zonesJson, UTF_8); - assertEquals(zones, bindings.decoder.decode(response, new TypeToken>() { + assertEquals(zones, decoder.decode(response, new TypeToken>() { }.getType())); } } diff --git a/gson/src/test/java/feign/gson/examples/GitHubExample.java b/gson/src/test/java/feign/gson/examples/GitHubExample.java index 6d41f700..7526bdff 100644 --- a/gson/src/test/java/feign/gson/examples/GitHubExample.java +++ b/gson/src/test/java/feign/gson/examples/GitHubExample.java @@ -19,7 +19,6 @@ import feign.Feign; import feign.Param; import feign.RequestLine; import feign.gson.GsonDecoder; - import java.util.List; /** @@ -37,8 +36,10 @@ public class GitHubExample { int contributions; } - public static void main(String... args) throws InterruptedException { - GitHub github = Feign.builder().decoder(new GsonDecoder()).target(GitHub.class, "https://api.github.com"); + public static void main(String... args) { + GitHub github = Feign.builder() + .decoder(new GsonDecoder()) + .target(GitHub.class, "https://api.github.com"); System.out.println("Let's fetch and print a list of the contributors to this library."); List contributors = github.contributors("netflix", "feign"); diff --git a/jackson/README.md b/jackson/README.md index a6b8f0fc..8be63277 100644 --- a/jackson/README.md +++ b/jackson/README.md @@ -25,9 +25,3 @@ GitHub github = Feign.builder() .decoder(new JacksonDecoder(mapper)) .target(GitHub.class, "https://api.github.com"); ``` - -Alternatively, you can add the encoder and decoder to your Dagger object graph using the provided `JacksonModule` like so: - -```java -GitHub github = Feign.create(GitHub.class, "https://api.github.com", new JacksonModule()); -``` diff --git a/jackson/src/main/java/feign/jackson/JacksonDecoder.java b/jackson/src/main/java/feign/jackson/JacksonDecoder.java index f0734d37..ffeabce5 100644 --- a/jackson/src/main/java/feign/jackson/JacksonDecoder.java +++ b/jackson/src/main/java/feign/jackson/JacksonDecoder.java @@ -16,6 +16,7 @@ package feign.jackson; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.RuntimeJsonMappingException; import feign.Response; @@ -24,12 +25,18 @@ import feign.codec.Decoder; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; +import java.util.Collections; public class JacksonDecoder implements Decoder { private final ObjectMapper mapper; public JacksonDecoder() { - this(new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)); + this(Collections.emptyList()); + } + + public JacksonDecoder(Iterable modules) { + this(new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .registerModules(modules)); } public JacksonDecoder(ObjectMapper mapper) { diff --git a/jackson/src/main/java/feign/jackson/JacksonEncoder.java b/jackson/src/main/java/feign/jackson/JacksonEncoder.java index 1cc6895f..2d0353f9 100644 --- a/jackson/src/main/java/feign/jackson/JacksonEncoder.java +++ b/jackson/src/main/java/feign/jackson/JacksonEncoder.java @@ -17,19 +17,26 @@ package feign.jackson; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; +import java.util.Collections; public class JacksonEncoder implements Encoder { private final ObjectMapper mapper; public JacksonEncoder() { + this(Collections.emptyList()); + } + + public JacksonEncoder(Iterable modules) { this(new ObjectMapper() .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .configure(SerializationFeature.INDENT_OUTPUT, true)); + .configure(SerializationFeature.INDENT_OUTPUT, true) + .registerModules(modules)); } public JacksonEncoder(ObjectMapper mapper) { diff --git a/jackson/src/main/java/feign/jackson/JacksonModule.java b/jackson/src/main/java/feign/jackson/JacksonModule.java deleted file mode 100644 index 7826118a..00000000 --- a/jackson/src/main/java/feign/jackson/JacksonModule.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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.jackson; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import dagger.Provides; -import feign.Feign; -import feign.codec.Decoder; -import feign.codec.Encoder; - -import javax.inject.Singleton; -import java.util.Collections; -import java.util.Set; - -/** - *

Custom serializers/deserializers

- *
- * In order to specify custom json parsing, Jackson's {@code ObjectMapper} supports {@link JsonSerializer serializers} - * and {@link JsonDeserializer deserializers}, which can be bundled together in {@link Module modules}. - *

- *
- * Here's an example of adding a custom module. - *

- *

- * public class ObjectIdSerializer extends StdSerializer<ObjectId> {
- *     public ObjectIdSerializer() {
- *         super(ObjectId.class);
- *     }
- *
- *     @Override
- *     public void serialize(ObjectId value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
- *         jsonGenerator.writeString(value.toString());
- *     }
- * }
- *
- * public class ObjectIdDeserializer extends StdDeserializer<ObjectId> {
- *     public ObjectIdDeserializer() {
- *         super(ObjectId.class);
- *     }
- *
- *     @Override
- *     public ObjectId deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
- *         return ObjectId.massageToObjectId(jsonParser.getValueAsString());
- *     }
- * }
- *
- * public class ObjectIdModule extends SimpleModule {
- *     public ObjectIdModule() {
- *         // first deserializers
- *         addDeserializer(ObjectId.class, new ObjectIdDeserializer());
- *
- *         // then serializers:
- *         addSerializer(ObjectId.class, new ObjectIdSerializer());
- *     }
- * }
- *
- * @Provides(type = Provides.Type.SET)
- * Module objectIdModule() {
- *     return new ObjectIdModule();
- * }
- * 
- */ -@dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) -public final class JacksonModule { - @Provides Encoder encoder(ObjectMapper mapper) { - return new JacksonEncoder(mapper); - } - - @Provides Decoder decoder(ObjectMapper mapper) { - return new JacksonDecoder(mapper); - } - - @Provides @Singleton ObjectMapper mapper(Set modules) { - return new ObjectMapper() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .configure(SerializationFeature.INDENT_OUTPUT, true) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .registerModules(modules); - } - - @Provides(type = Provides.Type.SET_VALUES) Set noDefaultModules() { - return Collections.emptySet(); - } -} diff --git a/jackson/src/test/java/feign/jackson/JacksonModuleTest.java b/jackson/src/test/java/feign/jackson/JacksonCodecTest.java similarity index 63% rename from jackson/src/test/java/feign/jackson/JacksonModuleTest.java rename to jackson/src/test/java/feign/jackson/JacksonCodecTest.java index 698feb32..f59a7bfa 100644 --- a/jackson/src/test/java/feign/jackson/JacksonModuleTest.java +++ b/jackson/src/test/java/feign/jackson/JacksonCodecTest.java @@ -4,15 +4,11 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; -import dagger.Module; -import dagger.ObjectGraph; -import dagger.Provides; import feign.RequestTemplate; import feign.Response; -import feign.codec.Decoder; -import feign.codec.Encoder; import java.io.IOException; import java.util.Arrays; import java.util.Collection; @@ -21,7 +17,6 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import javax.inject.Inject; import org.junit.Test; import static feign.Util.UTF_8; @@ -29,38 +24,14 @@ import static feign.assertj.FeignAssertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -public class JacksonModuleTest { - @Module(includes = JacksonModule.class, injects = EncoderAndDecoderBindings.class) - static class EncoderAndDecoderBindings { - @Inject - Encoder encoder; - @Inject - Decoder decoder; - } - - @Test - public void providesEncoderDecoder() throws Exception { - EncoderAndDecoderBindings bindings = new EncoderAndDecoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - - assertEquals(JacksonEncoder.class, bindings.encoder.getClass()); - assertEquals(JacksonDecoder.class, bindings.decoder.getClass()); - } - - @Module(includes = JacksonModule.class, injects = EncoderBindings.class) - static class EncoderBindings { - @Inject Encoder encoder; - } +public class JacksonCodecTest { @Test public void encodesMapObjectNumericalValuesAsInteger() throws Exception { - EncoderBindings bindings = new EncoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - Map map = new LinkedHashMap(); map.put("foo", 1); RequestTemplate template = new RequestTemplate(); - bindings.encoder.encode(map, template); + new JacksonEncoder().encode(map, template); assertThat(template).hasBody(""// + "{\n" // @@ -69,15 +40,12 @@ public class JacksonModuleTest { } @Test public void encodesFormParams() throws Exception { - EncoderBindings bindings = new EncoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - Map form = new LinkedHashMap(); form.put("foo", 1); form.put("bar", Arrays.asList(2, 3)); RequestTemplate template = new RequestTemplate(); - bindings.encoder.encode(form, template); + new JacksonEncoder().encode(form, template); assertThat(template).hasBody(""// + "{\n" // @@ -105,31 +73,20 @@ public class JacksonModuleTest { private static final long serialVersionUID = 1L; } - @Module(includes = JacksonModule.class, injects = DecoderBindings.class) - static class DecoderBindings { - @Inject Decoder decoder; - } - @Test public void decodes() throws Exception { - DecoderBindings bindings = new DecoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - List zones = new LinkedList(); zones.add(new Zone("denominator.io.")); zones.add(new Zone("denominator.io.", "ABCD")); Response response = Response.create(200, "OK", Collections.>emptyMap(), zonesJson, UTF_8); - assertEquals(zones, bindings.decoder.decode(response, new TypeReference>() { + assertEquals(zones, new JacksonDecoder().decode(response, new TypeReference>() { }.getType())); } @Test public void nullBodyDecodesToNull() throws Exception { - DecoderBindings bindings = new DecoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - Response response = Response.create(204, "OK", Collections.>emptyMap(), (byte[]) null); - assertNull(bindings.decoder.decode(response, String.class)); + assertNull(new JacksonDecoder().decode(response, String.class)); } private String zonesJson = ""// @@ -169,19 +126,8 @@ public class JacksonModuleTest { } } - @Module(includes = JacksonModule.class, injects = CustomJacksonModule.class) - static class CustomJacksonModule { - @Inject Decoder decoder; - - @Provides(type = Provides.Type.SET) - com.fasterxml.jackson.databind.Module upperZone() { - return new ZoneModule(); - } - } - @Test public void customDecoder() throws Exception { - CustomJacksonModule bindings = new CustomJacksonModule(); - ObjectGraph.create(bindings).inject(bindings); + JacksonDecoder decoder = new JacksonDecoder(Arrays.asList(new ZoneModule())); List zones = new LinkedList(); zones.add(new Zone("DENOMINATOR.IO.")); @@ -189,7 +135,7 @@ public class JacksonModuleTest { Response response = Response.create(200, "OK", Collections.>emptyMap(), zonesJson, UTF_8); - assertEquals(zones, bindings.decoder.decode(response, new TypeReference>() { + assertEquals(zones, decoder.decode(response, new TypeReference>() { }.getType())); } } diff --git a/jackson/src/test/java/feign/jackson/examples/GitHubExample.java b/jackson/src/test/java/feign/jackson/examples/GitHubExample.java index 73bacef4..5ec2c2e9 100644 --- a/jackson/src/test/java/feign/jackson/examples/GitHubExample.java +++ b/jackson/src/test/java/feign/jackson/examples/GitHubExample.java @@ -4,7 +4,6 @@ import feign.Feign; import feign.Param; import feign.RequestLine; import feign.jackson.JacksonDecoder; - import java.util.List; /** @@ -29,8 +28,11 @@ public class GitHubExample { } } - public static void main(String... args) throws InterruptedException { - GitHub github = Feign.builder().decoder(new JacksonDecoder()).target(GitHub.class, "https://api.github.com"); + public static void main(String... args) { + GitHub github = Feign.builder() + .decoder(new JacksonDecoder()) + .target(GitHub.class, "https://api.github.com"); + System.out.println("Let's fetch and print a list of the contributors to this library."); List contributors = github.contributors("netflix", "feign"); for (Contributor contributor : contributors) { diff --git a/jaxb/README.md b/jaxb/README.md index 46e1e2d7..2c658a3a 100644 --- a/jaxb/README.md +++ b/jaxb/README.md @@ -16,11 +16,3 @@ Response response = Feign.builder() .decoder(new JAXBDecoder(jaxbFactory)) .target(Response.class, "https://apihost"); ``` - -Alternatively, you can add the encoder and decoder to your Dagger object graph using the provided JAXBModule like so: - -```java -JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder().build(); - -Response response = Feign.create(Response.class, "https://apihost", new JAXBModule(jaxbFactory)); -``` \ No newline at end of file diff --git a/jaxb/src/main/java/feign/jaxb/JAXBDecoder.java b/jaxb/src/main/java/feign/jaxb/JAXBDecoder.java index b119463f..2cbc3cdb 100644 --- a/jaxb/src/main/java/feign/jaxb/JAXBDecoder.java +++ b/jaxb/src/main/java/feign/jaxb/JAXBDecoder.java @@ -19,12 +19,10 @@ import feign.FeignException; import feign.Response; import feign.codec.DecodeException; import feign.codec.Decoder; - -import javax.inject.Inject; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; import java.io.IOException; import java.lang.reflect.Type; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; /** * Decodes responses using JAXB. @@ -49,7 +47,6 @@ import java.lang.reflect.Type; public class JAXBDecoder implements Decoder { private final JAXBContextFactory jaxbContextFactory; - @Inject public JAXBDecoder(JAXBContextFactory jaxbContextFactory) { this.jaxbContextFactory = jaxbContextFactory; } diff --git a/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java b/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java index acbf0ca3..4b7801cb 100644 --- a/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java +++ b/jaxb/src/main/java/feign/jaxb/JAXBEncoder.java @@ -18,11 +18,9 @@ package feign.jaxb; import feign.RequestTemplate; import feign.codec.EncodeException; import feign.codec.Encoder; - -import javax.inject.Inject; +import java.io.StringWriter; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; -import java.io.StringWriter; /** * Encodes requests using JAXB. @@ -47,7 +45,6 @@ import java.io.StringWriter; public class JAXBEncoder implements Encoder { private final JAXBContextFactory jaxbContextFactory; - @Inject public JAXBEncoder(JAXBContextFactory jaxbContextFactory) { this.jaxbContextFactory = jaxbContextFactory; } diff --git a/jaxb/src/main/java/feign/jaxb/JAXBModule.java b/jaxb/src/main/java/feign/jaxb/JAXBModule.java deleted file mode 100644 index 94835dfe..00000000 --- a/jaxb/src/main/java/feign/jaxb/JAXBModule.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2014 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.jaxb; - -import dagger.Provides; -import feign.Feign; -import feign.codec.Decoder; -import feign.codec.Encoder; - -import javax.inject.Singleton; - -/** - * Provides an Encoder and Decoder for handling XML responses with JAXB annotated classes. - *

- *
- * Here is an example of configuring a custom JAXBContextFactory: - *

- *
- *    JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
- *               .withMarshallerJAXBEncoding("UTF-8")
- *               .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
- *               .build();
- *
- *    Response response = Feign.create(Response.class, "http://apihost", new JAXBModule(jaxbFactory));
- * 
- *

- * The JAXBContextFactory should be reused across requests as it caches the created JAXB contexts. - *

- */ -@dagger.Module(injects = Feign.class, addsTo = Feign.Defaults.class) -public final class JAXBModule { - private final JAXBContextFactory jaxbContextFactory; - - public JAXBModule() { - this.jaxbContextFactory = new JAXBContextFactory.Builder().build(); - } - - public JAXBModule(JAXBContextFactory jaxbContextFactory) { - this.jaxbContextFactory = jaxbContextFactory; - } - - @Provides Encoder encoder(JAXBEncoder jaxbEncoder) { - return jaxbEncoder; - } - - @Provides Decoder decoder(JAXBDecoder jaxbDecoder) { - return jaxbDecoder; - } - - @Provides @Singleton JAXBContextFactory jaxbContextFactory() { - return this.jaxbContextFactory; - } -} diff --git a/jaxb/src/test/java/feign/jaxb/JAXBModuleTest.java b/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java similarity index 73% rename from jaxb/src/test/java/feign/jaxb/JAXBModuleTest.java rename to jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java index bc0ed745..30bb99ff 100644 --- a/jaxb/src/test/java/feign/jaxb/JAXBModuleTest.java +++ b/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java @@ -15,15 +15,11 @@ */ package feign.jaxb; -import dagger.Module; -import dagger.ObjectGraph; import feign.RequestTemplate; import feign.Response; -import feign.codec.Decoder; import feign.codec.Encoder; import java.util.Collection; import java.util.Collections; -import javax.inject.Inject; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -34,34 +30,7 @@ import static feign.Util.UTF_8; import static feign.assertj.FeignAssertions.assertThat; import static org.junit.Assert.assertEquals; -public class JAXBModuleTest { - @Module(includes = JAXBModule.class, injects = EncoderAndDecoderBindings.class) - static class EncoderAndDecoderBindings { - @Inject - Encoder encoder; - - @Inject - Decoder decoder; - } - - @Module(includes = JAXBModule.class, injects = EncoderBindings.class) - static class EncoderBindings { - @Inject Encoder encoder; - } - - @Module(includes = JAXBModule.class, injects = DecoderBindings.class) - static class DecoderBindings { - @Inject Decoder decoder; - } - - @Test - public void providesEncoderDecoder() throws Exception { - EncoderAndDecoderBindings bindings = new EncoderAndDecoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - - assertEquals(JAXBEncoder.class, bindings.encoder.getClass()); - assertEquals(JAXBDecoder.class, bindings.decoder.getClass()); - } +public class JAXBCodecTest { @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) @@ -87,14 +56,11 @@ public class JAXBModuleTest { @Test public void encodesXml() throws Exception { - EncoderBindings bindings = new EncoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - MockObject mock = new MockObject(); mock.value = "Test"; RequestTemplate template = new RequestTemplate(); - bindings.encoder.encode(mock, template); + new JAXBEncoder(new JAXBContextFactory.Builder().build()).encode(mock, template); assertThat(template).hasBody( "Test"); @@ -106,8 +72,7 @@ public class JAXBModuleTest { .withMarshallerJAXBEncoding("UTF-16") .build(); - JAXBModule jaxbModule = new JAXBModule(jaxbContextFactory); - Encoder encoder = jaxbModule.encoder(new JAXBEncoder(jaxbContextFactory)); + Encoder encoder = new JAXBEncoder(jaxbContextFactory); MockObject mock = new MockObject(); mock.value = "Test"; @@ -125,8 +90,7 @@ public class JAXBModuleTest { .withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd") .build(); - JAXBModule jaxbModule = new JAXBModule(jaxbContextFactory); - Encoder encoder = jaxbModule.encoder(new JAXBEncoder(jaxbContextFactory)); + Encoder encoder = new JAXBEncoder(jaxbContextFactory); MockObject mock = new MockObject(); mock.value = "Test"; @@ -146,8 +110,7 @@ public class JAXBModuleTest { .withMarshallerNoNamespaceSchemaLocation("http://apihost/schema.xsd") .build(); - JAXBModule jaxbModule = new JAXBModule(jaxbContextFactory); - Encoder encoder = jaxbModule.encoder(new JAXBEncoder(jaxbContextFactory)); + Encoder encoder = new JAXBEncoder(jaxbContextFactory); MockObject mock = new MockObject(); mock.value = "Test"; @@ -167,8 +130,7 @@ public class JAXBModuleTest { .withMarshallerFormattedOutput(true) .build(); - JAXBModule jaxbModule = new JAXBModule(jaxbContextFactory); - Encoder encoder = jaxbModule.encoder(new JAXBEncoder(jaxbContextFactory)); + Encoder encoder = new JAXBEncoder(jaxbContextFactory); MockObject mock = new MockObject(); mock.value = "Test"; @@ -187,9 +149,6 @@ public class JAXBModuleTest { @Test public void decodesXml() throws Exception { - DecoderBindings bindings = new DecoderBindings(); - ObjectGraph.create(bindings).inject(bindings); - MockObject mock = new MockObject(); mock.value = "Test"; @@ -199,6 +158,8 @@ public class JAXBModuleTest { Response response = Response.create(200, "OK", Collections.>emptyMap(), mockXml, UTF_8); - assertEquals(mock, bindings.decoder.decode(response, MockObject.class)); + JAXBDecoder decoder = new JAXBDecoder(new JAXBContextFactory.Builder().build()); + + assertEquals(mock, decoder.decode(response, MockObject.class)); } } diff --git a/jaxrs/src/main/java/feign/jaxrs/JAXRSContract.java b/jaxrs/src/main/java/feign/jaxrs/JAXRSContract.java new file mode 100644 index 00000000..34d9526f --- /dev/null +++ b/jaxrs/src/main/java/feign/jaxrs/JAXRSContract.java @@ -0,0 +1,123 @@ +/* + * 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.jaxrs; + +import feign.Contract; +import feign.MethodMetadata; + +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collection; + +import static feign.Util.checkState; +import static feign.Util.emptyToNull; + +/** + * Please refer to the + * Feign JAX-RS README. + */ +public final class JAXRSContract extends Contract.BaseContract { + static final String ACCEPT = "Accept"; + static final String CONTENT_TYPE = "Content-Type"; + + @Override + public MethodMetadata parseAndValidatateMetadata(Method method) { + MethodMetadata md = super.parseAndValidatateMetadata(method); + Path path = method.getDeclaringClass().getAnnotation(Path.class); + if (path != null) { + String pathValue = emptyToNull(path.value()); + checkState(pathValue != null, "Path.value() was empty on type %s", method.getDeclaringClass().getName()); + if (!pathValue.startsWith("/")) { + pathValue = "/" + pathValue; + } + md.template().insert(0, pathValue); + } + return md; + } + + @Override + protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { + Class annotationType = methodAnnotation.annotationType(); + HttpMethod http = annotationType.getAnnotation(HttpMethod.class); + if (http != null) { + checkState(data.template().method() == null, + "Method %s contains multiple HTTP methods. Found: %s and %s", method.getName(), data.template() + .method(), http.value()); + data.template().method(http.value()); + } else if (annotationType == Path.class) { + String pathValue = emptyToNull(Path.class.cast(methodAnnotation).value()); + checkState(pathValue != null, "Path.value() was empty on method %s", method.getName()); + String methodAnnotationValue = Path.class.cast(methodAnnotation).value(); + if (!methodAnnotationValue.startsWith("/") && !data.template().toString().endsWith("/")) { + methodAnnotationValue = "/" + methodAnnotationValue; + } + data.template().append(methodAnnotationValue); + } else if (annotationType == Produces.class) { + String[] serverProduces = ((Produces) methodAnnotation).value(); + String clientAccepts = serverProduces.length == 0 ? null: emptyToNull(serverProduces[0]); + checkState(clientAccepts != null, "Produces.value() was empty on method %s", method.getName()); + data.template().header(ACCEPT, clientAccepts); + } else if (annotationType == Consumes.class) { + String[] serverConsumes = ((Consumes) methodAnnotation).value(); + String clientProduces = serverConsumes.length == 0 ? null: emptyToNull(serverConsumes[0]); + checkState(clientProduces != null, "Consumes.value() was empty on method %s", method.getName()); + data.template().header(CONTENT_TYPE, clientProduces); + } + } + + @Override + protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { + boolean isHttpParam = false; + for (Annotation parameterAnnotation : annotations) { + Class annotationType = parameterAnnotation.annotationType(); + if (annotationType == PathParam.class) { + String name = PathParam.class.cast(parameterAnnotation).value(); + checkState(emptyToNull(name) != null, "PathParam.value() was empty on parameter %s", paramIndex); + nameParam(data, name, paramIndex); + isHttpParam = true; + } else if (annotationType == QueryParam.class) { + String name = QueryParam.class.cast(parameterAnnotation).value(); + checkState(emptyToNull(name) != null, "QueryParam.value() was empty on parameter %s", paramIndex); + Collection query = addTemplatedParam(data.template().queries().get(name), name); + data.template().query(name, query); + nameParam(data, name, paramIndex); + isHttpParam = true; + } else if (annotationType == HeaderParam.class) { + String name = HeaderParam.class.cast(parameterAnnotation).value(); + checkState(emptyToNull(name) != null, "HeaderParam.value() was empty on parameter %s", paramIndex); + Collection header = addTemplatedParam(data.template().headers().get(name), name); + data.template().header(name, header); + nameParam(data, name, paramIndex); + isHttpParam = true; + } else if (annotationType == FormParam.class) { + String name = FormParam.class.cast(parameterAnnotation).value(); + checkState(emptyToNull(name) != null, "FormParam.value() was empty on parameter %s", paramIndex); + data.formParams().add(name); + nameParam(data, name, paramIndex); + isHttpParam = true; + } + } + return isHttpParam; + } +} diff --git a/jaxrs/src/main/java/feign/jaxrs/JAXRSModule.java b/jaxrs/src/main/java/feign/jaxrs/JAXRSModule.java deleted file mode 100644 index 1560058f..00000000 --- a/jaxrs/src/main/java/feign/jaxrs/JAXRSModule.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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.jaxrs; - -import dagger.Provides; -import feign.Body; -import feign.Contract; -import feign.MethodMetadata; - -import javax.ws.rs.Consumes; -import javax.ws.rs.FormParam; -import javax.ws.rs.HeaderParam; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Collection; - -import static feign.Util.checkState; -import static feign.Util.emptyToNull; - -/** - * Please refer to the - * Feign JAX-RS README. - */ -@dagger.Module(library = true, overrides = true) -public final class JAXRSModule { - static final String ACCEPT = "Accept"; - static final String CONTENT_TYPE = "Content-Type"; - - @Provides Contract provideContract() { - return new JAXRSContract(); - } - - public static final class JAXRSContract extends Contract.BaseContract { - - @Override - public MethodMetadata parseAndValidatateMetadata(Method method) { - MethodMetadata md = super.parseAndValidatateMetadata(method); - Path path = method.getDeclaringClass().getAnnotation(Path.class); - if (path != null) { - String pathValue = emptyToNull(path.value()); - checkState(pathValue != null, "Path.value() was empty on type %s", method.getDeclaringClass().getName()); - if (!pathValue.startsWith("/")) { - pathValue = "/" + pathValue; - } - md.template().insert(0, pathValue); - } - return md; - } - - @Override - protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) { - Class annotationType = methodAnnotation.annotationType(); - HttpMethod http = annotationType.getAnnotation(HttpMethod.class); - if (http != null) { - checkState(data.template().method() == null, - "Method %s contains multiple HTTP methods. Found: %s and %s", method.getName(), data.template() - .method(), http.value()); - data.template().method(http.value()); - } else if (annotationType == Path.class) { - String pathValue = emptyToNull(Path.class.cast(methodAnnotation).value()); - checkState(pathValue != null, "Path.value() was empty on method %s", method.getName()); - String methodAnnotationValue = Path.class.cast(methodAnnotation).value(); - if (!methodAnnotationValue.startsWith("/") && !data.template().toString().endsWith("/")) { - methodAnnotationValue = "/" + methodAnnotationValue; - } - data.template().append(methodAnnotationValue); - } else if (annotationType == Produces.class) { - String[] serverProduces = ((Produces) methodAnnotation).value(); - String clientAccepts = serverProduces.length == 0 ? null: emptyToNull(serverProduces[0]); - checkState(clientAccepts != null, "Produces.value() was empty on method %s", method.getName()); - data.template().header(ACCEPT, clientAccepts); - } else if (annotationType == Consumes.class) { - String[] serverConsumes = ((Consumes) methodAnnotation).value(); - String clientProduces = serverConsumes.length == 0 ? null: emptyToNull(serverConsumes[0]); - checkState(clientProduces != null, "Consumes.value() was empty on method %s", method.getName()); - data.template().header(CONTENT_TYPE, clientProduces); - } - } - - @Override - protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) { - boolean isHttpParam = false; - for (Annotation parameterAnnotation : annotations) { - Class annotationType = parameterAnnotation.annotationType(); - if (annotationType == PathParam.class) { - String name = PathParam.class.cast(parameterAnnotation).value(); - checkState(emptyToNull(name) != null, "PathParam.value() was empty on parameter %s", paramIndex); - nameParam(data, name, paramIndex); - isHttpParam = true; - } else if (annotationType == QueryParam.class) { - String name = QueryParam.class.cast(parameterAnnotation).value(); - checkState(emptyToNull(name) != null, "QueryParam.value() was empty on parameter %s", paramIndex); - Collection query = addTemplatedParam(data.template().queries().get(name), name); - data.template().query(name, query); - nameParam(data, name, paramIndex); - isHttpParam = true; - } else if (annotationType == HeaderParam.class) { - String name = HeaderParam.class.cast(parameterAnnotation).value(); - checkState(emptyToNull(name) != null, "HeaderParam.value() was empty on parameter %s", paramIndex); - Collection header = addTemplatedParam(data.template().headers().get(name), name); - data.template().header(name, header); - nameParam(data, name, paramIndex); - isHttpParam = true; - } else if (annotationType == FormParam.class) { - String name = FormParam.class.cast(parameterAnnotation).value(); - checkState(emptyToNull(name) != null, "FormParam.value() was empty on parameter %s", paramIndex); - data.formParams().add(name); - nameParam(data, name, paramIndex); - isHttpParam = true; - } - } - return isHttpParam; - } - } -} diff --git a/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java b/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java index a88fcb55..e3cb2872 100644 --- a/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java +++ b/jaxrs/src/test/java/feign/jaxrs/JAXRSContractTest.java @@ -44,14 +44,13 @@ import static java.util.Arrays.asList; import static org.assertj.core.data.MapEntry.entry; /** - * Tests interfaces defined per {@link feign.jaxrs.JAXRSModule.JAXRSContract} are interpreted into expected {@link feign + * Tests interfaces defined per {@link feign.jaxrs.JAXRSContract} are interpreted into expected {@link feign * .RequestTemplate template} * instances. */ public class JAXRSContractTest { @Rule public final ExpectedException thrown = ExpectedException.none(); - - JAXRSModule.JAXRSContract contract = new JAXRSModule.JAXRSContract(); + JAXRSContract contract = new JAXRSContract(); interface Methods { @POST void post(); diff --git a/jaxrs/src/test/java/feign/jaxrs/examples/GitHubExample.java b/jaxrs/src/test/java/feign/jaxrs/examples/GitHubExample.java index 5e994244..2a21e4dd 100644 --- a/jaxrs/src/test/java/feign/jaxrs/examples/GitHubExample.java +++ b/jaxrs/src/test/java/feign/jaxrs/examples/GitHubExample.java @@ -15,17 +15,12 @@ */ package feign.jaxrs.examples; -import dagger.Module; -import dagger.Provides; import feign.Feign; -import feign.Logger; -import feign.gson.GsonModule; -import feign.jaxrs.JAXRSModule; - +import feign.jaxrs.JAXRSContract; +import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; -import java.util.List; /** * adapted from {@code com.example.retrofit.GitHubClient} @@ -43,7 +38,9 @@ public class GitHubExample { } public static void main(String... args) throws InterruptedException { - GitHub github = Feign.create(GitHub.class, "https://api.github.com", new GitHubModule()); + GitHub github = Feign.builder() + .contract(new JAXRSContract()) + .target(GitHub.class, "https://api.github.com"); System.out.println("Let's fetch and print a list of the contributors to this library."); List contributors = github.contributors("netflix", "feign"); @@ -51,19 +48,4 @@ public class GitHubExample { System.out.println(contributor.login + " (" + contributor.contributions + ")"); } } - - /** - * JAXRSModule tells us to process @GET etc annotations - */ - @Module(overrides = true, library = true, includes = {JAXRSModule.class, GsonModule.class}) - static class GitHubModule { - - @Provides Logger.Level loggingLevel() { - return Logger.Level.BASIC; - } - - @Provides Logger logger() { - return new Logger.ErrorLogger(); - } - } } diff --git a/ribbon/README.md b/ribbon/README.md index 02f72ef9..4de2eba3 100644 --- a/ribbon/README.md +++ b/ribbon/README.md @@ -4,17 +4,17 @@ This module includes a feign `Target` and `Client` adapter to take advantage of ## Conventions This integration relies on the Feign `Target.url()` being encoded like `https://myAppProd` where `myAppProd` is the ribbon client or loadbalancer name and `myAppProd.ribbon.listOfServers` configuration is set. -### RibbonModule -Adding `RibbonModule` overrides URL resolution of Feign's client, adding smart routing and resiliency capabilities provided by Ribbon. +### RibbonClient +Adding `RibbonClient` overrides URL resolution of Feign's client, adding smart routing and resiliency capabilities provided by Ribbon. #### Usage instead of  ```java -MyService api = Feign.create(MyService.class, "https://myAppProd-1234567890.us-east-1.elb.amazonaws.com"); +MyService api = Feign.builder().target(MyService.class, "https://myAppProd-1234567890.us-east-1.elb.amazonaws.com"); ``` do ```java -MyService api = Feign.create(MyService.class, "https://myAppProd", new RibbonModule()); +MyService api = Feign.builder().client(new RibbonClient()).target(MyService.class, "https://myAppProd"); ``` ### LoadBalancingTarget Using or extending `LoadBalancingTarget` will enable dynamic url discovery via ribbon including incrementing server request counts. @@ -22,9 +22,9 @@ Using or extending `LoadBalancingTarget` will enable dynamic url discovery via r #### Usage instead of ```java -MyService api = Feign.create(MyService.class, "https://myAppProd-1234567890.us-east-1.elb.amazonaws.com"); +MyService api = Feign.builder().target(MyService.class, "https://myAppProd-1234567890.us-east-1.elb.amazonaws.com"); ``` do ```java -MyService api = Feign.create(LoadBalancingTarget.create(MyService.class, "https://myAppProd")); +MyService api = Feign.builder().target(LoadBalancingTarget.create(MyService.class, "https://myAppProd")); ``` diff --git a/ribbon/src/main/java/feign/ribbon/LoadBalancingTarget.java b/ribbon/src/main/java/feign/ribbon/LoadBalancingTarget.java index efa18e92..d1057027 100644 --- a/ribbon/src/main/java/feign/ribbon/LoadBalancingTarget.java +++ b/ribbon/src/main/java/feign/ribbon/LoadBalancingTarget.java @@ -34,7 +34,7 @@ import static java.lang.String.format; *
* Ex. *
- * MyService api = Feign.create(LoadBalancingTarget.create(MyService.class, "http://myAppProd"))
+ * MyService api = Feign.builder().target(LoadBalancingTarget.create(MyService.class, "http://myAppProd"))
  * 
* Where {@code myAppProd} is the ribbon loadbalancer name and {@code myAppProd.ribbon.listOfServers} configuration * is set. diff --git a/ribbon/src/main/java/feign/ribbon/RibbonClient.java b/ribbon/src/main/java/feign/ribbon/RibbonClient.java index 1535c24f..e9abdc78 100644 --- a/ribbon/src/main/java/feign/ribbon/RibbonClient.java +++ b/ribbon/src/main/java/feign/ribbon/RibbonClient.java @@ -4,18 +4,11 @@ import com.netflix.client.ClientException; import com.netflix.client.ClientFactory; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.ILoadBalancer; - -import java.io.IOException; -import java.net.URI; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; - import feign.Client; import feign.Request; import feign.Response; -import dagger.Lazy; +import java.io.IOException; +import java.net.URI; /** * RibbonClient can be used in Fiegn builder to activate smart routing and resiliency capabilities provided by Ribbon. @@ -31,18 +24,7 @@ public class RibbonClient implements Client { private final Client delegate; public RibbonClient() { - this.delegate = new Client.Default( - new Lazy() { - public SSLSocketFactory get() { - return (SSLSocketFactory)SSLSocketFactory.getDefault(); - } - }, - new Lazy() { - public HostnameVerifier get() { - return HttpsURLConnection.getDefaultHostnameVerifier(); - } - } - ); + this.delegate = new Client.Default(null, null); } public RibbonClient(Client delegate) { diff --git a/ribbon/src/main/java/feign/ribbon/RibbonModule.java b/ribbon/src/main/java/feign/ribbon/RibbonModule.java deleted file mode 100644 index 33ed6bc8..00000000 --- a/ribbon/src/main/java/feign/ribbon/RibbonModule.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.ribbon; - -import dagger.Provides; -import feign.Client; -import javax.inject.Named; -import javax.inject.Singleton; - -/** - * Adding this module will override URL resolution of {@link feign.Client Feign's client}, - * adding smart routing and resiliency capabilities provided by Ribbon. - *
- * When using this, ensure the {@link feign.Target#url()} is set to as {@code http://clientName} - * or {@code https://clientName}. {@link com.netflix.client.config.IClientConfig#getClientName() clientName} - * will lookup the real url and port of your service dynamically. - *
- * Ex. - *
- * MyService api = Feign.create(MyService.class, "http://myAppProd", new RibbonModule());
- * 
- * Where {@code myAppProd} is the ribbon client name and {@code myAppProd.ribbon.listOfServers} configuration - * is set. - */ -@dagger.Module(overrides = true, library = true, complete = false) -public class RibbonModule { - - @Provides @Named("delegate") Client delegate(Client.Default delegate) { - return delegate; - } - - @Provides @Singleton Client httpClient(@Named("delegate") Client client) { - return new RibbonClient(client); - } -} diff --git a/ribbon/src/test/java/feign/ribbon/RibbonClientTest.java b/ribbon/src/test/java/feign/ribbon/RibbonClientTest.java index 346a2ff1..c1e05da9 100644 --- a/ribbon/src/test/java/feign/ribbon/RibbonClientTest.java +++ b/ribbon/src/test/java/feign/ribbon/RibbonClientTest.java @@ -18,24 +18,19 @@ package feign.ribbon; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.SocketPolicy; import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; -import dagger.Provides; import feign.Feign; import feign.Param; import feign.RequestLine; -import feign.codec.Decoder; -import feign.codec.Encoder; - import java.io.IOException; import java.net.URL; - -import static com.netflix.config.ConfigurationManager.getConfigInstance; -import static org.junit.Assert.assertEquals; - import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; +import static com.netflix.config.ConfigurationManager.getConfigInstance; +import static org.junit.Assert.assertEquals; + public class RibbonClientTest { @Rule public final TestName testName = new TestName(); @Rule public final MockWebServerRule server1 = new MockWebServerRule(); @@ -44,17 +39,6 @@ public class RibbonClientTest { interface TestInterface { @RequestLine("POST /") void post(); @RequestLine("GET /?a={a}") void getWithQueryParameters(@Param("a") String a); - - @dagger.Module(injects = Feign.class, overrides = true, addsTo = Feign.Defaults.class) - static class Module { - @Provides Decoder defaultDecoder() { - return new Decoder.Default(); - } - - @Provides Encoder defaultEncoder() { - return new Encoder.Default(); - } - } } @Test public void loadBalancingDefaultPolicyRoundRobin() throws IOException, InterruptedException { @@ -63,8 +47,7 @@ public class RibbonClientTest { getConfigInstance().setProperty(serverListKey(), hostAndPort(server1.getUrl("")) + "," + hostAndPort(server2.getUrl(""))); - TestInterface api = Feign.create(TestInterface.class, "http://" + client(), new TestInterface.Module(), - new RibbonModule()); + TestInterface api = Feign.builder().client(new RibbonClient()).target(TestInterface.class, "http://" + client()); api.post(); api.post(); @@ -81,9 +64,7 @@ public class RibbonClientTest { getConfigInstance().setProperty(serverListKey(), hostAndPort(server1.getUrl(""))); - - TestInterface api = Feign.create(TestInterface.class, "http://" + client(), new TestInterface.Module(), - new RibbonModule()); + TestInterface api = Feign.builder().client(new RibbonClient()).target(TestInterface.class, "http://" + client()); api.post(); @@ -107,8 +88,7 @@ public class RibbonClientTest { getConfigInstance().setProperty(serverListKey(), hostAndPort(server1.getUrl(""))); - TestInterface api = Feign.create(TestInterface.class, "http://" + client(), new TestInterface.Module(), - new RibbonModule()); + TestInterface api = Feign.builder().client(new RibbonClient()).target(TestInterface.class, "http://" + client()); api.getWithQueryParameters(queryStringValue); diff --git a/sax/src/main/java/feign/sax/SAXDecoder.java b/sax/src/main/java/feign/sax/SAXDecoder.java index 0afc8177..b038f854 100644 --- a/sax/src/main/java/feign/sax/SAXDecoder.java +++ b/sax/src/main/java/feign/sax/SAXDecoder.java @@ -18,19 +18,17 @@ package feign.sax; import feign.Response; import feign.codec.DecodeException; import feign.codec.Decoder; -import org.xml.sax.ContentHandler; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import org.xml.sax.helpers.XMLReaderFactory; - -import javax.inject.Provider; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Type; import java.util.LinkedHashMap; import java.util.Map; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; import static feign.Util.checkNotNull; import static feign.Util.checkState; @@ -50,19 +48,6 @@ import static feign.Util.resolveLastTypeParameter; * .build()) * .target(MyApi.class, "http://api"); * - *

- *

Advanced example with Dagger

- *
- *
- * @Provides
- * Decoder saxDecoder(Provider<ContentHandlerForFoo> foo, //
- *         Provider<ContentHandlerForBar> bar) {
- *     return SAXDecoder.builder() //
- *             .registerContentHandler(Foo.class, foo) //
- *             .registerContentHandler(Bar.class, bar) //
- *             .build();
- * }
- * 
*/ public class SAXDecoder implements Decoder { @@ -70,10 +55,9 @@ public class SAXDecoder implements Decoder { return new Builder(); } - // builder as dagger doesn't support wildcard bindings, map bindings, or set bindings of providers. public static class Builder { - private final Map>> handlerProviders = - new LinkedHashMap>>(); + private final Map> handlerFactories = + new LinkedHashMap>(); /** * Will call {@link Constructor#newInstance(Object...)} on {@code handlerClass} for each content stream. @@ -86,13 +70,13 @@ public class SAXDecoder implements Decoder { */ public > Builder registerContentHandler(Class handlerClass) { Type type = resolveLastTypeParameter(checkNotNull(handlerClass, "handlerClass"), ContentHandlerWithResult.class); - return registerContentHandler(type, new NewInstanceProvider(handlerClass)); + return registerContentHandler(type, new NewInstanceContentHandlerWithResultFactory(handlerClass)); } - private static class NewInstanceProvider> implements Provider { - private final Constructor ctor; + private static class NewInstanceContentHandlerWithResultFactory implements ContentHandlerWithResult.Factory { + private final Constructor> ctor; - private NewInstanceProvider(Class clazz) { + private NewInstanceContentHandlerWithResultFactory(Class> clazz) { try { this.ctor = clazz.getDeclaredConstructor(); // allow private or package protected ctors @@ -102,7 +86,7 @@ public class SAXDecoder implements Decoder { } } - @Override public T get() { + @Override public ContentHandlerWithResult create() { try { return ctor.newInstance(); } catch (Exception e) { @@ -112,16 +96,16 @@ public class SAXDecoder implements Decoder { } /** - * Will call {@link Provider#get()} on {@code handler} for each content stream. + * Will call {@link ContentHandlerWithResult.Factory#create()} on {@code handler} for each content stream. * The {@code handler} is expected to have a generic parameter of {@code type}. */ - public Builder registerContentHandler(Type type, Provider> handler) { - this.handlerProviders.put(checkNotNull(type, "type"), checkNotNull(handler, "handler")); + public Builder registerContentHandler(Type type, ContentHandlerWithResult.Factory handler) { + this.handlerFactories.put(checkNotNull(type, "type"), checkNotNull(handler, "handler")); return this; } public SAXDecoder build() { - return new SAXDecoder(handlerProviders); + return new SAXDecoder(handlerFactories); } } @@ -129,16 +113,21 @@ public class SAXDecoder implements Decoder { * Implementations are not intended to be shared across requests. */ public interface ContentHandlerWithResult extends ContentHandler { + + public interface Factory { + ContentHandlerWithResult create(); + } + /** * expected to be set following a call to {@link XMLReader#parse(InputSource)} */ T result(); } - private final Map>> handlerProviders; + private final Map> handlerFactories; - private SAXDecoder(Map>> handlerProviders) { - this.handlerProviders = handlerProviders; + private SAXDecoder(Map> handlerFactories) { + this.handlerFactories = handlerFactories; } @Override @@ -146,9 +135,9 @@ public class SAXDecoder implements Decoder { if (response.body() == null) { return null; } - Provider> handlerProvider = handlerProviders.get(type); - checkState(handlerProvider != null, "type %s not in configured handlers %s", type, handlerProviders.keySet()); - ContentHandlerWithResult handler = handlerProvider.get(); + ContentHandlerWithResult.Factory handlerFactory = handlerFactories.get(type); + checkState(handlerFactory != null, "type %s not in configured handlers %s", type, handlerFactories.keySet()); + ContentHandlerWithResult handler = handlerFactory.create(); try { XMLReader xmlReader = XMLReaderFactory.createXMLReader(); xmlReader.setFeature("http://xml.org/sax/features/namespaces", false); diff --git a/sax/src/test/java/feign/sax/SAXDecoderTest.java b/sax/src/test/java/feign/sax/SAXDecoderTest.java index f6274645..903eb60b 100644 --- a/sax/src/test/java/feign/sax/SAXDecoderTest.java +++ b/sax/src/test/java/feign/sax/SAXDecoderTest.java @@ -15,17 +15,12 @@ */ package feign.sax; -import dagger.ObjectGraph; -import dagger.Provides; import feign.Response; import feign.codec.Decoder; import java.io.IOException; import java.text.ParseException; import java.util.Collection; import java.util.Collections; -import javax.inject.Inject; -import javax.inject.Provider; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -35,26 +30,17 @@ import static feign.Util.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -// unbound wildcards are not currently injectable in dagger. -@SuppressWarnings("rawtypes") public class SAXDecoderTest { @Rule public final ExpectedException thrown = ExpectedException.none(); - @dagger.Module(injects = SAXDecoderTest.class) - static class Module { - @Provides Decoder saxDecoder(Provider networkStatus) { - return SAXDecoder.builder() // - .registerContentHandler(NetworkStatus.class, networkStatus) // - .registerContentHandler(NetworkStatusStringHandler.class) // - .build(); - } - } - - @Inject Decoder decoder; - - @Before public void inject() { - ObjectGraph.create(new Module()).inject(this); - } + Decoder decoder = SAXDecoder.builder() // + .registerContentHandler(NetworkStatus.class, new SAXDecoder.ContentHandlerWithResult.Factory() { + @Override public SAXDecoder.ContentHandlerWithResult create() { + return new NetworkStatusHandler(); + } + }) // + .registerContentHandler(NetworkStatusStringHandler.class) // + .build(); @Test public void parsesConfiguredTypes() throws ParseException, IOException { assertEquals(NetworkStatus.FAILED, decoder.decode(statusFailedResponse(), NetworkStatus.class)); @@ -87,8 +73,6 @@ public class SAXDecoderTest { static class NetworkStatusStringHandler extends DefaultHandler implements SAXDecoder.ContentHandlerWithResult { - @Inject NetworkStatusStringHandler() { - } private StringBuilder currentText = new StringBuilder(); @@ -115,8 +99,6 @@ public class SAXDecoderTest { static class NetworkStatusHandler extends DefaultHandler implements SAXDecoder.ContentHandlerWithResult { - @Inject NetworkStatusHandler() { - } private StringBuilder currentText = new StringBuilder();