Browse Source

support Feign.builder() w/ SAX decoder

pull/70/head
adriancole 11 years ago
parent
commit
b389ef03ba
  1. 9
      README.md
  2. 20
      sax/README.md
  3. 79
      sax/src/main/java/feign/sax/SAXDecoder.java
  4. 10
      sax/src/test/java/feign/sax/SAXDecoderTest.java
  5. 29
      sax/src/test/java/feign/sax/examples/IAMExample.java

9
README.md

@ -94,6 +94,15 @@ GitHub github = Feign.create(GitHub.class, "https://api.github.com", new GsonMod
### Sax ### 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. [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 ### 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. [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.

20
sax/README.md

@ -6,19 +6,9 @@ This module adds support for decoding xml via SAX.
Add this to your object graph like so: Add this to your object graph like so:
```java ```java
IAM iam = Feign.create(IAM.class, "https://iam.amazonaws.com", new DecodeWithSax()); api = Feign.builder()
.decoder(SAXDecoder.builder()
--snip-- .registerContentHandler(UserIdHandler.class)
@Module(addsTo = Feign.Defaults.class) .build())
static class DecodeWithSax { .target(Api.class, "https://apihost");
@Provides Decoder saxDecoder(Provider<UserIdHandler> userIdHandler) {
return SAXDecoder.builder() //
.addContentHandler(userIdHandler) //
.build();
}
@Provides Encoder defaultEncoder() {
return new Encoder.Default();
}
}
``` ```

79
sax/src/main/java/feign/sax/SAXDecoder.java

@ -16,8 +16,8 @@
package feign.sax; package feign.sax;
import feign.Response; import feign.Response;
import feign.codec.Decoder;
import feign.codec.DecodeException; import feign.codec.DecodeException;
import feign.codec.Decoder;
import org.xml.sax.ContentHandler; import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@ -27,6 +27,7 @@ import org.xml.sax.helpers.XMLReaderFactory;
import javax.inject.Provider; import javax.inject.Provider;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -37,17 +38,28 @@ import static feign.Util.ensureClosed;
import static feign.Util.resolveLastTypeParameter; import static feign.Util.resolveLastTypeParameter;
/** /**
* Decodes responses using SAX. Configure using the {@link SAXDecoder.Builder * Decodes responses using SAX, which is supported both in normal JVM environments, as well Android.
* builder}. * <br>
* <h4>Basic example with with Feign.Builder</h4>
* <br>
* <pre>
* api = Feign.builder()
* .decoder(SAXDecoder.builder()
* .registerContentHandler(ContentHandlerForFoo.class)
* .registerContentHandler(ContentHandlerForBar.class)
* .build())
* .target(MyApi.class, "http://api");
* </pre>
* <p/> * <p/>
* * <h4>Advanced example with Dagger</h4>
* <br>
* <pre> * <pre>
* &#064;Provides * &#064;Provides
* Decoder saxDecoder(Provider&lt;ContentHandlerForFoo&gt; foo, // * Decoder saxDecoder(Provider&lt;ContentHandlerForFoo&gt; foo, //
* Provider&lt;ContentHandlerForBar&gt; bar) { * Provider&lt;ContentHandlerForBar&gt; bar) {
* return SAXDecoder.builder() // * return SAXDecoder.builder() //
* .addContentHandler(foo) // * .registerContentHandler(Foo.class, foo) //
* .addContentHandler(bar) // * .registerContentHandler(Bar.class, bar) //
* .build(); * .build();
* } * }
* </pre> * </pre>
@ -63,10 +75,48 @@ public class SAXDecoder implements Decoder {
private final Map<Type, Provider<? extends ContentHandlerWithResult<?>>> handlerProviders = private final Map<Type, Provider<? extends ContentHandlerWithResult<?>>> handlerProviders =
new LinkedHashMap<Type, Provider<? extends ContentHandlerWithResult<?>>>(); new LinkedHashMap<Type, Provider<? extends ContentHandlerWithResult<?>>>();
public Builder addContentHandler(Provider<? extends ContentHandlerWithResult<?>> handler) { /**
Type type = resolveLastTypeParameter(checkNotNull(handler, "handler").getClass(), Provider.class); * Will call {@link Constructor#newInstance(Object...)} on {@code handlerClass} for each content stream.
type = resolveLastTypeParameter(type, ContentHandlerWithResult.class); * <p/>
this.handlerProviders.put(type, handler); * <h3>Note</h3>
* <br/>
* 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 <T extends ContentHandlerWithResult<?>> Builder registerContentHandler(Class<T> handlerClass) {
Type type = resolveLastTypeParameter(checkNotNull(handlerClass, "handlerClass"), ContentHandlerWithResult.class);
return registerContentHandler(type, new NewInstanceProvider(handlerClass));
}
private static class NewInstanceProvider<T extends ContentHandlerWithResult<?>> implements Provider<T> {
private final Constructor<T> ctor;
private NewInstanceProvider(Class<T> 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<? extends ContentHandlerWithResult<?>> handler) {
this.handlerProviders.put(checkNotNull(type, "type"), checkNotNull(handler, "handler"));
return this; 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<T> extends ContentHandler { public interface ContentHandlerWithResult<T> extends ContentHandler {
/* /**
* expected to be set following a call to {@link * expected to be set following a call to {@link XMLReader#parse(InputSource)}
* XMLReader#parse(InputSource)}
*/ */
T result(); T result();
} }

