Skip to content

Commit 0d7a69b

Browse files
author
adriancole
committed
added IncrementalCallback example code and updated changelog
1 parent 309d4b3 commit 0d7a69b

File tree

3 files changed

+136
-32
lines changed

3 files changed

+136
-32
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
### Version 3.0
2+
* Added support for asynchronous callbacks via `IncrementalCallback<T>` and `IncrementalDecoder.TextStream<T>`.
23
* Wire is now Logger, with configurable Logger.Level.
34
* changed codec to be similar to [WebSocket JSR 356](http://docs.oracle.com/javaee/7/api/javax/websocket/package-summary.html)
45
* Decoder is now `Decoder.TextStream<T>`

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,59 @@ The generic parameter of `Decoder.TextStream<T>` designates which The type param
6868
return new SAXDecoder<ZoneList>(handlers){};
6969
}
7070
```
71+
### Asynchronous Incremental Callbacks
72+
If specified as the last argument of a method `IncrementalCallback<T>` fires a background task to add new elements to the callback as they are decoded. Think of `IncrementalCallback<T>` as an asynchronous equivalent to a lazy sequence.
73+
74+
Here's how one looks:
75+
```java
76+
IncrementalCallback<Contributor> printlnObserver = new IncrementalCallback<Contributor>() {
77+
78+
public int count;
79+
80+
@Override public void onNext(Contributor element) {
81+
count++;
82+
}
83+
84+
@Override public void onSuccess() {
85+
System.out.println("found " + count + " contributors");
86+
}
87+
88+
@Override public void onFailure(Throwable cause) {
89+
cause.printStackTrace();
90+
}
91+
};
92+
github.contributors("netflix", "feign", printlnObserver);
93+
```
94+
#### Incremental Decoding
95+
When using an `IncrementalCallback<T>`, you'll need to configure an `IncrementalDecoderi.TextStream<T>` or a general one for all types (`IncrementalDecoder.TextStream<Object>`).
96+
97+
Here's how to wire in a reflective incremental json decoder:
98+
```java
99+
@Provides(type = SET) IncrementalDecoder incrementalDecoder(final Gson gson) {
100+
return new IncrementalDecoder.TextStream<Object>() {
101+
102+
@Override
103+
public void decode(Reader reader, Type type, IncrementalCallback<? super Object> incrementalCallback) throws IOException {
104+
JsonReader jsonReader = new JsonReader(reader);
105+
jsonReader.beginArray();
106+
while (jsonReader.hasNext()) {
107+
try {
108+
incrementalCallback.onNext(gson.fromJson(jsonReader, type));
109+
} catch (JsonIOException e) {
110+
if (e.getCause() != null && e.getCause() instanceof IOException) {
111+
throw IOException.class.cast(e.getCause());
112+
}
113+
throw e;
114+
}
115+
}
116+
jsonReader.endArray();
117+
}
118+
};
119+
}
120+
```
121+
122+
123+
71124
### Multiple Interfaces
72125
Feign can produce multiple api interfaces. These are defined as `Target<T>` (default `HardCodedTarget<T>`), which allow for dynamic discovery and decoration of requests prior to execution.
73126

feign-core/src/test/java/feign/examples/GitHubExample.java

Lines changed: 82 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,26 @@
1515
*/
1616
package feign.examples;
1717

18-
import com.fasterxml.jackson.databind.ObjectMapper;
1918
import com.google.gson.Gson;
2019
import com.google.gson.JsonIOException;
20+
import com.google.gson.stream.JsonReader;
2121
import dagger.Module;
2222
import dagger.Provides;
2323
import feign.Feign;
24+
import feign.IncrementalCallback;
2425
import feign.RequestLine;
2526
import feign.codec.Decoder;
27+
import feign.codec.IncrementalDecoder;
2628

29+
import javax.inject.Inject;
2730
import javax.inject.Named;
31+
import javax.inject.Singleton;
2832
import java.io.IOException;
2933
import java.io.Reader;
3034
import java.lang.reflect.Type;
3135
import java.util.List;
36+
import java.util.concurrent.CountDownLatch;
3237

33-
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
34-
import static com.fasterxml.jackson.annotation.PropertyAccessor.FIELD;
35-
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
3638
import static dagger.Provides.Type.SET;
3739

3840
/**
@@ -43,59 +45,107 @@ public class GitHubExample {
4345
interface GitHub {
4446
@RequestLine("GET /repos/{owner}/{repo}/contributors")
4547
List<Contributor> contributors(@Named("owner") String owner, @Named("repo") String repo);
48+
49+
@RequestLine("GET /repos/{owner}/{repo}/contributors")
50+
void contributors(@Named("owner") String owner, @Named("repo") String repo,
51+
IncrementalCallback<Contributor> contributors);
4652
}
4753

4854
static class Contributor {
4955
String login;
5056
int contributions;
5157
}
5258

53-
public static void main(String... args) {
59+
public static void main(String... args) throws InterruptedException {
5460
GitHub github = Feign.create(GitHub.class, "https://api.github.com", new GsonModule());
5561

56-
// Fetch and print a list of the contributors to this library.
62+
System.out.println("Let's fetch and print a list of the contributors to this library.");
5763
List<Contributor> contributors = github.contributors("netflix", "feign");
5864
for (Contributor contributor : contributors) {
5965
System.out.println(contributor.login + " (" + contributor.contributions + ")");
6066
}
67+
68+
final CountDownLatch latch = new CountDownLatch(1);
69+
70+
System.out.println("Now, let's do it as an incremental async task.");
71+
IncrementalCallback<Contributor> task = new IncrementalCallback<Contributor>() {
72+
73+
public int count;
74+
75+
// parsed directly from the text stream without an intermediate collection.
76+
@Override public void onNext(Contributor contributor) {
77+
System.out.println(contributor.login + " (" + contributor.contributions + ")");
78+
count++;
79+
}
80+
81+
@Override public void onSuccess() {
82+
System.out.println("found " + count + " contributors");
83+
latch.countDown();
84+
}
85+
86+
@Override public void onFailure(Throwable cause) {
87+
cause.printStackTrace();
88+
latch.countDown();
89+
}
90+
};
91+
92+
// fire a task in the background.
93+
github.contributors("netflix", "feign", task);
94+
95+
// wait for the task to complete.
96+
latch.await();
97+
98+
System.exit(0);
6199
}
62100

63101
/**
64102
* Here's how to wire gson deserialization.
65103
*/
66104
@Module(overrides = true, library = true)
67105
static class GsonModule {
68-
@Provides(type = SET) Decoder decoder() {
69-
return new Decoder.TextStream<Object>() {
70-
Gson gson = new Gson();
71-
72-
@Override public Object decode(Reader reader, Type type) throws IOException {
73-
try {
74-
return gson.fromJson(reader, type);
75-
} catch (JsonIOException e) {
76-
if (e.getCause() != null && e.getCause() instanceof IOException) {
77-
throw IOException.class.cast(e.getCause());
78-
}
79-
throw e;
80-
}
81-
}
82-
};
106+
@Provides @Singleton Gson gson() {
107+
return new Gson();
108+
}
109+
110+
@Provides(type = SET) Decoder decoder(GsonDecoder gsonDecoder) {
111+
return gsonDecoder;
112+
}
113+
114+
@Provides(type = SET) IncrementalDecoder incrementalDecoder(GsonDecoder gsonDecoder) {
115+
return gsonDecoder;
83116
}
84117
}
85118

86-
/**
87-
* Here's how to wire jackson deserialization.
88-
*/
89-
@Module(overrides = true, library = true)
90-
static class JacksonModule {
91-
@Provides(type = SET) Decoder decoder() {
92-
return new Decoder.TextStream<Object>() {
93-
ObjectMapper mapper = new ObjectMapper().disable(FAIL_ON_UNKNOWN_PROPERTIES).setVisibility(FIELD, ANY);
119+
static class GsonDecoder implements Decoder.TextStream<Object>, IncrementalDecoder.TextStream<Object> {
120+
private final Gson gson;
121+
122+
@Inject GsonDecoder(Gson gson) {
123+
this.gson = gson;
124+
}
125+
126+
@Override public Object decode(Reader reader, Type type) throws IOException {
127+
return fromJson(new JsonReader(reader), type);
128+
}
129+
130+
@Override
131+
public void decode(Reader reader, Type type, IncrementalCallback<? super Object> incrementalCallback) throws IOException {
132+
JsonReader jsonReader = new JsonReader(reader);
133+
jsonReader.beginArray();
134+
while (jsonReader.hasNext()) {
135+
incrementalCallback.onNext(fromJson(jsonReader, type));
136+
}
137+
jsonReader.endArray();
138+
}
94139

95-
@Override public Object decode(Reader reader, final Type type) throws IOException {
96-
return mapper.readValue(reader, mapper.constructType(type));
140+
private Object fromJson(JsonReader jsonReader, Type type) throws IOException {
141+
try {
142+
return gson.fromJson(jsonReader, type);
143+
} catch (JsonIOException e) {
144+
if (e.getCause() != null && e.getCause() instanceof IOException) {
145+
throw IOException.class.cast(e.getCause());
97146
}
98-
};
147+
throw e;
148+
}
99149
}
100150
}
101151
}

0 commit comments

Comments
 (0)