From b389ef03ba522eb33ee562d656101f7ddd1563b7 Mon Sep 17 00:00:00 2001 From: adriancole Date: Tue, 17 Sep 2013 11:31:59 -0700 Subject: [PATCH] support Feign.builder() w/ SAX decoder --- README.md | 9 +++ sax/README.md | 20 ++--- sax/src/main/java/feign/sax/SAXDecoder.java | 79 +++++++++++++++---- .../test/java/feign/sax/SAXDecoderTest.java | 10 +-- .../java/feign/sax/examples/IAMExample.java | 29 +------ 5 files changed, 87 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 3541de16..c8dfcd3c 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,15 @@ GitHub github = Feign.create(GitHub.class, "https://api.github.com", new GsonMod ### Sax [SaxDecoder](https://github.com/Netflix/feign/tree/master/sax) allows you to decode XML in a way that is compatible with normal JVM and also Android environments. +Here's an example of how to configure Sax response parsing: +```java +api = Feign.builder() + .decoder(SAXDecoder.builder() + .registerContentHandler(UserIdHandler.class) + .build()) + .target(Api.class, "https://apihost"); +``` + ### 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. diff --git a/sax/README.md b/sax/README.md index e522f6f2..1c901ed6 100644 --- a/sax/README.md +++ b/sax/README.md @@ -6,19 +6,9 @@ This module adds support for decoding xml via SAX. Add this to your object graph like so: ```java -IAM iam = Feign.create(IAM.class, "https://iam.amazonaws.com", new DecodeWithSax()); - ---snip-- -@Module(addsTo = Feign.Defaults.class) -static class DecodeWithSax { - @Provides Decoder saxDecoder(Provider userIdHandler) { - return SAXDecoder.builder() // - .addContentHandler(userIdHandler) // - .build(); - } - - @Provides Encoder defaultEncoder() { - return new Encoder.Default(); - } -} +api = Feign.builder() + .decoder(SAXDecoder.builder() + .registerContentHandler(UserIdHandler.class) + .build()) + .target(Api.class, "https://apihost"); ``` diff --git a/sax/src/main/java/feign/sax/SAXDecoder.java b/sax/src/main/java/feign/sax/SAXDecoder.java index 1d467249..944f3255 100644 --- a/sax/src/main/java/feign/sax/SAXDecoder.java +++ b/sax/src/main/java/feign/sax/SAXDecoder.java @@ -16,8 +16,8 @@ package feign.sax; import feign.Response; -import feign.codec.Decoder; import feign.codec.DecodeException; +import feign.codec.Decoder; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -27,6 +27,7 @@ import org.xml.sax.helpers.XMLReaderFactory; import javax.inject.Provider; import java.io.IOException; import java.io.Reader; +import java.lang.reflect.Constructor; import java.lang.reflect.Type; import java.util.LinkedHashMap; import java.util.Map; @@ -37,17 +38,28 @@ import static feign.Util.ensureClosed; import static feign.Util.resolveLastTypeParameter; /** - * Decodes responses using SAX. Configure using the {@link SAXDecoder.Builder - * builder}. + * Decodes responses using SAX, which is supported both in normal JVM environments, as well Android. + *
+ *

Basic example with with Feign.Builder

+ *
+ *
+ * api = Feign.builder()
+ *            .decoder(SAXDecoder.builder()
+ *                               .registerContentHandler(ContentHandlerForFoo.class)
+ *                               .registerContentHandler(ContentHandlerForBar.class)
+ *                               .build())
+ *            .target(MyApi.class, "http://api");
+ * 
*

- * + *

Advanced example with Dagger

+ *
*
  * @Provides
  * Decoder saxDecoder(Provider<ContentHandlerForFoo> foo, //
  *         Provider<ContentHandlerForBar> bar) {
  *     return SAXDecoder.builder() //
- *             .addContentHandler(foo) //
- *             .addContentHandler(bar) //
+ *             .registerContentHandler(Foo.class, foo) //
+ *             .registerContentHandler(Bar.class, bar) //
  *             .build();
  * }
  * 
@@ -63,10 +75,48 @@ public class SAXDecoder implements Decoder { private final Map>> handlerProviders = new LinkedHashMap>>(); - public Builder addContentHandler(Provider> handler) { - Type type = resolveLastTypeParameter(checkNotNull(handler, "handler").getClass(), Provider.class); - type = resolveLastTypeParameter(type, ContentHandlerWithResult.class); - this.handlerProviders.put(type, handler); + /** + * Will call {@link Constructor#newInstance(Object...)} on {@code handlerClass} for each content stream. + *

+ *

Note

+ *
+ * While this is costly vs {@code new}, it may not affect real performance due to the high cost of reading streams. + * + * @throws IllegalArgumentException if there's no no-arg constructor on {@code handlerClass}. + */ + public > Builder registerContentHandler(Class handlerClass) { + Type type = resolveLastTypeParameter(checkNotNull(handlerClass, "handlerClass"), ContentHandlerWithResult.class); + return registerContentHandler(type, new NewInstanceProvider(handlerClass)); + } + + private static class NewInstanceProvider> implements Provider { + private final Constructor ctor; + + private NewInstanceProvider(Class clazz) { + try { + this.ctor = clazz.getDeclaredConstructor(); + // allow private or package protected ctors + ctor.setAccessible(true); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("ensure " + clazz + " has a no-args constructor", e); + } + } + + @Override public T get() { + try { + return ctor.newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException("exception attempting to instantiate " + ctor, e); + } + } + } + + /** + * Will call {@link Provider#get()} 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")); return this; } @@ -75,11 +125,12 @@ public class SAXDecoder implements Decoder { } } - /* Implementations are not intended to be shared across requests. */ + /** + * Implementations are not intended to be shared across requests. + */ public interface ContentHandlerWithResult extends ContentHandler { - /* - * expected to be set following a call to {@link - * XMLReader#parse(InputSource)} + /** + * expected to be set following a call to {@link XMLReader#parse(InputSource)} */ T result(); } diff --git a/sax/src/test/java/feign/sax/SAXDecoderTest.java b/sax/src/test/java/feign/sax/SAXDecoderTest.java index 0db53023..b01af77c 100644 --- a/sax/src/test/java/feign/sax/SAXDecoderTest.java +++ b/sax/src/test/java/feign/sax/SAXDecoderTest.java @@ -17,9 +17,8 @@ package feign.sax; import dagger.ObjectGraph; import dagger.Provides; -import feign.codec.Decoder; -import feign.codec.DecodeException; import feign.Response; +import feign.codec.Decoder; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import org.xml.sax.helpers.DefaultHandler; @@ -39,11 +38,10 @@ public class SAXDecoderTest { @dagger.Module(injects = SAXDecoderTest.class) static class Module { - @Provides Decoder saxDecoder(Provider networkStatus, // - Provider networkStatusAsString) { + @Provides Decoder saxDecoder(Provider networkStatus) { return SAXDecoder.builder() // - .addContentHandler(networkStatus) // - .addContentHandler(networkStatusAsString) // + .registerContentHandler(NetworkStatus.class, networkStatus) // + .registerContentHandler(NetworkStatusStringHandler.class) // .build(); } } diff --git a/sax/src/test/java/feign/sax/examples/IAMExample.java b/sax/src/test/java/feign/sax/examples/IAMExample.java index 2bdb583a..e00b7be4 100644 --- a/sax/src/test/java/feign/sax/examples/IAMExample.java +++ b/sax/src/test/java/feign/sax/examples/IAMExample.java @@ -15,21 +15,14 @@ */ package feign.sax.examples; -import dagger.Module; -import dagger.Provides; import feign.Feign; import feign.Request; import feign.RequestLine; import feign.RequestTemplate; import feign.Target; -import feign.codec.Decoder; -import feign.codec.Encoder; import feign.sax.SAXDecoder; import org.xml.sax.helpers.DefaultHandler; -import javax.inject.Inject; -import javax.inject.Provider; - public class IAMExample { interface IAM { @@ -37,7 +30,9 @@ public class IAMExample { } public static void main(String... args) { - IAM iam = Feign.create(new IAMTarget(args[0], args[1]), new DecodeWithSax()); + IAM iam = Feign.builder()// + .decoder(SAXDecoder.builder().registerContentHandler(UserIdHandler.class).build())// + .target(new IAMTarget(args[0], args[1])); System.out.println(iam.userId()); } @@ -65,23 +60,7 @@ public class IAMExample { } } - @Module(addsTo = Feign.Defaults.class) - static class DecodeWithSax { - @Provides Decoder saxDecoder(Provider userIdHandler) { - return SAXDecoder.builder() // - .addContentHandler(userIdHandler) // - .build(); - } - - @Provides Encoder defaultEncoder() { - return new Encoder.Default(); - } - } - - static class UserIdHandler extends DefaultHandler implements - SAXDecoder.ContentHandlerWithResult { - @Inject UserIdHandler() { - } + static class UserIdHandler extends DefaultHandler implements SAXDecoder.ContentHandlerWithResult { private StringBuilder currentText = new StringBuilder();