Skip to content

Commit 49818f1

Browse files
wplong11velo
andauthored
Support kotlin coroutines (OpenFeign#1706)
* Support kotlin coroutines Resolves: OpenFeign#1565 Inspired by PlaytikaOSS/feign-reactive#486 ## TODO - [ ] Separate Kotlin support module - [ ] Enhance test case - [ ] Refactoring - [ ] Clean up pom.xml * Apply optional dependency to kotlin support related dependency * Seperate Kotlin support module * Remove unused code from ClassUtils.java * Remove unused code from ClassUtils.java * Refactor KotlinDetector * Move ClassUtils location into KotlinDetector * Move KotlinDetector location * Format code * First attempt to move kotlin work to it's own isolated module * Coroutine Feign using AyncFeign * Coroutine Feign using AyncFeign * Refactor suspending function detect logic - Remove KotlinDetector.java - Add Method.isSuspend extension function * Cleanup CoroutineFeignTest test code format * Fix suspend function contract parsing error when using http body * Rename test names to be meaningful * Add Github Example With Coroutine - Copy of GithubExample * Remove unnecessary dependency https://github.com/OpenFeign/feign/pull/1706/files#r965389041 Co-authored-by: Marvin Froeder <velo.br@gmail.com> Co-authored-by: Marvin Froeder <velo@users.noreply.github.com>
1 parent bc49d92 commit 49818f1

18 files changed

Lines changed: 1200 additions & 20 deletions

File tree

‎core/pom.xml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
<dependency>
5555
<groupId>com.fasterxml.jackson.core</groupId>
5656
<artifactId>jackson-databind</artifactId>
57-
<version>${jackson.version}</version>
5857
<scope>test</scope>
5958
</dependency>
6059

‎core/src/main/java/feign/AsyncInvocation.java‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,45 +18,45 @@
1818

1919
/** A specific invocation of an APU */
2020
@Experimental
21-
class AsyncInvocation<C> {
21+
public class AsyncInvocation<C> {
2222

2323
private final C context;
2424
private final MethodInfo methodInfo;
2525
private final long startNanos;
2626
private CompletableFuture<Response> responseFuture;
2727

28-
AsyncInvocation(C context, MethodInfo methodInfo) {
28+
public AsyncInvocation(C context, MethodInfo methodInfo) {
2929
super();
3030
this.context = context;
3131
this.methodInfo = methodInfo;
3232
this.startNanos = System.nanoTime();
3333
}
3434

35-
C context() {
35+
public C context() {
3636
return context;
3737
}
3838

39-
String configKey() {
39+
public String configKey() {
4040
return methodInfo.configKey();
4141
}
4242

43-
long startNanos() {
43+
public long startNanos() {
4444
return startNanos;
4545
}
4646

47-
Type underlyingType() {
47+
public Type underlyingType() {
4848
return methodInfo.underlyingReturnType();
4949
}
5050

51-
boolean isAsyncReturnType() {
51+
public boolean isAsyncReturnType() {
5252
return methodInfo.isAsyncReturnType();
5353
}
5454

55-
void setResponseFuture(CompletableFuture<Response> responseFuture) {
55+
public void setResponseFuture(CompletableFuture<Response> responseFuture) {
5656
this.responseFuture = responseFuture;
5757
}
5858

59-
CompletableFuture<Response> responseFuture() {
59+
public CompletableFuture<Response> responseFuture() {
6060
return responseFuture;
6161
}
6262
}

‎core/src/main/java/feign/AsyncResponseHandler.java‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
* handling
2929
*/
3030
@Experimental
31-
class AsyncResponseHandler {
31+
public class AsyncResponseHandler {
3232

3333
private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L;
3434

@@ -42,7 +42,7 @@ class AsyncResponseHandler {
4242

4343
private final ResponseInterceptor responseInterceptor;
4444

45-
AsyncResponseHandler(
45+
public AsyncResponseHandler(
4646
Level logLevel,
4747
Logger logger,
4848
Decoder decoder,
@@ -61,10 +61,12 @@ class AsyncResponseHandler {
6161
}
6262

6363
boolean isVoidType(Type returnType) {
64-
return Void.class == returnType || void.class == returnType;
64+
return Void.class == returnType
65+
|| void.class == returnType
66+
|| returnType.getTypeName().equals("kotlin.Unit");
6567
}
6668

67-
void handleResponse(
69+
public void handleResponse(
6870
CompletableFuture<Object> resultFuture,
6971
String configKey,
7072
Response response,

‎core/src/main/java/feign/Contract.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method me
131131
data.ignoreParamater(i);
132132
}
133133

134+
if ("kotlin.coroutines.Continuation".equals(parameterTypes[i].getName())) {
135+
data.ignoreParamater(i);
136+
}
137+
134138
if (parameterTypes[i] == URI.class) {
135139
data.urlIndex(i);
136140
} else if (!isHttpAnnotation

‎core/src/main/java/feign/Feign.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public Builder addCapability(Capability capability) {
189189
}
190190

191191
/** Internal - used to indicate that the decoder should be immediately called */
192-
Builder forceDecoding() {
192+
public /* FIXME should not be public */ Builder forceDecoding() {
193193
this.forceDecoding = true;
194194
return this;
195195
}

‎core/src/main/java/feign/Logger.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
*/
1414
package feign;
1515

16-
import static feign.Util.*;
16+
import static feign.Util.UTF_8;
17+
import static feign.Util.decodeOrDefault;
18+
import static feign.Util.valuesOrEmpty;
1719
import static java.util.Objects.nonNull;
1820

1921
import java.io.IOException;
@@ -131,7 +133,7 @@ protected Response logAndRebufferResponse(
131133
return response;
132134
}
133135

134-
protected IOException logIOException(
136+
public IOException logIOException(
135137
String configKey, Level logLevel, IOException ioe, long elapsedTime) {
136138
log(
137139
configKey,

‎core/src/main/java/feign/MethodInfo.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
import java.util.concurrent.CompletableFuture;
2020

2121
@Experimental
22-
class MethodInfo {
22+
public class MethodInfo {
2323
private final String configKey;
2424
private final Type underlyingReturnType;
2525
private final boolean asyncReturnType;
2626

27-
MethodInfo(String configKey, Type underlyingReturnType, boolean asyncReturnType) {
27+
protected MethodInfo(String configKey, Type underlyingReturnType, boolean asyncReturnType) {
2828
this.configKey = configKey;
2929
this.underlyingReturnType = underlyingReturnType;
3030
this.asyncReturnType = asyncReturnType;

‎core/src/main/java/feign/Types.java‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ static Type getSupertype(Type context, Class<?> contextRawType, Class<?> superty
200200
context, contextRawType, getGenericSupertype(context, contextRawType, supertype));
201201
}
202202

203-
static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
203+
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
204204
// This implementation is made a little more complicated in an attempt to avoid object-creation.
205205
while (true) {
206206
if (toResolve instanceof TypeVariable) {
@@ -350,14 +350,17 @@ static final class ParameterizedTypeImpl implements ParameterizedType {
350350
}
351351
}
352352

353+
@Override
353354
public Type[] getActualTypeArguments() {
354355
return typeArguments.clone();
355356
}
356357

358+
@Override
357359
public Type getRawType() {
358360
return rawType;
359361
}
360362

363+
@Override
361364
public Type getOwnerType() {
362365
return ownerType;
363366
}
@@ -395,6 +398,7 @@ private static final class GenericArrayTypeImpl implements GenericArrayType {
395398
this.componentType = componentType;
396399
}
397400

401+
@Override
398402
public Type getGenericComponentType() {
399403
return componentType;
400404
}
@@ -453,10 +457,12 @@ static final class WildcardTypeImpl implements WildcardType {
453457
}
454458
}
455459

460+
@Override
456461
public Type[] getUpperBounds() {
457462
return new Type[] {upperBound};
458463
}
459464

465+
@Override
460466
public Type[] getLowerBounds() {
461467
return lowerBound != null ? new Type[] {lowerBound} : EMPTY_TYPE_ARRAY;
462468
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
GitHub Example With Coroutine
2+
===================
3+
4+
This is an example of a simple json client.
5+
6+
=== Building example with Gradle
7+
Install and run `gradle` to produce `build/github`
8+
9+
=== Building example with Maven
10+
Install and run `mvn` to produce `target/github`
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright 2012-2022 The Feign Authors
5+
6+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7+
in compliance with the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software distributed under the License
12+
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13+
or implied. See the License for the specific language governing permissions and limitations under
14+
the License.
15+
16+
-->
17+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
18+
<modelVersion>4.0.0</modelVersion>
19+
20+
<parent>
21+
<groupId>io.github.openfeign</groupId>
22+
<artifactId>parent</artifactId>
23+
<version>11.10-SNAPSHOT</version>
24+
</parent>
25+
26+
<artifactId>feign-example-github-with-coroutine</artifactId>
27+
<packaging>jar</packaging>
28+
<name>GitHub Example With Coroutine</name>
29+
30+
<properties>
31+
<main.basedir>${project.basedir}/..</main.basedir>
32+
</properties>
33+
34+
<dependencies>
35+
<dependency>
36+
<groupId>io.github.openfeign</groupId>
37+
<artifactId>feign-core</artifactId>
38+
</dependency>
39+
<dependency>
40+
<groupId>io.github.openfeign</groupId>
41+
<artifactId>feign-kotlin</artifactId>
42+
</dependency>
43+
<dependency>
44+
<groupId>io.github.openfeign</groupId>
45+
<artifactId>feign-gson</artifactId>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.apache.commons</groupId>
49+
<artifactId>commons-exec</artifactId>
50+
<version>1.3</version>
51+
<scope>test</scope>
52+
</dependency>
53+
</dependencies>
54+
55+
<build>
56+
<defaultGoal>package</defaultGoal>
57+
<plugins>
58+
<plugin>
59+
<groupId>org.apache.maven.plugins</groupId>
60+
<artifactId>maven-shade-plugin</artifactId>
61+
<version>3.3.0</version>
62+
<executions>
63+
<execution>
64+
<phase>package</phase>
65+
<goals>
66+
<goal>shade</goal>
67+
</goals>
68+
<configuration>
69+
<transformers>
70+
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
71+
<mainClass>example.github.GitHubExample</mainClass>
72+
</transformer>
73+
</transformers>
74+
<createDependencyReducedPom>false</createDependencyReducedPom>
75+
</configuration>
76+
</execution>
77+
</executions>
78+
</plugin>
79+
<plugin>
80+
<groupId>org.skife.maven</groupId>
81+
<artifactId>really-executable-jar-maven-plugin</artifactId>
82+
<version>1.5.0</version>
83+
<configuration>
84+
<programFile>github</programFile>
85+
</configuration>
86+
<executions>
87+
<execution>
88+
<phase>package</phase>
89+
<goals>
90+
<goal>really-executable-jar</goal>
91+
</goals>
92+
</execution>
93+
</executions>
94+
</plugin>
95+
<plugin>
96+
<groupId>org.apache.maven.plugins</groupId>
97+
<artifactId>maven-failsafe-plugin</artifactId>
98+
<version>${maven-surefire-plugin.version}</version>
99+
<configuration>
100+
<!--
101+
Travis does not inject GITHUB_TOKEN when building PRs, which will make module fail
102+
https://docs.travis-ci.com/user/environment-variables/#defining-encrypted-variables-in-travisyml
103+
104+
ignoring test errors for any build that is not master
105+
-->
106+
<testFailureIgnore>true</testFailureIgnore>
107+
</configuration>
108+
<executions>
109+
<execution>
110+
<goals>
111+
<goal>integration-test</goal>
112+
<goal>verify</goal>
113+
</goals>
114+
</execution>
115+
</executions>
116+
</plugin>
117+
</plugins>
118+
</build>
119+
120+
<profiles>
121+
<profile>
122+
<activation>
123+
<property>
124+
<name>env.TRAVIS_PULL_REQUEST</name>
125+
<value>false</value>
126+
</property>
127+
</activation>
128+
<build>
129+
<plugins>
130+
<plugin>
131+
<groupId>org.apache.maven.plugins</groupId>
132+
<artifactId>maven-failsafe-plugin</artifactId>
133+
<configuration>
134+
<testFailureIgnore>false</testFailureIgnore>
135+
</configuration>
136+
</plugin>
137+
<plugin>
138+
<groupId>org.jetbrains.kotlin</groupId>
139+
<artifactId>kotlin-maven-plugin</artifactId>
140+
<version>${kotlin.version}</version>
141+
<executions>
142+
<execution>
143+
<id>compile</id>
144+
<goals>
145+
<goal>compile</goal>
146+
</goals>
147+
<configuration>
148+
<sourceDirs>
149+
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
150+
</sourceDirs>
151+
</configuration>
152+
</execution>
153+
<execution>
154+
<id>test-compile</id>
155+
<goals>
156+
<goal>test-compile</goal>
157+
</goals>
158+
<configuration>
159+
<sourceDirs>
160+
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
161+
</sourceDirs>
162+
</configuration>
163+
</execution>
164+
</executions>
165+
</plugin>
166+
</plugins>
167+
</build>
168+
</profile>
169+
</profiles>
170+
</project>

0 commit comments

Comments
 (0)