1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mortbay.jetty.annotations;
17
18 import java.lang.reflect.Field;
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Modifier;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25
26 import javax.annotation.PostConstruct;
27 import javax.annotation.PreDestroy;
28 import javax.annotation.Resource;
29 import javax.annotation.Resources;
30 import javax.annotation.security.RunAs;
31 import javax.naming.InitialContext;
32 import javax.naming.NameNotFoundException;
33 import javax.naming.NamingException;
34 import javax.servlet.DispatcherType;
35 import javax.servlet.http.annotation.InitParam;
36 import javax.servlet.http.annotation.jaxrs.DELETE;
37 import javax.servlet.http.annotation.jaxrs.GET;
38 import javax.servlet.http.annotation.jaxrs.HEAD;
39 import javax.servlet.http.annotation.jaxrs.POST;
40 import javax.servlet.http.annotation.jaxrs.PUT;
41
42 import org.mortbay.jetty.Handler;
43 import org.mortbay.jetty.plus.annotation.Injection;
44 import org.mortbay.jetty.plus.annotation.InjectionCollection;
45 import org.mortbay.jetty.plus.annotation.LifeCycleCallbackCollection;
46 import org.mortbay.jetty.plus.annotation.PojoContextListener;
47 import org.mortbay.jetty.plus.annotation.PojoFilter;
48 import org.mortbay.jetty.plus.annotation.PojoServlet;
49 import org.mortbay.jetty.plus.annotation.PostConstructCallback;
50 import org.mortbay.jetty.plus.annotation.PreDestroyCallback;
51 import org.mortbay.jetty.plus.annotation.RunAsCollection;
52 import org.mortbay.jetty.Dispatcher;
53 import org.mortbay.jetty.servlet.FilterHolder;
54 import org.mortbay.jetty.servlet.FilterMapping;
55 import org.mortbay.jetty.servlet.ServletHolder;
56 import org.mortbay.jetty.servlet.ServletMapping;
57 import org.mortbay.jetty.webapp.WebAppContext;
58 import org.mortbay.log.Log;
59 import org.mortbay.util.IntrospectionUtil;
60 import org.mortbay.util.LazyList;
61
62
63
64
65
66
67
68
69 public class AnnotationProcessor
70 {
71 AnnotationFinder _finder;
72 ClassLoader _loader;
73 RunAsCollection _runAs;
74 InjectionCollection _injections;
75 LifeCycleCallbackCollection _callbacks;
76 List _servlets;
77 List _filters;
78 List _listeners;
79 List _servletMappings;
80 List _filterMappings;
81 Map _pojoInstances = new HashMap();
82 WebAppContext _webApp;
83
84 private static Class[] __envEntryTypes =
85 new Class[] {String.class, Character.class, Integer.class, Boolean.class, Double.class, Byte.class, Short.class, Long.class, Float.class};
86
87 public AnnotationProcessor(WebAppContext webApp, AnnotationFinder finder, RunAsCollection runAs, InjectionCollection injections, LifeCycleCallbackCollection callbacks,
88 List servlets, List filters, List listeners, List servletMappings, List filterMappings)
89 {
90 _webApp=webApp;
91 _finder=finder;
92 _runAs=runAs;
93 _injections=injections;
94 _callbacks=callbacks;
95 _servlets=servlets;
96 _filters=filters;
97 _listeners=listeners;
98 _servletMappings=servletMappings;
99 _filterMappings=filterMappings;
100 }
101
102
103 public void process ()
104 throws Exception
105 {
106 processServlets();
107 processFilters();
108 processListeners();
109 processRunAsAnnotations();
110 processLifeCycleCallbackAnnotations();
111 processResourcesAnnotations();
112 processResourceAnnotations();
113 }
114
115 public void processServlets ()
116 throws Exception
117 {
118
119 for (Class clazz:_finder.getClassesForAnnotation(javax.servlet.http.annotation.Servlet.class))
120 {
121 javax.servlet.http.annotation.Servlet annotation = (javax.servlet.http.annotation.Servlet)clazz.getAnnotation(javax.servlet.http.annotation.Servlet.class);
122 PojoServlet servlet = new PojoServlet(getPojoInstanceFor(clazz));
123
124 List<Method> methods = _finder.getMethodsForAnnotation(GET.class);
125 if (methods.size() > 1)
126 throw new IllegalStateException ("More than one GET annotation on "+clazz.getName());
127 else if (methods.size() == 1)
128 servlet.setGetMethodName(methods.get(0).getName());
129
130 methods = _finder.getMethodsForAnnotation(POST.class);
131 if (methods.size() > 1)
132 throw new IllegalStateException ("More than one POST annotation on "+clazz.getName());
133 else if (methods.size() == 1)
134 servlet.setPostMethodName(methods.get(0).getName());
135
136 methods = _finder.getMethodsForAnnotation(PUT.class);
137 if (methods.size() > 1)
138 throw new IllegalStateException ("More than one PUT annotation on "+clazz.getName());
139 else if (methods.size() == 1)
140 servlet.setPutMethodName(methods.get(0).getName());
141
142 methods = _finder.getMethodsForAnnotation(DELETE.class);
143 if (methods.size() > 1)
144 throw new IllegalStateException ("More than one DELETE annotation on "+clazz.getName());
145 else if (methods.size() == 1)
146 servlet.setDeleteMethodName(methods.get(0).getName());
147
148 methods = _finder.getMethodsForAnnotation(HEAD.class);
149 if (methods.size() > 1)
150 throw new IllegalStateException ("More than one HEAD annotation on "+clazz.getName());
151 else if (methods.size() == 1)
152 servlet.setHeadMethodName(methods.get(0).getName());
153
154 ServletHolder holder = new ServletHolder(servlet);
155 holder.setName((annotation.name().equals("")?clazz.getName():annotation.name()));
156 holder.setInitOrder(annotation.loadOnStartup());
157 LazyList.add(_servlets, holder);
158
159 for (InitParam ip:annotation.initParams())
160 {
161 holder.setInitParameter(ip.name(), ip.value());
162 }
163
164 if (annotation.urlMappings().length > 0)
165 {
166 ArrayList paths = new ArrayList();
167 ServletMapping mapping = new ServletMapping();
168 mapping.setServletName(holder.getName());
169 for (String s:annotation.urlMappings())
170 {
171 paths.add(normalizePattern(s));
172 }
173 mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
174 LazyList.add(_servletMappings,mapping);
175 }
176 }
177 }
178
179 public void processFilters ()
180 throws Exception
181 {
182
183 for (Class clazz:_finder.getClassesForAnnotation(javax.servlet.http.annotation.ServletFilter.class))
184 {
185 javax.servlet.http.annotation.ServletFilter annotation = (javax.servlet.http.annotation.ServletFilter)clazz.getAnnotation(javax.servlet.http.annotation.ServletFilter.class);
186 PojoFilter filter = new PojoFilter(getPojoInstanceFor(clazz));
187
188 FilterHolder holder = new FilterHolder(filter);
189 holder.setName((annotation.filterName().equals("")?clazz.getName():annotation.filterName()));
190 holder.setDisplayName(annotation.displayName());
191 LazyList.add(_filters, holder);
192
193 for (InitParam ip:annotation.initParams())
194 {
195 holder.setInitParameter(ip.name(), ip.value());
196 }
197
198 if (annotation.filterMapping() != null)
199 {
200 FilterMapping mapping = new FilterMapping();
201 mapping.setFilterName(holder.getName());
202 ArrayList paths = new ArrayList();
203 for (String s:annotation.filterMapping().urlPattern())
204 {
205 paths.add(normalizePattern(s));
206 }
207 mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
208 ArrayList names = new ArrayList();
209 for (String s:annotation.filterMapping().servletNames())
210 {
211 names.add(s);
212 }
213 mapping.setServletNames((String[])names.toArray(new String[names.size()]));
214
215 int dispatcher=Handler.DEFAULT;
216 for (DispatcherType d:annotation.filterMapping().dispatcherTypes())
217 {
218 dispatcher = dispatcher|Dispatcher.type(d);
219 }
220 mapping.setDispatches(dispatcher);
221 LazyList.add(_filterMappings,mapping);
222 }
223 }
224 }
225
226
227
228 public void processListeners ()
229 throws Exception
230 {
231
232 for (Class clazz:_finder.getClassesForAnnotation(javax.servlet.http.annotation.ServletContextListener.class))
233 {
234 PojoContextListener listener = new PojoContextListener(getPojoInstanceFor(clazz));
235 LazyList.add(_listeners, listener);
236 }
237 }
238
239
240 public List getServlets ()
241 {
242 return _servlets;
243 }
244
245 public List getServletMappings ()
246 {
247 return _servletMappings;
248 }
249
250 public List getFilters ()
251 {
252 return _filters;
253 }
254
255 public List getFilterMappings ()
256 {
257 return _filterMappings;
258 }
259
260 public List getListeners()
261 {
262 return _listeners;
263 }
264
265
266 public void processRunAsAnnotations ()
267 throws Exception
268 {
269 for (Class clazz:_finder.getClassesForAnnotation(RunAs.class))
270 {
271 if (!javax.servlet.Servlet.class.isAssignableFrom(clazz) && !(_pojoInstances.containsKey(clazz)))
272 {
273 Log.debug("Ignoring runAs notation on on-servlet class "+clazz.getName());
274 continue;
275 }
276 RunAs runAs = (RunAs)clazz.getAnnotation(RunAs.class);
277 if (runAs != null)
278 {
279 String role = runAs.value();
280 if (role != null)
281 {
282 org.mortbay.jetty.plus.annotation.RunAs ra = new org.mortbay.jetty.plus.annotation.RunAs();
283 ra.setTargetClass(clazz);
284 ra.setRoleName(role);
285 _runAs.add(ra);
286 }
287 }
288 }
289 }
290
291
292 public void processLifeCycleCallbackAnnotations()
293 throws Exception
294 {
295 processPostConstructAnnotations();
296 processPreDestroyAnnotations();
297 }
298
299 private void processPostConstructAnnotations ()
300 throws Exception
301 {
302
303 for (Method m:_finder.getMethodsForAnnotation(PostConstruct.class))
304 {
305 if (!isServletType(m.getDeclaringClass()))
306 {
307 Log.debug("Ignoring "+m.getName()+" as non-servlet type");
308 continue;
309 }
310 if (m.getParameterTypes().length != 0)
311 throw new IllegalStateException(m+" has parameters");
312 if (m.getReturnType() != Void.TYPE)
313 throw new IllegalStateException(m+" is not void");
314 if (m.getExceptionTypes().length != 0)
315 throw new IllegalStateException(m+" throws checked exceptions");
316 if (Modifier.isStatic(m.getModifiers()))
317 throw new IllegalStateException(m+" is static");
318
319 PostConstructCallback callback = new PostConstructCallback();
320 callback.setTargetClass(m.getDeclaringClass());
321 callback.setTarget(m);
322 _callbacks.add(callback);
323 }
324 }
325
326 public void processPreDestroyAnnotations ()
327 throws Exception
328 {
329
330
331 for (Method m: _finder.getMethodsForAnnotation(PreDestroy.class))
332 {
333 if (!isServletType(m.getDeclaringClass()))
334 {
335 Log.debug("Ignoring "+m.getName()+" as non-servlet type");
336 continue;
337 }
338 if (m.getParameterTypes().length != 0)
339 throw new IllegalStateException(m+" has parameters");
340 if (m.getReturnType() != Void.TYPE)
341 throw new IllegalStateException(m+" is not void");
342 if (m.getExceptionTypes().length != 0)
343 throw new IllegalStateException(m+" throws checked exceptions");
344 if (Modifier.isStatic(m.getModifiers()))
345 throw new IllegalStateException(m+" is static");
346
347 PreDestroyCallback callback = new PreDestroyCallback();
348 callback.setTargetClass(m.getDeclaringClass());
349 callback.setTarget(m);
350 _callbacks.add(callback);
351 }
352 }
353
354
355
356
357
358 public void processResourcesAnnotations ()
359 throws Exception
360 {
361 List<Class<?>> classes = _finder.getClassesForAnnotation(Resources.class);
362 for (Class<?> clazz:classes)
363 {
364 if (!isServletType(clazz))
365 {
366 Log.debug("Ignoring @Resources annotation on on-servlet type class "+clazz.getName());
367 continue;
368 }
369
370 Resources resources = (Resources)clazz.getAnnotation(Resources.class);
371 if (resources == null)
372 continue;
373
374 Resource[] resArray = resources.value();
375 if (resArray==null||resArray.length==0)
376 continue;
377
378 for (int j=0;j<resArray.length;j++)
379 {
380 String name = resArray[j].name();
381 String mappedName = resArray[j].mappedName();
382 Resource.AuthenticationType auth = resArray[j].authenticationType();
383 Class type = resArray[j].type();
384 boolean shareable = resArray[j].shareable();
385
386 if (name==null || name.trim().equals(""))
387 throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
388 try
389 {
390
391
392 if (!org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp, name, mappedName))
393 if (!org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp.getServer(), name, mappedName))
394 throw new IllegalStateException("No resource bound at "+(mappedName==null?name:mappedName));
395 }
396 catch (NamingException e)
397 {
398 throw new IllegalStateException(e);
399 }
400 }
401 }
402 }
403
404
405 public void processResourceAnnotations ()
406 throws Exception
407 {
408 processClassResourceAnnotations();
409 processMethodResourceAnnotations();
410 processFieldResourceAnnotations();
411 }
412
413
414
415
416
417
418 public void processClassResourceAnnotations ()
419 throws Exception
420 {
421 List<Class<?>> classes = _finder.getClassesForAnnotation(Resource.class);
422 for (Class<?> clazz:classes)
423 {
424 if (!isServletType(clazz))
425 {
426 Log.debug("Ignoring @Resource annotation on on-servlet type class "+clazz.getName());
427 continue;
428 }
429
430 Resource resource = (Resource)clazz.getAnnotation(Resource.class);
431 if (resource != null)
432 {
433 String name = resource.name();
434 String mappedName = resource.mappedName();
435 Resource.AuthenticationType auth = resource.authenticationType();
436 Class type = resource.type();
437 boolean shareable = resource.shareable();
438
439 if (name==null || name.trim().equals(""))
440 throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
441
442 try
443 {
444
445 if (!org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp, name,mappedName))
446 if (!org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp.getServer(), name,mappedName))
447 throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
448 }
449 catch (NamingException e)
450 {
451 throw new IllegalStateException(e);
452 }
453 }
454 }
455 }
456
457
458
459
460
461
462
463
464 public void processMethodResourceAnnotations ()
465 throws Exception
466 {
467
468 List<Method> methods = _finder.getMethodsForAnnotation(javax.annotation.Resource.class);
469
470 for (Method m: methods)
471 {
472 if (!isServletType(m.getDeclaringClass()))
473 {
474 Log.debug("Ignoring @Resource annotation on on-servlet type method "+m.getName());
475 continue;
476 }
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494 Resource resource = (Resource)m.getAnnotation(Resource.class);
495 if (resource == null)
496 continue;
497
498
499 if (Modifier.isStatic(m.getModifiers()))
500 throw new IllegalStateException(m+" cannot be static");
501
502
503
504 if (!IntrospectionUtil.isJavaBeanCompliantSetter(m))
505 throw new IllegalStateException(m+" is not a java bean compliant setter method");
506
507
508 String name = m.getName().substring(3);
509 name = name.substring(0,1).toLowerCase()+name.substring(1);
510 name = m.getDeclaringClass().getCanonicalName()+"/"+name;
511
512 name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
513
514 String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
515
516 Class type = m.getParameterTypes()[0];
517
518
519 Resource.AuthenticationType auth = resource.authenticationType();
520 boolean shareable = resource.shareable();
521
522
523 if ((resource.type() != null)
524 &&
525 !resource.type().equals(Object.class)
526 &&
527 (!IntrospectionUtil.isTypeCompatible(type, resource.type(), false)))
528 throw new IllegalStateException("@Resource incompatible type="+resource.type()+ " with method param="+type+ " for "+m);
529
530
531 Injection webXmlInjection = _injections.getInjection(m.getDeclaringClass(), m);
532 if (webXmlInjection == null)
533 {
534 try
535 {
536
537
538 boolean bound = org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp, name, mappedName);
539
540
541 if (!bound)
542 bound = org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp.getServer(), name, mappedName);
543
544
545 if (!bound)
546 bound = org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(null, name, mappedName);
547
548
549
550 if (!bound)
551 {
552 try
553 {
554 InitialContext ic = new InitialContext();
555 String nameInEnvironment = (mappedName!=null?mappedName:name);
556 ic.lookup("java:comp/env/"+nameInEnvironment);
557 bound = true;
558 }
559 catch (NameNotFoundException e)
560 {
561 bound = false;
562 }
563 }
564
565 if (bound)
566 {
567 Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
568
569 Injection injection = new Injection();
570 injection.setTargetClass(m.getDeclaringClass());
571 injection.setJndiName(name);
572 injection.setMappingName(mappedName);
573 injection.setTarget(m);
574 _injections.add(injection);
575 }
576 else if (!isEnvEntryType(type))
577 {
578
579
580
581
582 throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
583 }
584 }
585 catch (NamingException e)
586 {
587
588
589
590 if (!isEnvEntryType(type))
591 throw new IllegalStateException(e);
592 }
593 }
594 else
595 {
596
597
598
599 Object value = webXmlInjection.lookupInjectedValue();
600 if (!IntrospectionUtil.isTypeCompatible(type, value.getClass(), false))
601 throw new IllegalStateException("Type of field="+type+" is not compatible with Resource type="+value.getClass());
602 }
603 }
604 }
605
606
607
608
609
610
611
612
613 public void processFieldResourceAnnotations ()
614 throws Exception
615 {
616
617 List<Field> fields = _finder.getFieldsForAnnotation(Resource.class);
618 for (Field f: fields)
619 {
620 if (!isServletType(f.getDeclaringClass()))
621 {
622 Log.debug("Ignoring @Resource annotation on on-servlet type field "+f.getName());
623 continue;
624 }
625 Resource resource = (Resource)f.getAnnotation(Resource.class);
626 if (resource == null)
627 continue;
628
629
630 if (Modifier.isStatic(f.getModifiers()))
631 throw new IllegalStateException(f+" cannot be static");
632
633
634 if (Modifier.isFinal(f.getModifiers()))
635 throw new IllegalStateException(f+" cannot be final");
636
637
638 String name = f.getDeclaringClass().getCanonicalName()+"/"+f.getName();
639
640 name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
641
642
643 Class type = f.getType();
644
645 if ((resource.type() != null)
646 &&
647 !resource.type().equals(Object.class)
648 &&
649 (!IntrospectionUtil.isTypeCompatible(type, resource.type(), false)))
650 throw new IllegalStateException("@Resource incompatible type="+resource.type()+ " with field type ="+f.getType());
651
652
653 String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
654
655 Resource.AuthenticationType auth = resource.authenticationType();
656 boolean shareable = resource.shareable();
657
658 Injection webXmlInjection = _injections.getInjection(f.getDeclaringClass(), f);
659 if (webXmlInjection == null)
660 {
661 try
662 {
663 boolean bound = org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp, name, mappedName);
664 if (!bound)
665 bound = org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp.getServer(), name, mappedName);
666 if (!bound)
667 bound = org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(null, name, mappedName);
668 if (!bound)
669 {
670
671 try
672 {
673 InitialContext ic = new InitialContext();
674 String nameInEnvironment = (mappedName!=null?mappedName:name);
675 ic.lookup("java:comp/env/"+nameInEnvironment);
676 bound = true;
677 }
678 catch (NameNotFoundException e)
679 {
680 bound = false;
681 }
682 }
683
684 if (bound)
685 {
686 Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
687
688 Injection injection = new Injection();
689 injection.setTargetClass(f.getDeclaringClass());
690 injection.setJndiName(name);
691 injection.setMappingName(mappedName);
692 injection.setTarget(f);
693 _injections.add(injection);
694 }
695 else if (!isEnvEntryType(type))
696 {
697
698
699
700
701 throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
702 }
703 }
704 catch (NamingException e)
705 {
706
707
708
709 if (!isEnvEntryType(type))
710 throw new IllegalStateException(e);
711 }
712 }
713 else
714 {
715
716
717 Object value = webXmlInjection.lookupInjectedValue();
718 if (!IntrospectionUtil.isTypeCompatible(type, value.getClass(), false))
719 throw new IllegalStateException("Type of field="+type+" is not compatible with Resource type="+value.getClass());
720 }
721 }
722 }
723
724
725
726
727
728
729
730
731 private boolean isServletType (Class c)
732 {
733 boolean isServlet = false;
734 if (javax.servlet.Servlet.class.isAssignableFrom(c) ||
735 javax.servlet.Filter.class.isAssignableFrom(c) ||
736 javax.servlet.ServletContextListener.class.isAssignableFrom(c) ||
737 javax.servlet.ServletContextAttributeListener.class.isAssignableFrom(c) ||
738 javax.servlet.ServletRequestListener.class.isAssignableFrom(c) ||
739 javax.servlet.ServletRequestAttributeListener.class.isAssignableFrom(c) ||
740 javax.servlet.http.HttpSessionListener.class.isAssignableFrom(c) ||
741 javax.servlet.http.HttpSessionAttributeListener.class.isAssignableFrom(c) ||
742 (_pojoInstances.get(c) != null))
743
744 isServlet=true;
745
746 return isServlet;
747 }
748
749
750
751
752
753
754
755
756
757
758 private Object getPojoInstanceFor (Class clazz)
759 throws InstantiationException, IllegalAccessException
760 {
761 Object instance = _pojoInstances.get(clazz);
762 if (instance == null)
763 {
764 instance = clazz.newInstance();
765 _pojoInstances.put(clazz, instance);
766 }
767 return instance;
768 }
769
770 private static boolean isEnvEntryType (Class type)
771 {
772 boolean result = false;
773 for (int i=0;i<__envEntryTypes.length && !result;i++)
774 {
775 result = (type.equals(__envEntryTypes[i]));
776 }
777 return result;
778 }
779
780 protected static String normalizePattern(String p)
781 {
782 if (p!=null && p.length()>0 && !p.startsWith("/") && !p.startsWith("*"))
783 return "/"+p;
784 return p;
785 }
786 }