1   // ========================================================================
2   // $Id: AnnotationProcessor.java 3736 2008-10-04 22:19:26Z gregw $
3   // Copyright 2008 Mort Bay Consulting Pty. Ltd.
4   // ------------------------------------------------------------------------
5   // Licensed under the Apache License, Version 2.0 (the "License");
6   // you may not use this file except in compliance with the License.
7   // You may obtain a copy of the License at 
8   // http://www.apache.org/licenses/LICENSE-2.0
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
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   * AnnotationProcessor
66   *
67   * Act on the annotations discovered in the webapp.
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         //@Servlet(urlMappings=String[], description=String, icon=String, loadOnStartup=int, name=String, initParams=InitParams[])
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         //@ServletFilter(description=String, filterName=String, displayName=String, icon=String,initParams=InitParam[], filterMapping=FilterMapping)
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         //@ServletContextListener(description=String)
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         //      TODO: check that the same class does not have more than one
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         //TODO: check that the same class does not have more than one
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      * Process @Resources annotation on classes
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             //Handle Resources annotation - add namespace entries
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                     //TODO don't ignore the shareable, auth etc etc
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      *  Class level Resource annotations declare a name in the
415      *  environment that will be looked up at runtime. They do
416      *  not specify an injection.
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             //Handle Resource annotation - add namespace entries
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                    //TODO don't ignore the shareable, auth etc etc
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      * Process a Resource annotation on the Methods.
459      * 
460      * This will generate a JNDI entry, and an Injection to be
461      * processed when an instance of the class is created.
462      * @param injections
463      */
464     public void processMethodResourceAnnotations ()
465     throws Exception
466     {
467         //Get all methods that have a Resource annotation
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              * Commons Annotations Spec 2.3
479              * " The Resource annotation is used to declare a reference to a resource.
480              *   It can be specified on a class, methods or on fields. When the 
481              *   annotation is applied on a field or method, the container will 
482              *   inject an instance of the requested resource into the application 
483              *   when the application is initialized... Even though this annotation 
484              *   is not marked Inherited, if used all superclasses MUST be examined 
485              *   to discover all uses of this annotation. All such annotation instances 
486              *   specify resources that are needed by the application. Note that this 
487              *   annotation may appear on private fields and methods of the superclasses. 
488              *   Injection of the declared resources needs to happen in these cases as 
489              *   well, even if a method with such an annotation is overridden by a subclass."
490              *  
491              *  Which IMHO, put more succinctly means "If you find a @Resource on any method
492              *  or field, inject it!".
493              */
494             Resource resource = (Resource)m.getAnnotation(Resource.class);
495             if (resource == null)
496                 continue;
497 
498             //JavaEE Spec 5.2.3: Method cannot be static
499             if (Modifier.isStatic(m.getModifiers()))
500                 throw new IllegalStateException(m+" cannot be static");
501 
502 
503             // Check it is a valid javabean 
504             if (!IntrospectionUtil.isJavaBeanCompliantSetter(m))
505                 throw new IllegalStateException(m+" is not a java bean compliant setter method");
506 
507             //default name is the javabean property name
508             String name = m.getName().substring(3);
509             name = name.substring(0,1).toLowerCase()+name.substring(1);
510             name = m.getDeclaringClass().getCanonicalName()+"/"+name;
511             //allow default name to be overridden
512             name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
513             //get the mappedName if there is one
514             String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
515 
516             Class type = m.getParameterTypes()[0];
517 
518             //get other parts that can be specified in @Resource
519             Resource.AuthenticationType auth = resource.authenticationType();
520             boolean shareable = resource.shareable();
521 
522             //if @Resource specifies a type, check it is compatible with setter param
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             //check if an injection has already been setup for this target by web.xml
531             Injection webXmlInjection = _injections.getInjection(m.getDeclaringClass(), m);
532             if (webXmlInjection == null)
533             {
534                 try
535                 {
536                     //try binding name to environment
537                     //try the webapp's environment first
538                     boolean bound = org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp, name, mappedName);
539                     
540                     //try the server's environment
541                     if (!bound)
542                         bound = org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(_webApp.getServer(), name, mappedName);
543                     
544                     //try the jvm's environment
545                     if (!bound)
546                         bound = org.mortbay.jetty.plus.naming.NamingEntryUtil.bindToENC(null, name, mappedName);
547                     
548                     //TODO if it is an env-entry from web.xml it can be injected, in which case there will be no
549                     //NamingEntry, just a value bound in java:comp/env
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                         //   Make the Injection for it
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                         //if this is an env-entry type resource and there is no value bound for it, it isn't
580                         //an error, it just means that perhaps the code will use a default value instead
581                         // JavaEE Spec. sec 5.4.1.3   
582                         throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
583                     }
584                 }
585                 catch (NamingException e)
586                 {  
587                     //if this is an env-entry type resource and there is no value bound for it, it isn't
588                     //an error, it just means that perhaps the code will use a default value instead
589                     // JavaEE Spec. sec 5.4.1.3
590                     if (!isEnvEntryType(type))
591                         throw new IllegalStateException(e);
592                 }
593             }
594             else
595             {
596                 //if an injection is already set up for this name, then the types must be compatible
597                 //JavaEE spec sec 5.2.4
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      * Process @Resource annotation for a Field. These will both set up a
608      * JNDI entry and generate an Injection. Or they can be the equivalent
609      * of env-entries with default values
610      * 
611      * @param injections
612      */
613     public void processFieldResourceAnnotations ()
614     throws Exception
615     {
616         //Get all fields that have a Resource annotation
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             //JavaEE Spec 5.2.3: Field cannot be static
630             if (Modifier.isStatic(f.getModifiers()))
631                 throw new IllegalStateException(f+" cannot be static");
632 
633             //JavaEE Spec 5.2.3: Field cannot be final
634             if (Modifier.isFinal(f.getModifiers()))
635                 throw new IllegalStateException(f+" cannot be final");
636 
637             //work out default name
638             String name = f.getDeclaringClass().getCanonicalName()+"/"+f.getName();
639             //allow @Resource name= to override the field name
640             name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
641 
642             //get the type of the Field
643             Class type = f.getType();
644             //if @Resource specifies a type, check it is compatible with field type
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             //get the mappedName if there is one
653             String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
654             //get other parts that can be specified in @Resource
655             Resource.AuthenticationType auth = resource.authenticationType();
656             boolean shareable = resource.shareable();
657             //check if an injection has already been setup for this target by web.xml
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                         //see if there is an env-entry value been bound from web.xml
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                     //Check there is a JNDI entry for this annotation 
684                     if (bound)
685                     { 
686                         Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
687                         //   Make the Injection for it if the binding succeeded
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                         //if this is an env-entry type resource and there is no value bound for it, it isn't
698                         //an error, it just means that perhaps the code will use a default value instead
699                         // JavaEE Spec. sec 5.4.1.3
700 
701                         throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
702                     }
703                 }
704                 catch (NamingException e)
705                 {
706                     //if this is an env-entry type resource and there is no value bound for it, it isn't
707                     //an error, it just means that perhaps the code will use a default value instead
708                     // JavaEE Spec. sec 5.4.1.3
709                     if (!isEnvEntryType(type))
710                         throw new IllegalStateException(e);
711                 }
712             }
713             else
714             {
715                 //if an injection is already set up for this name, then the types must be compatible
716                 //JavaEE spec sec 5.2.4
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      * Check if the presented method belongs to a class that is one
727      * of the classes with which a servlet container should be concerned.
728      * @param m
729      * @return
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      * Get an already-created instance of a pojo, or create one
752      * otherwise.
753      * @param clazz
754      * @return
755      * @throws InstantiationException
756      * @throws IllegalAccessException
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 }