View Javadoc

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