1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jetty.annotations;
15
16 import java.lang.reflect.Field;
17 import java.lang.reflect.Method;
18 import java.lang.reflect.Modifier;
19 import java.util.List;
20
21 import javax.annotation.PostConstruct;
22 import javax.annotation.PreDestroy;
23 import javax.annotation.Resource;
24 import javax.annotation.Resources;
25 import javax.annotation.security.RunAs;
26 import javax.naming.InitialContext;
27 import javax.naming.NameNotFoundException;
28 import javax.naming.NamingException;
29
30 import org.eclipse.jetty.plus.annotation.Injection;
31 import org.eclipse.jetty.plus.annotation.InjectionCollection;
32 import org.eclipse.jetty.plus.annotation.LifeCycleCallbackCollection;
33 import org.eclipse.jetty.plus.annotation.PostConstructCallback;
34 import org.eclipse.jetty.plus.annotation.PreDestroyCallback;
35 import org.eclipse.jetty.plus.annotation.RunAsCollection;
36 import org.eclipse.jetty.servlet.ServletHandler;
37 import org.eclipse.jetty.util.IntrospectionUtil;
38 import org.eclipse.jetty.util.LazyList;
39 import org.eclipse.jetty.util.log.Log;
40 import org.eclipse.jetty.webapp.WebAppContext;
41
42
43
44
45
46
47
48
49 public class AnnotationProcessor
50 {
51 AnnotationFinder _finder;
52 ClassLoader _loader;
53 RunAsCollection _runAs;
54 InjectionCollection _injections;
55 LifeCycleCallbackCollection _callbacks;
56 List _servlets;
57 List _filters;
58 List _listeners;
59 List _servletMappings;
60 List _filterMappings;
61 WebAppContext _webApp;
62
63 private static Class[] __envEntryTypes =
64 new Class[] {String.class, Character.class, Integer.class, Boolean.class, Double.class, Byte.class, Short.class, Long.class, Float.class};
65
66 public AnnotationProcessor(WebAppContext webApp, AnnotationFinder finder)
67 {
68 if (webApp == null)
69 throw new IllegalStateException("No WebAppContext");
70
71 _webApp=webApp;
72 _finder=finder;
73 ServletHandler servletHandler = _webApp.getServletHandler();
74 _filters = LazyList.array2List(servletHandler.getFilters());
75 _filterMappings = LazyList.array2List(servletHandler.getFilterMappings());
76 _servlets = LazyList.array2List(servletHandler.getServlets());
77 _servletMappings = LazyList.array2List(servletHandler.getServletMappings());
78 _listeners = LazyList.array2List(_webApp.getEventListeners());
79
80 _runAs = (RunAsCollection)_webApp.getAttribute(RunAsCollection.RUNAS_COLLECTION);
81 _injections = (InjectionCollection)_webApp.getAttribute(InjectionCollection.INJECTION_COLLECTION);
82 _callbacks = (LifeCycleCallbackCollection)_webApp.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
83
84 if (_runAs == null || _injections == null || _callbacks == null)
85 throw new IllegalStateException("RunAs, Injections or LifeCycleCallbacks is null");
86 }
87
88
89 public void process ()
90 throws Exception
91 {
92 processServlets();
93 processFilters();
94 processListeners();
95 processRunAsAnnotations();
96 processLifeCycleCallbackAnnotations();
97 processResourcesAnnotations();
98 processResourceAnnotations();
99 }
100
101 public void processServlets ()
102 throws Exception
103 {
104 }
105
106 public void processFilters ()
107 throws Exception
108 {
109 }
110
111
112
113 public void processListeners ()
114 throws Exception
115 {
116 }
117
118
119 public List getServlets ()
120 {
121 return _servlets;
122 }
123
124 public List getServletMappings ()
125 {
126 return _servletMappings;
127 }
128
129 public List getFilters ()
130 {
131 return _filters;
132 }
133
134 public List getFilterMappings ()
135 {
136 return _filterMappings;
137 }
138
139 public List getListeners()
140 {
141 return _listeners;
142 }
143
144
145 public void processRunAsAnnotations ()
146 throws Exception
147 {
148 for (Class clazz:_finder.getClassesForAnnotation(RunAs.class))
149 {
150 if (!javax.servlet.Servlet.class.isAssignableFrom(clazz))
151 {
152 Log.debug("Ignoring runAs notation on on-servlet class "+clazz.getName());
153 continue;
154 }
155 RunAs runAs = (RunAs)clazz.getAnnotation(RunAs.class);
156 if (runAs != null)
157 {
158 String role = runAs.value();
159 if (role != null)
160 {
161 org.eclipse.jetty.plus.annotation.RunAs ra = new org.eclipse.jetty.plus.annotation.RunAs();
162 ra.setTargetClass(clazz);
163 ra.setRoleName(role);
164 _runAs.add(ra);
165 }
166 }
167 }
168 }
169
170
171 public void processLifeCycleCallbackAnnotations()
172 throws Exception
173 {
174 processPostConstructAnnotations();
175 processPreDestroyAnnotations();
176 }
177
178 private void processPostConstructAnnotations ()
179 throws Exception
180 {
181
182 for (Method m:_finder.getMethodsForAnnotation(PostConstruct.class))
183 {
184 if (!isServletType(m.getDeclaringClass()))
185 {
186 Log.debug("Ignoring "+m.getName()+" as non-servlet type");
187 continue;
188 }
189 if (m.getParameterTypes().length != 0)
190 throw new IllegalStateException(m+" has parameters");
191 if (m.getReturnType() != Void.TYPE)
192 throw new IllegalStateException(m+" is not void");
193 if (m.getExceptionTypes().length != 0)
194 throw new IllegalStateException(m+" throws checked exceptions");
195 if (Modifier.isStatic(m.getModifiers()))
196 throw new IllegalStateException(m+" is static");
197
198 PostConstructCallback callback = new PostConstructCallback();
199 callback.setTargetClass(m.getDeclaringClass());
200 callback.setTarget(m);
201 _callbacks.add(callback);
202 }
203 }
204
205 public void processPreDestroyAnnotations ()
206 throws Exception
207 {
208
209
210 for (Method m: _finder.getMethodsForAnnotation(PreDestroy.class))
211 {
212 if (!isServletType(m.getDeclaringClass()))
213 {
214 Log.debug("Ignoring "+m.getName()+" as non-servlet type");
215 continue;
216 }
217 if (m.getParameterTypes().length != 0)
218 throw new IllegalStateException(m+" has parameters");
219 if (m.getReturnType() != Void.TYPE)
220 throw new IllegalStateException(m+" is not void");
221 if (m.getExceptionTypes().length != 0)
222 throw new IllegalStateException(m+" throws checked exceptions");
223 if (Modifier.isStatic(m.getModifiers()))
224 throw new IllegalStateException(m+" is static");
225
226 PreDestroyCallback callback = new PreDestroyCallback();
227 callback.setTargetClass(m.getDeclaringClass());
228 callback.setTarget(m);
229 _callbacks.add(callback);
230 }
231 }
232
233
234
235
236
237 public void processResourcesAnnotations ()
238 throws Exception
239 {
240 List<Class<?>> classes = _finder.getClassesForAnnotation(Resources.class);
241 for (Class<?> clazz:classes)
242 {
243 if (!isServletType(clazz))
244 {
245 Log.debug("Ignoring @Resources annotation on on-servlet type class "+clazz.getName());
246 continue;
247 }
248
249 Resources resources = (Resources)clazz.getAnnotation(Resources.class);
250 if (resources == null)
251 continue;
252
253 Resource[] resArray = resources.value();
254 if (resArray==null||resArray.length==0)
255 continue;
256
257 for (int j=0;j<resArray.length;j++)
258 {
259 String name = resArray[j].name();
260 String mappedName = resArray[j].mappedName();
261 Resource.AuthenticationType auth = resArray[j].authenticationType();
262 Class type = resArray[j].type();
263 boolean shareable = resArray[j].shareable();
264
265 if (name==null || name.trim().equals(""))
266 throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
267 try
268 {
269
270
271 if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp, name, mappedName))
272 if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp.getServer(), name, mappedName))
273 throw new IllegalStateException("No resource bound at "+(mappedName==null?name:mappedName));
274 }
275 catch (NamingException e)
276 {
277 throw new IllegalStateException(e);
278 }
279 }
280 }
281 }
282
283
284 public void processResourceAnnotations ()
285 throws Exception
286 {
287 processClassResourceAnnotations();
288 processMethodResourceAnnotations();
289 processFieldResourceAnnotations();
290 }
291
292
293
294
295
296
297 public void processClassResourceAnnotations ()
298 throws Exception
299 {
300 List<Class<?>> classes = _finder.getClassesForAnnotation(Resource.class);
301 for (Class<?> clazz:classes)
302 {
303 if (!isServletType(clazz))
304 {
305 Log.debug("Ignoring @Resource annotation on on-servlet type class "+clazz.getName());
306 continue;
307 }
308
309 Resource resource = (Resource)clazz.getAnnotation(Resource.class);
310 if (resource != null)
311 {
312 String name = resource.name();
313 String mappedName = resource.mappedName();
314 Resource.AuthenticationType auth = resource.authenticationType();
315 Class type = resource.type();
316 boolean shareable = resource.shareable();
317
318 if (name==null || name.trim().equals(""))
319 throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
320
321 try
322 {
323
324 if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp, name,mappedName))
325 if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp.getServer(), name,mappedName))
326 throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
327 }
328 catch (NamingException e)
329 {
330 throw new IllegalStateException(e);
331 }
332 }
333 }
334 }
335
336
337
338
339
340
341
342
343 public void processMethodResourceAnnotations ()
344 throws Exception
345 {
346
347 List<Method> methods = _finder.getMethodsForAnnotation(javax.annotation.Resource.class);
348
349 for (Method m: methods)
350 {
351 if (!isServletType(m.getDeclaringClass()))
352 {
353 Log.debug("Ignoring @Resource annotation on on-servlet type method "+m.getName());
354 continue;
355 }
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373 Resource resource = (Resource)m.getAnnotation(Resource.class);
374 if (resource == null)
375 continue;
376
377
378 if (Modifier.isStatic(m.getModifiers()))
379 throw new IllegalStateException(m+" cannot be static");
380
381
382
383 if (!IntrospectionUtil.isJavaBeanCompliantSetter(m))
384 throw new IllegalStateException(m+" is not a java bean compliant setter method");
385
386
387 String name = m.getName().substring(3);
388 name = name.substring(0,1).toLowerCase()+name.substring(1);
389 name = m.getDeclaringClass().getCanonicalName()+"/"+name;
390
391 name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
392
393 String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
394
395 Class type = m.getParameterTypes()[0];
396
397
398 Resource.AuthenticationType auth = resource.authenticationType();
399 boolean shareable = resource.shareable();
400
401
402 if ((resource.type() != null)
403 &&
404 !resource.type().equals(Object.class)
405 &&
406 (!IntrospectionUtil.isTypeCompatible(type, resource.type(), false)))
407 throw new IllegalStateException("@Resource incompatible type="+resource.type()+ " with method param="+type+ " for "+m);
408
409
410 Injection webXmlInjection = _injections.getInjection(m.getDeclaringClass(), m);
411 if (webXmlInjection == null)
412 {
413 try
414 {
415
416
417 boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp, name, mappedName);
418
419
420 if (!bound)
421 bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp.getServer(), name, mappedName);
422
423
424 if (!bound)
425 bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName);
426
427
428
429 if (!bound)
430 {
431 try
432 {
433 InitialContext ic = new InitialContext();
434 String nameInEnvironment = (mappedName!=null?mappedName:name);
435 ic.lookup("java:comp/env/"+nameInEnvironment);
436 bound = true;
437 }
438 catch (NameNotFoundException e)
439 {
440 bound = false;
441 }
442 }
443
444 if (bound)
445 {
446 Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
447
448 Injection injection = new Injection();
449 injection.setTargetClass(m.getDeclaringClass());
450 injection.setJndiName(name);
451 injection.setMappingName(mappedName);
452 injection.setTarget(m);
453 _injections.add(injection);
454 }
455 else if (!isEnvEntryType(type))
456 {
457
458
459
460
461 throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
462 }
463 }
464 catch (NamingException e)
465 {
466
467
468
469 if (!isEnvEntryType(type))
470 throw new IllegalStateException(e);
471 }
472 }
473 else
474 {
475
476
477
478 Object value = webXmlInjection.lookupInjectedValue();
479 if (!IntrospectionUtil.isTypeCompatible(type, value.getClass(), false))
480 throw new IllegalStateException("Type of field="+type+" is not compatible with Resource type="+value.getClass());
481 }
482 }
483 }
484
485
486
487
488
489
490
491
492 public void processFieldResourceAnnotations ()
493 throws Exception
494 {
495
496 List<Field> fields = _finder.getFieldsForAnnotation(Resource.class);
497 for (Field f: fields)
498 {
499 if (!isServletType(f.getDeclaringClass()))
500 {
501 Log.debug("Ignoring @Resource annotation on on-servlet type field "+f.getName());
502 continue;
503 }
504 Resource resource = (Resource)f.getAnnotation(Resource.class);
505 if (resource == null)
506 continue;
507
508
509 if (Modifier.isStatic(f.getModifiers()))
510 throw new IllegalStateException(f+" cannot be static");
511
512
513 if (Modifier.isFinal(f.getModifiers()))
514 throw new IllegalStateException(f+" cannot be final");
515
516
517 String name = f.getDeclaringClass().getCanonicalName()+"/"+f.getName();
518
519 name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
520
521
522 Class type = f.getType();
523
524 if ((resource.type() != null)
525 &&
526 !resource.type().equals(Object.class)
527 &&
528 (!IntrospectionUtil.isTypeCompatible(type, resource.type(), false)))
529 throw new IllegalStateException("@Resource incompatible type="+resource.type()+ " with field type ="+f.getType());
530
531
532 String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
533
534 Resource.AuthenticationType auth = resource.authenticationType();
535 boolean shareable = resource.shareable();
536
537 Injection webXmlInjection = _injections.getInjection(f.getDeclaringClass(), f);
538 if (webXmlInjection == null)
539 {
540 try
541 {
542 boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp, name, mappedName);
543 if (!bound)
544 bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_webApp.getServer(), name, mappedName);
545 if (!bound)
546 bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName);
547 if (!bound)
548 {
549
550 try
551 {
552 InitialContext ic = new InitialContext();
553 String nameInEnvironment = (mappedName!=null?mappedName:name);
554 ic.lookup("java:comp/env/"+nameInEnvironment);
555 bound = true;
556 }
557 catch (NameNotFoundException e)
558 {
559 bound = false;
560 }
561 }
562
563 if (bound)
564 {
565 Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
566
567 Injection injection = new Injection();
568 injection.setTargetClass(f.getDeclaringClass());
569 injection.setJndiName(name);
570 injection.setMappingName(mappedName);
571 injection.setTarget(f);
572 _injections.add(injection);
573 }
574 else if (!isEnvEntryType(type))
575 {
576
577
578
579
580 throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
581 }
582 }
583 catch (NamingException e)
584 {
585
586
587
588 if (!isEnvEntryType(type))
589 throw new IllegalStateException(e);
590 }
591 }
592 else
593 {
594
595
596 Object value = webXmlInjection.lookupInjectedValue();
597 if (!IntrospectionUtil.isTypeCompatible(type, value.getClass(), false))
598 throw new IllegalStateException("Type of field="+type+" is not compatible with Resource type="+value.getClass());
599 }
600 }
601 }
602
603
604
605
606
607
608
609
610 private boolean isServletType (Class c)
611 {
612 boolean isServlet = false;
613 if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
614 javax.servlet.Filter.class.isAssignableFrom(c) ||
615 javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
616 javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
617 javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
618 javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
619 javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
620 javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c))
621
622 isServlet=true;
623
624 return isServlet;
625 }
626
627
628
629 private static boolean isEnvEntryType (Class type)
630 {
631 boolean result = false;
632 for (int i=0;i<__envEntryTypes.length && !result;i++)
633 {
634 result = (type.equals(__envEntryTypes[i]));
635 }
636 return result;
637 }
638
639 protected static String normalizePattern(String p)
640 {
641 if (p!=null && p.length()>0 && !p.startsWith("/") && !p.startsWith("*"))
642 return "/"+p;
643 return p;
644 }
645 }