10
sax/src/test/java/feign/sax/SAXDecoderTest.java

@ -17,9 +17,8 @@ package feign.sax;
import dagger.ObjectGraph; import dagger.ObjectGraph;
import dagger.Provides; import dagger.Provides;
import feign.codec.Decoder;
import feign.codec.DecodeException;
import feign.Response; import feign.Response;
import feign.codec.Decoder;
import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.DefaultHandler;
@ -39,11 +38,10 @@ public class SAXDecoderTest {
@dagger.Module(injects = SAXDecoderTest.class) @dagger.Module(injects = SAXDecoderTest.class)
static class Module { static class Module {
@Provides Decoder saxDecoder(Provider<NetworkStatusHandler> networkStatus, // @Provides Decoder saxDecoder(Provider<NetworkStatusHandler> networkStatus) {
Provider<NetworkStatusStringHandler> networkStatusAsString) {
return SAXDecoder.builder() // return SAXDecoder.builder() //
.addContentHandler(networkStatus) // .registerContentHandler(NetworkStatus.class, networkStatus) //
.addContentHandler(networkStatusAsString) // .registerContentHandler(NetworkStatusStringHandler.class) //
.build(); .build();
} }
} }

29
sax/src/test/java/feign/sax/examples/IAMExample.java

@ -15,21 +15,14 @@
*/ */
package feign.sax.examples; package feign.sax.examples;
import dagger.Module;
import dagger.Provides;
import feign.Feign; import feign.Feign;
import feign.Request; import feign.Request;
import feign.RequestLine; import feign.RequestLine;
import feign.RequestTemplate; import feign.RequestTemplate;
import feign.Target; import feign.Target;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.sax.SAXDecoder; import feign.sax.SAXDecoder;
import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.DefaultHandler;
import javax.inject.Inject;
import javax.inject.Provider;
public class IAMExample { public class IAMExample {
interface IAM { interface IAM {
@ -37,7 +30,9 @@ public class IAMExample {
} }
public static void main(String... args) { 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()); System.out.println(iam.userId());
} }
@ -65,23 +60,7 @@ public class IAMExample {
} }
} }
@Module(addsTo = Feign.Defaults.class) static class UserIdHandler extends DefaultHandler implements SAXDecoder.ContentHandlerWithResult<Long> {
static class DecodeWithSax {
@Provides Decoder saxDecoder(Provider<UserIdHandler> userIdHandler) {
return SAXDecoder.builder() //
.addContentHandler(userIdHandler) //
.build();
}
@Provides Encoder defaultEncoder() {
return new Encoder.Default();
}
}
static class UserIdHandler extends DefaultHandler implements
SAXDecoder.ContentHandlerWithResult<Long> {
@Inject UserIdHandler() {
}
private StringBuilder currentText = new StringBuilder(); private StringBuilder currentText = new StringBuilder();

Loading…
Cancel
Save