View Javadoc

1   // ========================================================================
2   // Copyright (c) 2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
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  
20  import javax.annotation.Resource;
21  import javax.naming.InitialContext;
22  import javax.naming.NameNotFoundException;
23  import javax.naming.NamingException;
24  
25  import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
26  import org.eclipse.jetty.plus.annotation.Injection;
27  import org.eclipse.jetty.plus.annotation.InjectionCollection;
28  import org.eclipse.jetty.util.log.Log;
29  import org.eclipse.jetty.util.log.Logger;
30  import org.eclipse.jetty.webapp.MetaData;
31  import org.eclipse.jetty.webapp.WebAppContext;
32  
33  public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationHandler
34  {
35      private static final Logger LOG = Log.getLogger(ResourceAnnotationHandler.class);
36  
37      protected WebAppContext _context;
38     
39  
40      public ResourceAnnotationHandler (WebAppContext wac)
41      {
42          super(true);
43          _context = wac;
44      }
45  
46  
47      /**
48       *  Class level Resource annotations declare a name in the
49       *  environment that will be looked up at runtime. They do
50       *  not specify an injection.
51       */
52      public void doHandle(Class clazz)
53      {
54          if (Util.isServletType(clazz))
55          {
56              handleClass(clazz);
57              
58              Method[] methods = clazz.getDeclaredMethods();
59              for (int i=0; i<methods.length; i++)
60                  handleMethod(clazz, methods[i]);
61              Field[] fields = clazz.getDeclaredFields();
62              //For each field, get all of it's annotations
63              for (int i=0; i<fields.length; i++)
64                  handleField(clazz, fields[i]);
65          }
66      }
67          
68       public void handleClass (Class clazz)
69       {
70           Resource resource = (Resource)clazz.getAnnotation(Resource.class);
71           if (resource != null)
72           {
73               String name = resource.name();
74               String mappedName = resource.mappedName();
75               Resource.AuthenticationType auth = resource.authenticationType();
76               Class type = resource.type();
77               boolean shareable = resource.shareable();
78               
79               if (name==null || name.trim().equals(""))
80                   throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
81               
82               try
83               {
84                   if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name,mappedName))
85                       if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name,mappedName))
86                           throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
87               }
88               catch (NamingException e)
89               {
90                   LOG.warn(e);
91               }
92           }
93      }
94  
95      public void handleField(Class clazz, Field field)
96      {
97          Resource resource = (Resource)field.getAnnotation(Resource.class);
98          if (resource != null)
99          {
100             //JavaEE Spec 5.2.3: Field cannot be static
101             if (Modifier.isStatic(field.getModifiers()))
102             {
103                 LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+field.getName()+": cannot be static");
104                 return;
105             }
106 
107             //JavaEE Spec 5.2.3: Field cannot be final
108             if (Modifier.isFinal(field.getModifiers()))
109             {
110                 LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+field.getName()+": cannot be final");
111                 return;
112             }
113 
114             //work out default name
115             String name = clazz.getCanonicalName()+"/"+field.getName();     
116            
117             //allow @Resource name= to override the field name
118             name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
119             String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
120             //get the type of the Field
121             Class type = field.getType();
122             
123             //Servlet Spec 3.0 p. 76
124             //If a descriptor has specified at least 1 injection target for this
125             //resource, then it overrides this annotation
126             MetaData metaData = _context.getMetaData();
127             if (metaData.getOriginDescriptor("resource-ref."+name+".injection") != null)
128             {
129                 //at least 1 injection was specified for this resource by a descriptor, so
130                 //it overrides this annotation
131                 return;
132             }
133 
134             //No injections for this resource in any descriptors, so we can add it
135             //Does the injection already exist?
136             InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
137             if (injections == null)
138             {
139                 injections = new InjectionCollection();
140                 _context.setAttribute(InjectionCollection.INJECTION_COLLECTION, injections);
141             }
142             Injection injection = injections.getInjection(name, clazz, field);
143             if (injection == null)
144             {
145                 //No injection has been specified, add it
146                 try
147                 {
148                     boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name, mappedName);
149                     if (!bound)
150                         bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name, mappedName);
151                     if (!bound)
152                         bound =  org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName); 
153                     if (!bound)
154                     {
155                         //see if there is an env-entry value been bound
156                         try
157                         {
158                             InitialContext ic = new InitialContext();
159                             String nameInEnvironment = (mappedName!=null?mappedName:name);
160                             ic.lookup("java:comp/env/"+nameInEnvironment);                               
161                             bound = true;
162                         }
163                         catch (NameNotFoundException e)
164                         {
165                             bound = false;
166                         }
167                     }
168                     //Check there is a JNDI entry for this annotation 
169                     if (bound)
170                     { 
171                         LOG.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
172                         //   Make the Injection for it if the binding succeeded
173                         injection = new Injection();
174                         injection.setTarget(clazz, field, type);
175                         injection.setJndiName(name);
176                         injection.setMappingName(mappedName);
177                         injections.add(injection); 
178                         
179                         //TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination 
180                         metaData.setOrigin("resource-ref."+name+".injection");
181                     }  
182                     else if (!Util.isEnvEntryType(type))
183                     {   
184                         //if this is an env-entry type resource and there is no value bound for it, it isn't
185                         //an error, it just means that perhaps the code will use a default value instead
186                         // JavaEE Spec. sec 5.4.1.3
187 
188                         throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
189                     }
190                 }
191                 catch (NamingException e)
192                 {
193                     //if this is an env-entry type resource and there is no value bound for it, it isn't
194                     //an error, it just means that perhaps the code will use a default value instead
195                     // JavaEE Spec. sec 5.4.1.3
196                     if (!Util.isEnvEntryType(type))
197                         throw new IllegalStateException(e);
198                 }
199             }
200         }
201     }
202 
203     
204     /**
205      * Process a Resource annotation on a Method.
206      * 
207      * This will generate a JNDI entry, and an Injection to be
208      * processed when an instance of the class is created.
209      */
210     public void handleMethod(Class clazz, Method method)
211     {
212 
213         Resource resource = (Resource)method.getAnnotation(Resource.class);
214         if (resource != null)
215         {
216             /*
217              * Commons Annotations Spec 2.3
218              * " The Resource annotation is used to declare a reference to a resource.
219              *   It can be specified on a class, methods or on fields. When the 
220              *   annotation is applied on a field or method, the container will 
221              *   inject an instance of the requested resource into the application 
222              *   when the application is initialized... Even though this annotation 
223              *   is not marked Inherited, if used all superclasses MUST be examined 
224              *   to discover all uses of this annotation. All such annotation instances 
225              *   specify resources that are needed by the application. Note that this 
226              *   annotation may appear on private fields and methods of the superclasses. 
227              *   Injection of the declared resources needs to happen in these cases as 
228              *   well, even if a method with such an annotation is overridden by a subclass."
229              *  
230              *  Which IMHO, put more succinctly means "If you find a @Resource on any method
231              *  or field, inject it!".
232              */
233             //JavaEE Spec 5.2.3: Method cannot be static
234             if (Modifier.isStatic(method.getModifiers()))
235             {
236                 LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": cannot be static");
237                 return;
238             }
239 
240             // Check it is a valid javabean: must be void return type, the name must start with "set" and it must have
241             // only 1 parameter
242             if (!method.getName().startsWith("set"))
243             {
244                 LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, does not start with 'set'");
245                 return;
246             }
247 
248             if (method.getParameterTypes().length != 1)
249             {
250                 LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, not single argument to method");
251                 return; 
252             }
253 
254             if (Void.TYPE != method.getReturnType())
255             {
256                 LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, not void");
257                 return; 
258             }
259 
260 
261             //default name is the javabean property name
262             String name = method.getName().substring(3);
263             name = name.substring(0,1).toLowerCase()+name.substring(1);
264             name = clazz.getCanonicalName()+"/"+name;
265 
266             name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
267             String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
268             Class paramType = method.getParameterTypes()[0];
269 
270             Class resourceType = resource.type();
271             
272             //Servlet Spec 3.0 p. 76
273             //If a descriptor has specified at least 1 injection target for this
274             //resource, then it overrides this annotation
275             MetaData metaData = _context.getMetaData();
276             if (metaData.getOriginDescriptor("resource-ref."+name+".injection") != null)
277             {
278                 //at least 1 injection was specified for this resource by a descriptor, so
279                 //it overrides this annotation
280                 return;
281             }
282             
283             //check if an injection has already been setup for this target by web.xml
284             InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
285             if (injections == null)
286             {
287                 injections = new InjectionCollection();
288                 _context.setAttribute(InjectionCollection.INJECTION_COLLECTION, injections);
289             }
290             Injection injection = injections.getInjection(name, clazz, method, paramType);
291             if (injection == null)
292             {
293                 try
294                 {
295                     //try binding name to environment
296                     //try the webapp's environment first
297                     boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name, mappedName);
298 
299                     //try the server's environment
300                     if (!bound)
301                         bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name, mappedName);
302 
303                     //try the jvm's environment
304                     if (!bound)
305                         bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName);
306 
307                     //TODO if it is an env-entry from web.xml it can be injected, in which case there will be no
308                     //NamingEntry, just a value bound in java:comp/env
309                     if (!bound)
310                     {
311                         try
312                         {
313                             InitialContext ic = new InitialContext();
314                             String nameInEnvironment = (mappedName!=null?mappedName:name);
315                             ic.lookup("java:comp/env/"+nameInEnvironment);                               
316                             bound = true;
317                         }
318                         catch (NameNotFoundException e)
319                         {
320                             bound = false;
321                         }
322                     }
323 
324                     if (bound)
325                     {
326                         LOG.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
327                         //   Make the Injection for it
328                         injection = new Injection();
329                         injection.setTarget(clazz, method,paramType,resourceType);
330                         injection.setJndiName(name);
331                         injection.setMappingName(mappedName);
332                         injections.add(injection);
333                         //TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination 
334                         metaData.setOrigin("resource-ref."+name+".injection");
335                     } 
336                     else if (!Util.isEnvEntryType(paramType))
337                     {
338 
339                         //if this is an env-entry type resource and there is no value bound for it, it isn't
340                         //an error, it just means that perhaps the code will use a default value instead
341                         // JavaEE Spec. sec 5.4.1.3   
342                         throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
343                     }
344                 }
345                 catch (NamingException e)
346                 {  
347                     //if this is an env-entry type resource and there is no value bound for it, it isn't
348                     //an error, it just means that perhaps the code will use a default value instead
349                     // JavaEE Spec. sec 5.4.1.3
350                     if (!Util.isEnvEntryType(paramType))
351                         throw new IllegalStateException(e);
352                 }
353             }
354 
355         }
356     }
357 }