Skip to content

Commit 6acd841

Browse files
declarative websockets upd
1 parent 4c7758d commit 6acd841

7 files changed

Lines changed: 176 additions & 234 deletions

File tree

docs/asciidoc/websocket.adoc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,15 +171,15 @@ import io.jooby.jackson.Jackson2Module
171171
==== Declarative definition
172172

173173
You can implement the same WebSocket as above using annotated classes in declarative style.
174-
Ensure that `jooby-apt` is in the annotation processor path, annotate the class with javadoc:annotation.ws.WebSocketRoute[],
174+
Ensure that `jooby-apt` is in the annotation processor path, annotate the class with javadoc:annotation.Path[],
175175
and mark methods with javadoc:annotation.ws.OnConnect[], javadoc:annotation.ws.OnMessage[], javadoc:annotation.ws.OnClose[], and javadoc:annotation.ws.OnError[]. Compile code to generate an extension javadoc:Extension[] and register it by calling javadoc:Jooby[ws, io.jooby.Extension].
176176

177177
When a lifecycle method **returns** a value, that value is written to the client automatically: plain text or binary for `String`, `byte[]`, and `ByteBuffer`, and structured values (for example JSON) using the same encoders as in <<Structured Data>>. Alternatively, use a **void** method and send with `ws.send(...)` on the javadoc:WebSocket[] argument.
178178

