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.jsr356.annotations;
20  
21  import java.lang.annotation.Annotation;
22  import java.lang.reflect.Method;
23  import java.util.LinkedList;
24  import java.util.List;
25  
26  import javax.websocket.EndpointConfig;
27  import javax.websocket.OnClose;
28  import javax.websocket.OnError;
29  import javax.websocket.OnMessage;
30  import javax.websocket.OnOpen;
31  
32  import org.eclipse.jetty.util.log.Log;
33  import org.eclipse.jetty.util.log.Logger;
34  import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner;
35  import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
36  import org.eclipse.jetty.websocket.common.util.ReflectUtils;
37  
38  public class AnnotatedEndpointScanner<T extends Annotation, C extends EndpointConfig> extends AbstractMethodAnnotationScanner<AnnotatedEndpointMetadata<T, C>>
39  {
40      private static final Logger LOG = Log.getLogger(AnnotatedEndpointScanner.class);
41  
42      private final LinkedList<IJsrParamId> paramsOnOpen;
43      private final LinkedList<IJsrParamId> paramsOnClose;
44      private final LinkedList<IJsrParamId> paramsOnError;
45      private final LinkedList<IJsrParamId> paramsOnMessage;
46      private final AnnotatedEndpointMetadata<T, C> metadata;
47  
48      public AnnotatedEndpointScanner(AnnotatedEndpointMetadata<T, C> metadata)
49      {
50          this.metadata = metadata;
51  
52          paramsOnOpen = new LinkedList<>();
53          paramsOnClose = new LinkedList<>();
54          paramsOnError = new LinkedList<>();
55          paramsOnMessage = new LinkedList<>();
56  
57          metadata.customizeParamsOnOpen(paramsOnOpen);
58          paramsOnOpen.add(JsrParamIdOnOpen.INSTANCE);
59  
60          metadata.customizeParamsOnClose(paramsOnClose);
61          paramsOnClose.add(JsrParamIdOnClose.INSTANCE);
62  
63          metadata.customizeParamsOnError(paramsOnError);
64          paramsOnError.add(JsrParamIdOnError.INSTANCE);
65  
66          metadata.customizeParamsOnMessage(paramsOnMessage);
67          paramsOnMessage.add(JsrParamIdText.INSTANCE);
68          paramsOnMessage.add(JsrParamIdBinary.INSTANCE);
69          paramsOnMessage.add(JsrParamIdPong.INSTANCE);
70      }
71  
72      private void assertNotDuplicate(JsrCallable callable, Class<? extends Annotation> methodAnnotationClass, Class<?> pojo, Method method)
73      {
74          if (callable != null)
75          {
76              // Duplicate annotation detected
77              StringBuilder err = new StringBuilder();
78              err.append("Encountered duplicate method annotations @");
79              err.append(methodAnnotationClass.getSimpleName());
80              err.append(" on ");
81              err.append(ReflectUtils.toString(pojo,callable.getMethod()));
82              err.append(" and ");
83              err.append(ReflectUtils.toString(pojo,method));
84  
85              throw new InvalidSignatureException(err.toString());
86          }
87      }
88  
89      @Override
90      public void onMethodAnnotation(AnnotatedEndpointMetadata<T, C> metadata, Class<?> pojo, Method method, Annotation annotation)
91      {
92          LOG.debug("onMethodAnnotation({}, {}, {}, {})",metadata,pojo,method,annotation);
93  
94          if (isAnnotation(annotation,OnOpen.class))
95          {
96              assertIsPublicNonStatic(method);
97              assertIsReturn(method,Void.TYPE);
98              assertNotDuplicate(metadata.onOpen,OnOpen.class,pojo,method);
99              OnOpenCallable onopen = new OnOpenCallable(pojo,method);
100             visitMethod(onopen,pojo,method,paramsOnOpen,OnOpen.class);
101             metadata.onOpen = onopen;
102             return;
103         }
104 
105         if (isAnnotation(annotation,OnClose.class))
106         {
107             assertIsPublicNonStatic(method);
108             assertIsReturn(method,Void.TYPE);
109             assertNotDuplicate(metadata.onClose,OnClose.class,pojo,method);
110             OnCloseCallable onclose = new OnCloseCallable(pojo,method);
111             visitMethod(onclose,pojo,method,paramsOnClose,OnClose.class);
112             metadata.onClose = onclose;
113             return;
114         }
115 
116         if (isAnnotation(annotation,OnError.class))
117         {
118             assertIsPublicNonStatic(method);
119             assertIsReturn(method,Void.TYPE);
120             assertNotDuplicate(metadata.onError,OnError.class,pojo,method);
121             OnErrorCallable onerror = new OnErrorCallable(pojo,method);
122             visitMethod(onerror,pojo,method,paramsOnError,OnError.class);
123             metadata.onError = onerror;
124             return;
125         }
126 
127         if (isAnnotation(annotation,OnMessage.class))
128         {
129             assertIsPublicNonStatic(method);
130             // assertIsReturn(method,Void.TYPE); // no validation, it can be any return type
131             OnMessageCallable onmessage = new OnMessageCallable(pojo,method);
132             visitMethod(onmessage,pojo,method,paramsOnMessage,OnMessage.class);
133 
134             Param param = onmessage.getMessageObjectParam();
135             switch (param.role)
136             {
137                 case MESSAGE_BINARY:
138                     metadata.onBinary = new OnMessageBinaryCallable(onmessage);
139                     break;
140                 case MESSAGE_BINARY_STREAM:
141                     metadata.onBinaryStream = new OnMessageBinaryStreamCallable(onmessage);
142                     break;
143                 case MESSAGE_TEXT:
144                     metadata.onText = new OnMessageTextCallable(onmessage);
145                     break;
146                 case MESSAGE_TEXT_STREAM:
147                     metadata.onTextStream = new OnMessageTextStreamCallable(onmessage);
148                     break;
149                 case MESSAGE_PONG:
150                     metadata.onPong = new OnMessagePongCallable(onmessage);
151                     break;
152                 default:
153                     StringBuilder err = new StringBuilder();
154                     err.append("An unrecognized message type <");
155                     err.append(param.type);
156                     err.append(">: does not meet specified type categories of [TEXT, BINARY, DECODER, or PONG]");
157                     throw new InvalidSignatureException(err.toString());
158             }
159         }
160     }
161 
162     public AnnotatedEndpointMetadata<T, C> scan()
163     {
164         scanMethodAnnotations(metadata,metadata.getEndpointClass());
165         return metadata;
166     }
167 
168     private void visitMethod(JsrCallable callable, Class<?> pojo, Method method, LinkedList<IJsrParamId> paramIds,
169             Class<? extends Annotation> methodAnnotationClass)
170     {
171         // Identify all of the parameters
172         for (Param param : callable.getParams())
173         {
174             if (!visitParam(callable,param,paramIds))
175             {
176                 StringBuilder err = new StringBuilder();
177                 err.append("Encountered unknown parameter type <");
178                 err.append(param.type.getName());
179                 err.append("> on @");
180                 err.append(methodAnnotationClass.getSimpleName());
181                 err.append(" annotated method: ");
182                 err.append(ReflectUtils.toString(pojo,method));
183 
184                 throw new InvalidSignatureException(err.toString());
185             }
186         }
187     }
188 
189     private boolean visitParam(JsrCallable callable, Param param, List<IJsrParamId> paramIds)
190     {
191         for (IJsrParamId paramId : paramIds)
192         {
193             LOG.debug("{}.process()",paramId);
194             if (paramId.process(param,callable))
195             {
196                 // Successfully identified
197                 LOG.debug("Identified: {}",param);
198                 return true;
199             }
200         }
201 
202         // Failed identification as a known parameter
203         return false;
204     }
205 }