1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.common.events;
20
21 import java.io.InputStream;
22 import java.io.Reader;
23 import java.lang.annotation.Annotation;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Modifier;
26 import java.util.concurrent.ConcurrentHashMap;
27
28 import org.eclipse.jetty.util.StringUtil;
29 import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
30 import org.eclipse.jetty.websocket.api.Session;
31 import org.eclipse.jetty.websocket.api.WebSocketListener;
32 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
33 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
34 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
35 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
36 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
37 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
38 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
39 import org.eclipse.jetty.websocket.api.extensions.Frame;
40
41
42
43
44 public class EventDriverFactory
45 {
46
47
48
49 private static final ParamList validBinaryParams;
50
51
52
53
54 private static final ParamList validConnectParams;
55
56
57
58
59 private static final ParamList validCloseParams;
60
61
62
63
64 private static final ParamList validErrorParams;
65
66
67
68
69 private static final ParamList validFrameParams;
70
71
72
73
74 private static final ParamList validTextParams;
75
76 static
77 {
78 validConnectParams = new ParamList();
79 validConnectParams.addParams(Session.class);
80
81 validCloseParams = new ParamList();
82 validCloseParams.addParams(int.class,String.class);
83 validCloseParams.addParams(Session.class,int.class,String.class);
84
85 validErrorParams = new ParamList();
86 validErrorParams.addParams(Throwable.class);
87 validErrorParams.addParams(Session.class,Throwable.class);
88
89 validTextParams = new ParamList();
90 validTextParams.addParams(String.class);
91 validTextParams.addParams(Session.class,String.class);
92 validTextParams.addParams(Reader.class);
93 validTextParams.addParams(Session.class,Reader.class);
94
95 validBinaryParams = new ParamList();
96 validBinaryParams.addParams(byte[].class,int.class,int.class);
97 validBinaryParams.addParams(Session.class,byte[].class,int.class,int.class);
98 validBinaryParams.addParams(InputStream.class);
99 validBinaryParams.addParams(Session.class,InputStream.class);
100
101 validFrameParams = new ParamList();
102 validFrameParams.addParams(Frame.class);
103 validFrameParams.addParams(Session.class,Frame.class);
104 }
105
106 private ConcurrentHashMap<Class<?>, EventMethods> cache;
107 private final WebSocketPolicy policy;
108
109 public EventDriverFactory(WebSocketPolicy policy)
110 {
111 this.policy = policy;
112 this.cache = new ConcurrentHashMap<>();
113 }
114
115 private void assertIsPublicNonStatic(Method method)
116 {
117 int mods = method.getModifiers();
118 if (!Modifier.isPublic(mods))
119 {
120 StringBuilder err = new StringBuilder();
121 err.append("Invalid declaration of ");
122 err.append(method);
123 err.append(StringUtil.__LINE_SEPARATOR);
124
125 err.append("Method modifier must be public");
126
127 throw new InvalidWebSocketException(err.toString());
128 }
129
130 if (Modifier.isStatic(mods))
131 {
132 StringBuilder err = new StringBuilder();
133 err.append("Invalid declaration of ");
134 err.append(method);
135 err.append(StringUtil.__LINE_SEPARATOR);
136
137 err.append("Method modifier may not be static");
138
139 throw new InvalidWebSocketException(err.toString());
140 }
141 }
142
143 private void assertIsReturn(Method method, Class<?> type)
144 {
145 if (!type.equals(method.getReturnType()))
146 {
147 StringBuilder err = new StringBuilder();
148 err.append("Invalid declaration of ");
149 err.append(method);
150 err.append(StringUtil.__LINE_SEPARATOR);
151
152 err.append("Return type must be ").append(type);
153
154 throw new InvalidWebSocketException(err.toString());
155 }
156 }
157
158 private void assertUnset(EventMethod event, Class<? extends Annotation> annoClass, Method method)
159 {
160 if (event != null)
161 {
162
163 StringBuilder err = new StringBuilder();
164 err.append("Duplicate @").append(annoClass.getSimpleName()).append(" declaration on ");
165 err.append(method);
166 err.append(StringUtil.__LINE_SEPARATOR);
167
168 err.append("@").append(annoClass.getSimpleName()).append(" previously declared at ");
169 err.append(event.getMethod());
170
171 throw new InvalidWebSocketException(err.toString());
172 }
173 }
174
175 private void assertValidSignature(Method method, Class<? extends Annotation> annoClass, ParamList validParams)
176 {
177 assertIsPublicNonStatic(method);
178 assertIsReturn(method,Void.TYPE);
179
180 boolean valid = false;
181
182
183 Class<?> actual[] = method.getParameterTypes();
184 for (Class<?>[] params : validParams)
185 {
186 if (isSameParameters(actual,params))
187 {
188 valid = true;
189 break;
190 }
191 }
192
193 if (!valid)
194 {
195 throw InvalidSignatureException.build(method,annoClass,validParams);
196 }
197 }
198
199
200
201
202
203
204
205
206
207 private EventMethods discoverMethods(Class<?> pojo) throws InvalidWebSocketException
208 {
209 WebSocket anno = pojo.getAnnotation(WebSocket.class);
210 if (anno == null)
211 {
212 return null;
213 }
214
215 return scanAnnotatedMethods(pojo);
216 }
217
218 public EventMethods getMethods(Class<?> pojo) throws InvalidWebSocketException
219 {
220 if (pojo == null)
221 {
222 throw new InvalidWebSocketException("Cannot get methods for null class");
223 }
224 if (cache.containsKey(pojo))
225 {
226 return cache.get(pojo);
227 }
228 EventMethods methods = discoverMethods(pojo);
229 if (methods == null)
230 {
231 return null;
232 }
233 cache.put(pojo,methods);
234 return methods;
235 }
236
237 private boolean isSameParameters(Class<?>[] actual, Class<?>[] params)
238 {
239 if (actual.length != params.length)
240 {
241
242 return false;
243 }
244
245 int len = params.length;
246 for (int i = 0; i < len; i++)
247 {
248 if (!actual[i].equals(params[i]))
249 {
250 return false;
251 }
252 }
253
254 return true;
255 }
256
257 private boolean isSignatureMatch(Method method, ParamList validParams)
258 {
259 assertIsPublicNonStatic(method);
260 assertIsReturn(method,Void.TYPE);
261
262
263 Class<?> actual[] = method.getParameterTypes();
264 for (Class<?>[] params : validParams)
265 {
266 if (isSameParameters(actual,params))
267 {
268 return true;
269 }
270 }
271
272 return false;
273 }
274
275 private EventMethods scanAnnotatedMethods(Class<?> pojo)
276 {
277 Class<?> clazz = pojo;
278 EventMethods events = new EventMethods(pojo);
279
280 clazz = pojo;
281 while (clazz.getAnnotation(WebSocket.class) != null)
282 {
283 for (Method method : clazz.getDeclaredMethods())
284 {
285 if (method.getAnnotation(OnWebSocketConnect.class) != null)
286 {
287 assertValidSignature(method,OnWebSocketConnect.class,validConnectParams);
288 assertUnset(events.onConnect,OnWebSocketConnect.class,method);
289 events.onConnect = new EventMethod(pojo,method);
290 continue;
291 }
292
293 if (method.getAnnotation(OnWebSocketMessage.class) != null)
294 {
295 if (isSignatureMatch(method,validTextParams))
296 {
297
298
299
300 assertUnset(events.onText,OnWebSocketMessage.class,method);
301 events.onText = new EventMethod(pojo,method);
302 continue;
303 }
304
305 if (isSignatureMatch(method,validBinaryParams))
306 {
307
308
309 assertUnset(events.onBinary,OnWebSocketMessage.class,method);
310 events.onBinary = new EventMethod(pojo,method);
311 continue;
312 }
313
314 throw InvalidSignatureException.build(method,OnWebSocketMessage.class,validTextParams,validBinaryParams);
315 }
316
317 if (method.getAnnotation(OnWebSocketClose.class) != null)
318 {
319 assertValidSignature(method,OnWebSocketClose.class,validCloseParams);
320 assertUnset(events.onClose,OnWebSocketClose.class,method);
321 events.onClose = new EventMethod(pojo,method);
322 continue;
323 }
324
325 if (method.getAnnotation(OnWebSocketError.class) != null)
326 {
327 assertValidSignature(method,OnWebSocketError.class,validErrorParams);
328 assertUnset(events.onError,OnWebSocketError.class,method);
329 events.onError = new EventMethod(pojo,method);
330 continue;
331 }
332
333 if (method.getAnnotation(OnWebSocketFrame.class) != null)
334 {
335 assertValidSignature(method,OnWebSocketFrame.class,validFrameParams);
336 assertUnset(events.onFrame,OnWebSocketFrame.class,method);
337 events.onFrame = new EventMethod(pojo,method);
338 continue;
339 }
340
341
342 }
343
344
345 clazz = clazz.getSuperclass();
346 }
347
348 return events;
349 }
350
351 @Override
352 public String toString()
353 {
354 return String.format("EventMethodsCache [cache.count=%d]",cache.size());
355 }
356
357
358
359
360
361
362
363
364 public EventDriver wrap(Object websocket)
365 {
366 if (websocket == null)
367 {
368 throw new InvalidWebSocketException("null websocket object");
369 }
370
371 if (websocket instanceof WebSocketListener)
372 {
373 WebSocketPolicy pojoPolicy = policy.clonePolicy();
374 WebSocketListener listener = (WebSocketListener)websocket;
375 return new ListenerEventDriver(pojoPolicy,listener);
376 }
377
378 EventMethods methods = getMethods(websocket.getClass());
379 if (methods != null)
380 {
381 WebSocketPolicy pojoPolicy = policy.clonePolicy();
382 return new AnnotatedEventDriver(pojoPolicy,websocket,methods);
383 }
384
385 throw new InvalidWebSocketException(websocket.getClass().getName() + " does not implement " + WebSocketListener.class.getName()
386 + " or declare @WebSocket");
387 }
388 }