View Javadoc

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