Skip to content

Commit 9d36d3c

Browse files
committed
apt: integrate with kotlin coroutines
1 parent b19753f commit 9d36d3c

File tree

14 files changed

+120
-54
lines changed

14 files changed

+120
-54
lines changed

jooby/src/main/kotlin/io/jooby/CoroutineRouter.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,12 @@ class CoroutineRouter(val coroutineStart: CoroutineStart, val router: Router) {
6868
val xhandler = CoroutineExceptionHandler { _, x ->
6969
ctx.sendError(x)
7070
}
71-
coroutineScope.launch(CoroutineRouter.ContextCoroutineName + xhandler, coroutineStart) {
71+
coroutineScope.launch(xhandler, coroutineStart) {
7272
val result = handler(HandlerContext(ctx))
7373
if (result != ctx) {
7474
ctx.render(result)
7575
}
7676
}
7777
}.setHandle(handler)
7878
}
79-
80-
companion object {
81-
private val ContextCoroutineName = CoroutineName("ctx")
82-
}
8379
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal.mvc
7+
8+
import io.jooby.Context
9+
import io.jooby.CoroutineRouter
10+
import io.jooby.Route
11+
import kotlinx.coroutines.CoroutineExceptionHandler
12+
import kotlinx.coroutines.launch
13+
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
14+
15+
class CoroutineLauncher(val next: Route.Handler) : Route.Handler {
16+
override fun apply(ctx: Context): Any {
17+
val router = ctx.router.attribute<CoroutineRouter>("coroutineRouter")
18+
val exceptionHandler = CoroutineExceptionHandler { _, x ->
19+
ctx.sendError(x)
20+
}
21+
router.coroutineScope.launch(exceptionHandler, router.coroutineStart) {
22+
val result = suspendCoroutineUninterceptedOrReturn<Any> {
23+
ctx.attribute("___continuation", it)
24+
next.apply(ctx)
25+
}
26+
if (!ctx.isResponseStarted) {
27+
ctx.render(result!!)
28+
}
29+
}
30+
return ctx
31+
}
32+
}

modules/jooby-apt/src/main/java/io/jooby/internal/apt/HandlerCompiler.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import javax.annotation.processing.ProcessingEnvironment;
2727
import javax.inject.Provider;
28+
import javax.lang.model.element.AnnotationMirror;
2829
import javax.lang.model.element.Element;
2930
import javax.lang.model.element.ExecutableElement;
3031
import javax.lang.model.element.TypeElement;
@@ -177,16 +178,36 @@ private void apply(ClassWriter writer) throws Exception {
177178
apply.visitEnd();
178179
}
179180

