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