View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
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   * Create EventDriver implementations.
43   */
44  public class EventDriverFactory
45  {
46      /**
47       * Parameter list for @OnWebSocketMessage (Binary mode)
48       */
49      private static final ParamList validBinaryParams;
50  
51      /**
52       * Parameter list for @OnWebSocketConnect
53       */
54      private static final ParamList validConnectParams;
55  
56      /**
57       * Parameter list for @OnWebSocketClose
58       */
59      private static final ParamList validCloseParams;
60  
61      /**
62       * Parameter list for @OnWebSocketError
63       */
64      private static final ParamList validErrorParams;
65  
66      /**
67       * Parameter list for @OnWebSocketFrame
68       */
69      private static final ParamList validFrameParams;
70  
71      /**
72       * Parameter list for @OnWebSocketMessage (Text mode)
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             // Attempt to add duplicate frame type (a no-no)
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         // validate parameters
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      * Perform the basic discovery mechanism for WebSocket events from the provided pojo.
201      * 
202      * @param pojo
203      *            the pojo to scan
204      * @return the discovered event methods
205      * @throws InvalidWebSocketException
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             // skip
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; // not valid
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         // validate parameters
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                         // Text mode
298                         // TODO
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                         // Binary Mode
308                         // TODO
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                 // Not a tagged method we are interested in, ignore
342             }
343 
344             // try superclass now
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      * Wrap the given WebSocket object instance in a suitable EventDriver
359      * 
360      * @param websocket
361      *            the websocket instance to wrap. Must either implement {@link WebSocketListener} or be annotated with {@link WebSocket &#064WebSocket}
362      * @return appropriate EventDriver for this websocket instance.
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 }