180-
private void processArguments(ClassWriter classWriter, MethodVisitor visitor) throws Exception {
181-
for (VariableElement var : executable.getParameters()) {
182-
visitor.visitVarInsn(ALOAD, 1);
183-
ParamDefinition param = ParamDefinition.create(environment, var);
184-
ParamWriter writer = param.newWriter();
185-
writer.accept(classWriter, getGeneratedInternalClass(), visitor, param);
181+
public boolean isSuspendFunction() {
182+
List<? extends VariableElement> parameters = executable.getParameters();
183+
if (parameters.isEmpty()) {
184+
return false;
186185
}
186+
VariableElement last = parameters.get(parameters.size() - 1);
187+
return isSuspendFunction(last);
187188
}
188189

190+
private boolean isSuspendFunction(VariableElement parameter) {
191+
String type = ParamDefinition.create(environment, parameter).getType().getRawType().toString();
192+
return type.equals("kotlin.coroutines.Continuation");
193+
}
189194

195+
private void processArguments(ClassWriter classWriter, MethodVisitor visitor) throws Exception {
196+
for (VariableElement var : executable.getParameters()) {
197+
if (isSuspendFunction(var)) {
198+
visitor.visitVarInsn(ALOAD, 1);
199+
visitor.visitMethodInsn(INVOKEINTERFACE, "io/jooby/Context", "getAttributes", "()Ljava/util/Map;", true);
200+
visitor.visitLdcInsn("___continuation");
201+
visitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "remove", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
202+
visitor.visitTypeInsn(CHECKCAST, "kotlin/coroutines/Continuation");
203+
} else {
204+
visitor.visitVarInsn(ALOAD, 1);
205+
ParamDefinition param = ParamDefinition.create(environment, var);
206+
ParamWriter writer = param.newWriter();
207+
writer.accept(classWriter, getGeneratedInternalClass(), visitor, param);
208+
}
209+
}
210+
}
190211

191212
private void processReturnType(MethodVisitor visitor) throws Exception {
192213
TypeKind kind = executable.getReturnType().getKind();

modules/jooby-apt/src/main/java/io/jooby/internal/apt/ModuleCompiler.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,21 @@ private void install(ClassWriter writer, List<HandlerCompiler> handlers) throws
101101
for (HandlerCompiler handler : handlers) {
102102
visitor.visitVarInsn(ALOAD, 1);
103103
visitor.visitLdcInsn(handler.getPattern());
104+
if (handler.isSuspendFunction()) {
105+
visitor.visitTypeInsn(NEW, "io/jooby/internal/mvc/CoroutineLauncher");
106+
visitor.visitInsn(DUP);
107+
}
104108
visitor.visitTypeInsn(NEW, handler.getGeneratedInternalClass());
105109
visitor.visitInsn(DUP);
106110
visitor.visitVarInsn(ALOAD, 0);
107111
visitor.visitFieldInsn(GETFIELD, moduleInternalName, "provider",
108112
"Ljavax/inject/Provider;");
109113
visitor.visitMethodInsn(INVOKESPECIAL, handler.getGeneratedInternalClass(), "<init>",
110114
"(Ljavax/inject/Provider;)V", false);
115+
if (handler.isSuspendFunction()) {
116+
visitor.visitMethodInsn(INVOKESPECIAL, "io/jooby/internal/mvc/CoroutineLauncher", "<init>",
117+
"(Lio/jooby/Route$Handler;)V", false);
118+
}
111119
visitor.visitMethodInsn(INVOKEVIRTUAL, "io/jooby/Jooby", handler.getHttpMethod(),
112120
"(Ljava/lang/String;Lio/jooby/Route$Handler;)Lio/jooby/Route;", false);
113121
visitor.visitVarInsn(ASTORE, 2);
@@ -190,7 +198,9 @@ private Optional<? extends AnnotationMirror> findAnnotation(
190198
private void setReturnType(MethodVisitor visitor, HandlerCompiler handler)
191199
throws NoSuchMethodException {
192200
TypeDefinition returnType = handler.getReturnType();
193-
if (returnType.isVoid()) {
201+
if (handler.isSuspendFunction()) {
202+
visitor.visitLdcInsn(Type.getType("Lkotlin/coroutines/Continuation;"));
203+
} else if (returnType.isVoid()) {
194204
visitor.visitLdcInsn(Type.getType(Context.class));
195205
} else if (returnType.isPrimitive()) {
196206
Method wrapper = Primitives.wrapper(returnType);
Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,33 @@
11
package output;
22

3-
import examples.CoroutineApp;
43
import org.junit.jupiter.api.Test;
54
import org.objectweb.asm.util.ASMifier;
6-
import source.NullRoutes;
75

86
public class ASMPrinter {
97

10-
@Test
11-
public void coroutines() throws Exception {
12-
ASMifier.main(new String[] {CoroutineApp.class.getName() + "$1"});
13-
}
14-
158
@Test
169
public void mvcModule() throws Exception {
17-
ASMifier.main(new String[] {MyMvcModule.class.getName() });
10+
ASMifier.main(new String[]{MyMvcModule.class.getName()});
1811
}
1912

2013
@Test
2114
public void mvcExtension() throws Exception {
22-
ASMifier.main(new String[] {MvcExtension.class.getName() });
15+
ASMifier.main(new String[]{MvcExtension.class.getName()});
2316
}
2417

2518
@Test
2619
public void mvcDispatch() throws Exception {
27-
ASMifier.main(new String[] {MvcDispatch.class.getName() });
20+
ASMifier.main(new String[]{MvcDispatch.class.getName()});
2821
}
2922

3023
@Test
3124
public void myController() throws Exception {
32-
ASMifier.main(new String[] {MyControllerHandler.class.getName() });
25+
ASMifier.main(new String[]{MyControllerHandler.class.getName()});
3326
}
3427

3528
@Test
3629
public void nullRoutes() throws Exception {
37-
ASMifier.main(new String[] {NullRoutes.class.getName() });
30+
ASMifier.main(new String[]{"source.SuspendRoute"});
3831
}
3932
}
4033

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package output;
22

3+
import io.jooby.Context;
34
import io.jooby.Extension;
45
import io.jooby.Jooby;
56
import io.jooby.Route;
7+
import io.jooby.internal.mvc.CoroutineLauncher;
68

79
import javax.annotation.Nonnull;
810
import javax.inject.Provider;
911

1012
public class MvcExtension implements Extension {
11-
public MvcExtension(Provider c) {
13+
private Provider provider;
1214

15+
public MvcExtension(Provider c) {
16+
this.provider = c;
1317
}
1418

1519
@Override public void install(@Nonnull Jooby application) throws Exception {
16-
Route route = application.get("/", ctx -> "..");
17-
route.setExecutorKey("myexecutor");
20+
Route route = application.get("/", new CoroutineLauncher(new MyControllerHandler(provider)));
21+
route.setReturnType(Context.class);
1822
}
1923
}

modules/jooby-apt/src/test/java/output/MyControllerHandler.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@
33
import io.jooby.Context;
44
import io.jooby.Route;
55
import io.jooby.Value;
6+
import kotlin.coroutines.Continuation;
7+
import source.SuspendRoute;
68

79
import javax.annotation.Nonnull;
810
import javax.inject.Provider;
911

1012
public class MyControllerHandler implements Route.Handler {
1113

12-
private Provider<MyController> provider;
14+
private Provider<SuspendRoute> provider;
1315

14-
public MyControllerHandler(Provider<MyController> provider) {
16+
public MyControllerHandler(Provider<SuspendRoute> provider) {
1517
this.provider = provider;
1618
}
1719

1820
@Nonnull @Override public Object apply(@Nonnull Context ctx) throws Exception {
19-
return provider.get().doIt(x(ctx.query("x")));
21+
return provider.get().suspendFun((Continuation) ctx.getAttributes().remove("__continuation"));
2022
}
2123

2224
private static Integer x(Value x) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package source;
2+
3+
import io.jooby.annotations.GET;
4+
import io.jooby.annotations.Path;
5+
import kotlin.coroutines.Continuation;
6+
7+
@Path("/suspend")
8+
public class SuspendRoute {
9+
10+
@GET
11+
public Continuation suspendFun(Continuation continuation) {
12+
return continuation;
13+
}
14+
}

modules/jooby-apt/src/test/java/tests/ModuleCompilerTest.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package tests;
22

33
import io.jooby.Context;
4+
import io.jooby.CoroutineRouter;
5+
import io.jooby.MockContext;
46
import io.jooby.MockRouter;
57
import io.jooby.Route;
68
import io.jooby.StatusCode;
79
import io.jooby.apt.MvcModuleCompilerRunner;
10+
import kotlin.coroutines.Continuation;
11+
import kotlinx.coroutines.GlobalScope;
812
import org.junit.jupiter.api.Test;
913
import source.GetPostRoute;
1014
import source.JavaBeanParam;
@@ -14,6 +18,7 @@
1418
import source.RouteDispatch;
1519
import source.RouteWithMimeTypes;
1620
import source.Routes;
21+
import source.SuspendRoute;
1722
import source.VoidRoute;
1823

1924
import java.util.Arrays;
@@ -23,6 +28,7 @@
2328
import static org.junit.jupiter.api.Assertions.assertEquals;
2429
import static org.junit.jupiter.api.Assertions.assertNotNull;
2530
import static org.junit.jupiter.api.Assertions.assertTrue;
31+
import static org.mockito.Mockito.mock;
2632

2733
public class ModuleCompilerTest {
2834
@Test
@@ -147,7 +153,7 @@ public void routeAttributes() throws Exception {
147153
@Test
148154
public void routeDispatch() throws Exception {
149155
new MvcModuleCompilerRunner(new RouteDispatch())
150-
.module(true, app -> {
156+
.module(app -> {
151157
assertEquals("worker", app.getRoutes().get(0).getExecutorKey());
152158
assertEquals("single", app.getRoutes().get(1).getExecutorKey());
153159
})

modules/jooby-apt/src/test/kotlin/examples/CoroutineApp.kt

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)