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