Skip to content

Commit b19753f

Browse files
committed
apt: implement dispatch annotation
1 parent b82cde4 commit b19753f

8 files changed

Lines changed: 144 additions & 16 deletions

File tree

jooby/src/main/java/io/jooby/Route.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.ArrayList;
2222
import java.util.Arrays;
2323
import java.util.TreeMap;
24+
import java.util.concurrent.Executor;
2425

2526
/**
2627
* Route contains information about the HTTP method, path pattern, which content types consumes and
@@ -340,6 +341,8 @@ public interface Handler extends Serializable {
340341

341342
private Set<String> supportedMethod;
342343

344+
private String executorKey;
345+
343346
/**
344347
* Creates a new route.
345348
*
@@ -764,6 +767,34 @@ public boolean isHttpHead() {
764767
return this;
765768
}
766769

770+
/**
771+
* Specify the name of the executor where the route is going to run.
772+
* Default is <code>null</code>.
773+
*
774+
* @return Executor key.
775+
*/
776+
public @Nullable String getExecutorKey() {
777+
return executorKey;
778+
}
779+
780+
/**
781+
* Set executor key. The route is going to use the given key to fetch an executor. Possible values
782+
* are:
783+
*
784+
* - <code>null</code>: no specific executor, uses the default Jooby logic to choose one, based
785+
* on the value of {@link ExecutionMode};
786+
* - <code>worker</code>: use the executor provided by the server.
787+
* - <code>arbitrary name</code>: use an named executor which as registered using
788+
* {@link Router#executor(String, Executor)}.
789+
*
790+
* @param executorKey Executor key.
791+
* @return This route.
792+
*/
793+
public @Nonnull Route setExecutorKey(@Nullable String executorKey) {
794+
this.executorKey = executorKey;
795+
return this;
796+
}
797+
767798
@Override public String toString() {
768799
return method + " " + pattern;
769800
}

jooby/src/main/java/io/jooby/annotations/Dispatch.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@
3636
*
3737
* @return Name of the executor to use or blank to use the server worker executor.
3838
*/
39-
String value() default "";
39+
String value() default "worker";
4040
}

jooby/src/main/java/io/jooby/internal/RouterImpl.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package io.jooby.internal;
77