179179
.Java
180180
[source,java,role="primary"]
181181
----
182-
@WebSocketRoute("/chat/{room}") // <1>
182+
@Path("/chat/{room}") // <1>
183183
public class ChatSocket {
184184
185185
@OnConnect
@@ -190,7 +190,7 @@ public class ChatSocket {
190190
@OnMessage
191191
public Map<String, String> onMessage(WebSocket ws, Context ctx, WebSocketMessage message) { // <3>
192192
return Map.of("echo", message.value());
193-
// ws.send(message.value()); // <4>
193+
// ws.send(message.value()); // <4>
194194
}
195195
196196
@OnClose
@@ -202,14 +202,14 @@ public class ChatSocket {
202202
203203
// Application startup:
204204
{
205-
ws(new ChatSocketWs_()); // <5>
205+
ws(new ChatSocketWs_()); // <5>
206206
}
207207
----
208208

209209
.Kotlin
210210
[source,kotlin,role="secondary"]
211211
----
212-
@WebSocketRoute("/chat/{room}") // <1>
212+
@Path("/chat/{room}") // <1>
213213
class ChatSocket {
214214
215215
@OnConnect
@@ -220,7 +220,7 @@ class ChatSocket {
220220
@OnMessage
221221
fun onMessage(ws: WebSocket, ctx: Context, message: WebSocketMessage): Map<String, String> { // <3>
222222
return mapOf("echo" to message.value())
223-
// ws.send(message.value()) // <4>
223+
// ws.send(message.value()) // <4>
224224
}
225225
226226
@OnClose
@@ -232,7 +232,7 @@ class ChatSocket {
232232
233233
// Application startup:
234234
{
235-
ws(ChatSocketWs_()) // <5>
235+
ws(ChatSocketWs_()) // <5>
236236
}
237237
----
238238

jooby/src/main/java/io/jooby/annotation/ws/WebSocketRoute.java

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

modules/jooby-apt/src/main/java/io/jooby/apt/JoobyProcessor.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,6 @@ public Set<String> getSupportedAnnotationTypes() {
284284
supportedTypes.add("io.jooby.annotation.mcp.McpResource");
285285
supportedTypes.add("io.jooby.annotation.mcp.McpServer");
286286
// Add WS Annotations
287-
supportedTypes.add("io.jooby.annotation.ws.WebSocketRoute");
288287
supportedTypes.add("io.jooby.annotation.ws.OnConnect");
289288
supportedTypes.add("io.jooby.annotation.ws.OnClose");
290289
supportedTypes.add("io.jooby.annotation.ws.OnMessage");

modules/jooby-apt/src/main/java/io/jooby/internal/apt/ws/WsHandlerMethod.java

Lines changed: 0 additions & 93 deletions
This file was deleted.
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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.apt.ws;
7+
8+
import io.jooby.internal.apt.*;
9+
10+
import javax.lang.model.element.ExecutableElement;
11+
import javax.lang.model.element.TypeElement;
12+
import javax.lang.model.element.VariableElement;
13+
import javax.lang.model.type.TypeMirror;
14+
import java.util.Map;
15+
import java.util.StringJoiner;
16+
17+
import static io.jooby.internal.apt.CodeBlock.*;
18+
import static java.lang.System.lineSeparator;
19+
20+
public class WsRoute extends WebRoute<WsRouter> {
21+
22+
private static final Map<String, WsLifecycle> LIFECYCLE_ANNOTATIONS =
23+
Map.of(
24+
"io.jooby.annotation.ws.OnConnect", WsLifecycle.CONNECT,
25+
"io.jooby.annotation.ws.OnMessage", WsLifecycle.MESSAGE,
26+
"io.jooby.annotation.ws.OnClose", WsLifecycle.CLOSE,
27+
"io.jooby.annotation.ws.OnError", WsLifecycle.ERROR);
28+
29+
private WsLifecycle wsLifecycle;
30+
31+
public WsRoute(WsRouter router, ExecutableElement method) {
32+
super(router, method);
33+
chekWsAnnotations();
34+
}
35+
36+
private void chekWsAnnotations() {
37+
for (var entry : LIFECYCLE_ANNOTATIONS.entrySet()) {
38+
if (AnnotationSupport.findAnnotationByName(this.method, entry.getKey()) != null) {
39+
this.wsLifecycle = entry.getValue();
40+
validateLifecycleParameters(context, method);
41+
break;
42+
}
43+
}
44+
}
45+
46+
public WsLifecycle getWsLifecycle() {
47+
return wsLifecycle;
48+
}
49+
50+
@Override
51+
public boolean hasBeanValidation() {
52+
return false;
53+
}
54+
55+
@Override
56+
public TypeDefinition getReturnType() {
57+
var types = context.getProcessingEnvironment().getTypeUtils();
58+
return new TypeDefinition(types, method.getReturnType());
59+
}
60+
61+
private String wsParameterName(MvcParameter parameter) {
62+
String rawParamType = parameter.getType().getRawType().toString();
63+
var name = WsParamTypes.generateArgumentName(rawParamType);
64+
if (name != null) {
65+
return name;
66+
}
67+
68+
getContext()
69+
.error("Unsupported websocket handler parameter type: %s.", rawParamType);
70+
return "null";
71+
}
72+
73+
private String paramList() {
74+
var joiner = new StringJoiner(", ", "(", ")");
75+
for (var param : getParameters(true)) {
76+
joiner.add(wsParameterName(param));
77+
}
78+
return joiner.toString();
79+
}
80+
81+
public String invocation(boolean kt) {
82+
return makeCall(kt, paramList(), false, false);
83+
}
84+
85+
public void appendBody(boolean kt, StringBuilder buffer, String indent) {
86+
buffer.append(statement(indent, var(kt), "c = this.factory.apply(ctx)", semicolon(kt)));
87+
88+
TypeDefinition wsReturnType = getReturnType();
89+
var expr = invocation(kt);
90+
91+
if (isUncheckedCast()) {
92+
buffer
93+
.append(indent)
94+
.append(
95+
kt ? "@Suppress(\"UNCHECKED_CAST\") " : "@SuppressWarnings(\"unchecked\") ")
96+
.append(lineSeparator());
97+
}
98+
99+
if (wsReturnType.isVoid()) {
100+
buffer.append(statement(indent, expr, semicolon(kt)));
101+
return;
102+
}
103+
104+
buffer.append(
105+
statement(
106+
indent, kt ? "val" : "var", " __wsReturn = ", expr, semicolon(kt)));
107+
String rawErasure = wsReturnType.getRawType().toString();
108+
switch (rawErasure) {
109+
case "java.lang.String", "byte[]", "java.nio.ByteBuffer" ->
110+
buffer.append(statement(indent, "ws.send(__wsReturn)", semicolon(kt)));
111+
default -> buffer.append(statement(indent, "ws.render(__wsReturn)", semicolon(kt)));
112+
}
113+
}
114+
115+
private void validateLifecycleParameters(MvcContext context, ExecutableElement method) {
116+
var env = context.getProcessingEnvironment();
117+
var types = env.getTypeUtils();
118+
var throwableType = env.getElementUtils().getTypeElement(Throwable.class.getName()).asType();
119+
var allowed = WsParamTypes.getAllowedTypes(wsLifecycle);
120+
121+
for (VariableElement parameter : method.getParameters()) {
122+
TypeMirror rawMirror = websocketParameterRawType(types, parameter);
123+
var raw = rawMirror.toString();
124+
if (allowed.contains(raw)) {
125+
continue;
126+
}
127+
128+
if (wsLifecycle == WsLifecycle.ERROR
129+
&& throwableType != null
130+
&& types.isAssignable(rawMirror, throwableType)) {
131+
continue;
132+
}
133+
134+
context.error(
135+
"Illegal parameter type %s on websocket %s method %s#%s",
136+
raw,
137+
wsLifecycle.name().toLowerCase(),
138+
((TypeElement) method.getEnclosingElement()).getQualifiedName(),
139+
method.getSimpleName());
140+
}
141+
}
142+
143+
private static TypeMirror websocketParameterRawType(javax.lang.model.util.Types types,
144+
VariableElement parameter) {
145+
return new TypeDefinition(types, parameter.asType()).getRawType();
146+
}
147+
}

0 commit comments

Comments
 (0)