88
import io.jooby.Context;
9-
import io.jooby.HeadHandler;
109
import io.jooby.RegistryException;
1110
import io.jooby.ServiceKey;
1211
import io.jooby.StatusCodeException;
@@ -38,7 +37,6 @@
3837
import javax.annotation.Nonnull;
3938
import javax.inject.Provider;
4039
import java.io.FileNotFoundException;
41-
import java.lang.annotation.Annotation;
4240
import java.lang.reflect.Method;
4341
import java.lang.reflect.Modifier;
4442
import java.nio.file.Path;
@@ -373,9 +371,11 @@ private Route defineRoute(@Nonnull String method, @Nonnull String pattern,
373371
handler.setRoute(route);
374372

375373
Stack stack = this.stack.peekLast();
374+
String executorKey = route.getExecutorKey();
376375
if (stack.executor != null) {
377376
routeExecutor.put(route, stack.executor);
378377
}
378+
379379
String routePattern = normalizePath(basePath == null
380380
? safePattern
381381
: basePath + safePattern, false, true);
@@ -384,7 +384,9 @@ private Route defineRoute(@Nonnull String method, @Nonnull String pattern,
384384
tree.insert(Router.OPTIONS, routePattern, route);
385385
} else if (route.isHttpTrace()) {
386386
tree.insert(Router.TRACE, routePattern, route);
387-
} else if (route.isHttpHead() && route.getMethod().equals(GET)) {
387+
} else if (route.isHttpHead() && route.getMethod().
388+
389+
equals(GET)) {
388390
tree.insert(Router.HEAD, routePattern, route);
389391
}
390392
routes.add(route);
@@ -401,9 +403,21 @@ private Route defineRoute(@Nonnull String method, @Nonnull String pattern,
401403
renderer.add(MessageEncoder.TO_STRING);
402404
ExecutionMode mode = owner.getExecutionMode();
403405
for (Route route : routes) {
404-
Executor executor = routeExecutor.get(route);
405-
if (executor instanceof ForwardingExecutor) {
406-
executor = ((ForwardingExecutor) executor).executor;
406+
String executorKey = route.getExecutorKey();
407+
Executor executor;
408+
if (executorKey == null) {
409+
executor = routeExecutor.get(route);
410+
if (executor instanceof ForwardingExecutor) {
411+
executor = ((ForwardingExecutor) executor).executor;
412+
}
413+
} else {
414+
if (executorKey.equals("worker")) {
415+
executor = (worker instanceof ForwardingExecutor) ?
416+
((ForwardingExecutor) worker).executor :
417+
worker;
418+
} else {
419+
executor = executor(executorKey);
420+
}
407421
}
408422
/** Return type: */
409423
if (route.getReturnType() == null) {

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.jooby.MediaType;
1212
import io.jooby.Reified;
1313
import io.jooby.Route;
14+
import io.jooby.annotations.Dispatch;
1415
import io.jooby.internal.apt.asm.ArrayWriter;
1516
import io.jooby.internal.apt.asm.ConstructorWriter;
1617
import io.jooby.internal.apt.asm.RouteAttributesWriter;
@@ -20,8 +21,13 @@
2021
import org.objectweb.asm.Type;
2122

2223
import javax.annotation.processing.ProcessingEnvironment;
24+
import javax.lang.model.element.AnnotationMirror;
25+
import javax.lang.model.element.AnnotationValue;
26+
import javax.lang.model.element.ExecutableElement;
2327
import java.lang.reflect.Method;
2428
import java.util.List;
29+
import java.util.Map;
30+
import java.util.Optional;
2531

2632
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
2733
import static org.objectweb.asm.Opcodes.ACC_SUPER;
@@ -122,15 +128,65 @@ private void install(ClassWriter writer, List<HandlerCompiler> handlers) throws
122128
setContentType(visitor, "setProduces", handler.getProduces());
123129

124130
/**
131+
* ******************************************************************************************
125132
* Annotations as route attributes
133+
* ******************************************************************************************
126134
*/
127135
routeAttributes.process(handler.getExecutable());
136+
137+
/**
138+
* ******************************************************************************************
139+
* Dispatch
140+
* ******************************************************************************************
141+
*/
142+
setDispatch(visitor, handler.getExecutable());
128143
}
129144
visitor.visitInsn(RETURN);
130145
visitor.visitMaxs(0, 0);
131146
visitor.visitEnd();
132147
}
133148

149+
private void setDispatch(MethodVisitor visitor, ExecutableElement executable)
150+
throws NoSuchMethodException {
151+
String executorKey = findAnnotation(executable.getAnnotationMirrors(), Dispatch.class.getName())
152+
.map(it -> annotationAttribute(it, "value").toString())
153+
.orElseGet(() ->
154+
findAnnotation(executable.getEnclosingElement().getAnnotationMirrors(),
155+
Dispatch.class.getName())
156+
.map(it -> annotationAttribute(it, "value").toString())
157+
.orElse(null)
158+
);
159+
160+
if (executorKey != null) {
161+
Method setExecutor = Route.class.getDeclaredMethod("setExecutorKey", String.class);
162+
visitor.visitVarInsn(ALOAD, 2);
163+
visitor.visitLdcInsn(executorKey);
164+
visitor.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(setExecutor.getDeclaringClass()),
165+
setExecutor.getName(),
166+
Type.getMethodDescriptor(setExecutor), false);
167+
visitor.visitInsn(POP);
168+
}
169+
}
170+
171+
private Object annotationAttribute(AnnotationMirror annotationMirror, String method) {
172+
Map<? extends ExecutableElement, ? extends AnnotationValue> map = env
173+
.getElementUtils().getElementValuesWithDefaults(annotationMirror);
174+
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : map.entrySet()) {
175+
if (entry.getKey().getSimpleName().toString().equals(method)) {
176+
return entry.getValue().getValue();
177+
}
178+
}
179+
throw new IllegalArgumentException(
180+
"Missing: " + annotationMirror.getAnnotationType().toString() + "." + method);
181+
}
182+
183+
private Optional<? extends AnnotationMirror> findAnnotation(
184+
List<? extends AnnotationMirror> annotationMirrors, String name) {
185+
return annotationMirrors.stream()
186+
.filter(it -> it.getAnnotationType().toString().equals(name))
187+
.findFirst();
188+
}
189+
134190
private void setReturnType(MethodVisitor visitor, HandlerCompiler handler)
135191
throws NoSuchMethodException {
136192
TypeDefinition returnType = handler.getReturnType();

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@
33
import io.jooby.Extension;
44
import io.jooby.Jooby;
55
import io.jooby.Route;
6-
import org.jetbrains.annotations.NotNull;
76

87
import javax.annotation.Nonnull;
98
import javax.inject.Provider;
10-
import java.util.Arrays;
11-
import java.util.HashMap;
12-
import java.util.Map;
139

1410
public class MvcExtension implements Extension {
1511
public MvcExtension(Provider c) {
1612

1713
}
1814

1915
@Override public void install(@Nonnull Jooby application) throws Exception {
20-
application.dispatch(new MvcDispatch(application));
16+
Route route = application.get("/", ctx -> "..");
17+
route.setExecutorKey("myexecutor");
2118
}
2219
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package source;
2+
3+
import io.jooby.annotations.Dispatch;
4+
import io.jooby.annotations.GET;
5+
import io.jooby.annotations.Path;
6+
7+
@Dispatch
8+
public class RouteDispatch {
9+
10+
@Path("/toplevel")
11+
@GET
12+
public void toplevel() {
13+
14+
}
15+
16+
@Path("/methodlevel")
17+
@GET
18+
@Dispatch("single")
19+
public void methodlevel() {
20+
21+
}
22+
}

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

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

33
import io.jooby.Context;
4-
import io.jooby.MockContext;
54
import io.jooby.MockRouter;
65
import io.jooby.Route;
76
import io.jooby.StatusCode;
8-
import io.jooby.apt.MvcHandlerCompilerRunner;
97
import io.jooby.apt.MvcModuleCompilerRunner;
10-
import org.junit.Assert;
118
import org.junit.jupiter.api.Test;
129
import source.GetPostRoute;
1310
import source.JavaBeanParam;
1411
import source.NoPathRoute;
1512
import source.PrimitiveReturnType;
1613
import source.RouteAttributes;
14+
import source.RouteDispatch;
1715
import source.RouteWithMimeTypes;
1816
import source.Routes;
1917
import source.VoidRoute;
@@ -145,4 +143,14 @@ public void routeAttributes() throws Exception {
145143
})
146144
;
147145
}
146+
147+
@Test
148+
public void routeDispatch() throws Exception {
149+
new MvcModuleCompilerRunner(new RouteDispatch())
150+
.module(true, app -> {
151+
assertEquals("worker", app.getRoutes().get(0).getExecutorKey());
152+
assertEquals("single", app.getRoutes().get(1).getExecutorKey());
153+
})
154+
;
155+
}
148156
}

tests/src/test/java/io/jooby/MvcTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ public void mvcBody() {
378378
});
379379
}
380380

381-
// @Test
381+
@Test
382382
public void mvcDispatch() {
383383
new JoobyRunner(app -> {
384384
app.executor("single", Executors.newSingleThreadExecutor(r ->

0 commit comments

Comments
 (0)