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  import java.util.List;
20  
21  import javax.annotation.Resource;
22  import javax.naming.InitialContext;
23  import javax.naming.NameNotFoundException;
24  import javax.naming.NamingException;
25  
26  import org.eclipse.jetty.annotations.AnnotationParser.AnnotationHandler;
27  import org.eclipse.jetty.annotations.AnnotationParser.Value;
28  import org.eclipse.jetty.plus.annotation.Injection;
29  import org.eclipse.jetty.plus.annotation.InjectionCollection;
30  import org.eclipse.jetty.util.IntrospectionUtil;
31  import org.eclipse.jetty.util.Loader;
32  import org.eclipse.jetty.util.log.Log;
33  import org.eclipse.jetty.webapp.WebAppContext;
34  
35  public class ResourceAnnotationHandler implements AnnotationHandler
36  {
37      protected WebAppContext _wac;
38  
39      public ResourceAnnotationHandler (WebAppContext wac)
40      {
41          _wac = 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 handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
51                              List<Value> values)
52      {
53          Class clazz = null;
54          try
55          {
56              clazz = Loader.loadClass(null, className);
57          }
58          catch (Exception e)
59          {
60              Log.warn(e);
61          }
62          if (!Util.isServletType(clazz))
63          {
64              Log.debug("Ignoring @Resource annotation on on-servlet type class "+clazz.getName());
65              return;
66          }
67  
68          //Handle Resource annotation - add namespace entries
69          Resource resource = (Resource)clazz.getAnnotation(Resource.class);
70          if (resource != null)
71          {
72              String name = resource.name();
73              String mappedName = resource.mappedName();
74              Resource.AuthenticationType auth = resource.authenticationType();
75              Class type = resource.type();
76              boolean shareable = resource.shareable();
77  
78              if (name==null || name.trim().equals(""))
79                  throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
80  
81              try
82              {
83                  //TODO don't ignore the shareable, auth etc etc
84                  if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac, name,mappedName))
85                      if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac.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(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
96                              List<Value> values)
97      {
98          InjectionCollection injections = (InjectionCollection)_wac.getAttribute(InjectionCollection.INJECTION_COLLECTION);
99          Class clazz = null;
100         try
101         {
102             clazz = Loader.loadClass(null, className);      
103             Field f = clazz.getDeclaredField(fieldName);
104 
105             if (!Util.isServletType(clazz))
106             {
107                 Log.debug("Ignoring @Resource annotation on on-servlet type field "+fieldName);
108                 return;
109             }
110             Resource resource = (Resource)f.getAnnotation(Resource.class);
111             if (resource == null)
112                 return;
113 
114             //JavaEE Spec 5.2.3: Field cannot be static
115             if (Modifier.isStatic(f.getModifiers()))
116                 throw new IllegalStateException(f+" cannot be static");
117 
118             //JavaEE Spec 5.2.3: Field cannot be final
119             if (Modifier.isFinal(f.getModifiers()))
120                 throw new IllegalStateException(f+" cannot be final");
121 
122             //work out default name
123             String name = f.getDeclaringClass().getCanonicalName()+"/"+f.getName();
124             //allow @Resource name= to override the field name
125             name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
126 
127             //get the type of the Field
128             Class type = f.getType();
129             //if @Resource specifies a type, check it is compatible with field type
130             if ((resource.type() != null)
131                     && 
132                     !resource.type().equals(Object.class)
133                     &&
134                     (!IntrospectionUtil.isTypeCompatible(type, resource.type(), false)))
135                 throw new IllegalStateException("@Resource incompatible type="+resource.type()+ " with field type ="+f.getType());
136 
137             //get the mappedName if there is one
138             String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
139             //get other parts that can be specified in @Resource
140             Resource.AuthenticationType auth = resource.authenticationType();
141             boolean shareable = resource.shareable();
142             //check if an injection has already been setup for this target by web.xml
143             Injection webXmlInjection = injections.getInjection(f.getDeclaringClass(), f);
144             if (webXmlInjection == null)
145             {
146                 try
147                 {
148                     boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac, name, mappedName);
149                     if (!bound)
150                         bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac.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 from web.xml
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 injection = new Injection();
174                         injection.setTargetClass(f.getDeclaringClass());
175                         injection.setJndiName(name);
176                         injection.setMappingName(mappedName);
177                         injection.setTarget(f);
178                         injections.add(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             else
199             {
200                 //if an injection is already set up for this name, then the types must be compatible
201                 //JavaEE spec sec 5.2.4
202                 Object val = webXmlInjection.lookupInjectedValue();
203                 if (!IntrospectionUtil.isTypeCompatible(type, value.getClass(), false))
204                     throw new IllegalStateException("Type of field="+type+" is not compatible with Resource type="+val.getClass());
205             }
206         }
207         catch (Exception e)
208         {
209             Log.warn(e);
210         }
211     }
212 
213     /**
214      * Process a Resource annotation on a Method.
215      * 
216      * This will generate a JNDI entry, and an Injection to be
217      * processed when an instance of the class is created.
218      * @param injections
219      */
220     public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
221                              List<Value> values)
222     {
223         InjectionCollection injections = (InjectionCollection)_wac.getAttribute(InjectionCollection.INJECTION_COLLECTION);
224         Class clazz = null;
225         try
226         {
227             clazz = Loader.loadClass(null, className);
228 
229             Class[] args = Util.convertTypes(params); 
230             Method m = clazz.getDeclaredMethod(methodName, args);
231 
232             if (!Util.isServletType(m.getDeclaringClass()))
233             {
234                 Log.debug("Ignoring @Resource annotation on on-servlet type method "+m.getName());
235                 return;
236             }
237             /*
238              * Commons Annotations Spec 2.3
239              * " The Resource annotation is used to declare a reference to a resource.
240              *   It can be specified on a class, methods or on fields. When the 
241              *   annotation is applied on a field or method, the container will 
242              *   inject an instance of the requested resource into the application 
243              *   when the application is initialized... Even though this annotation 
244              *   is not marked Inherited, if used all superclasses MUST be examined 
245              *   to discover all uses of this annotation. All such annotation instances 
246              *   specify resources that are needed by the application. Note that this 
247              *   annotation may appear on private fields and methods of the superclasses. 
248              *   Injection of the declared resources needs to happen in these cases as 
249              *   well, even if a method with such an annotation is overridden by a subclass."
250              *  
251              *  Which IMHO, put more succinctly means "If you find a @Resource on any method
252              *  or field, inject it!".
253              */
254             Resource resource = (Resource)m.getAnnotation(Resource.class);
255             if (resource == null)
256                 return;
257 
258             //JavaEE Spec 5.2.3: Method cannot be static
259             if (Modifier.isStatic(m.getModifiers()))
260                 throw new IllegalStateException(m+" cannot be static");
261 
262 
263             // Check it is a valid javabean 
264             if (!IntrospectionUtil.isJavaBeanCompliantSetter(m))
265                 throw new IllegalStateException(m+" is not a java bean compliant setter method");
266 
267             //default name is the javabean property name
268             String name = m.getName().substring(3);
269             name = name.substring(0,1).toLowerCase()+name.substring(1);
270             name = m.getDeclaringClass().getCanonicalName()+"/"+name;
271             //allow default name to be overridden
272             name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
273             //get the mappedName if there is one
274             String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
275 
276             Class type = m.getParameterTypes()[0];
277 
278             //get other parts that can be specified in @Resource
279             Resource.AuthenticationType auth = resource.authenticationType();
280             boolean shareable = resource.shareable();
281 
282             //if @Resource specifies a type, check it is compatible with setter param
283             if ((resource.type() != null) 
284                     && 
285                     !resource.type().equals(Object.class)
286                     &&
287                     (!IntrospectionUtil.isTypeCompatible(type, resource.type(), false)))
288                 throw new IllegalStateException("@Resource incompatible type="+resource.type()+ " with method param="+type+ " for "+m);
289 
290             //check if an injection has already been setup for this target by web.xml
291             Injection webXmlInjection = injections.getInjection(m.getDeclaringClass(), m);
292             if (webXmlInjection == 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(_wac, name, mappedName);
299 
300                     //try the server's environment
301                     if (!bound)
302                         bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac.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 injection = new Injection();
330                         injection.setTargetClass(m.getDeclaringClass());
331                         injection.setJndiName(name);
332                         injection.setMappingName(mappedName);
333                         injection.setTarget(m);
334                         injections.add(injection);
335                     } 
336                     else if (!Util.isEnvEntryType(type))
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(type))
351                         throw new IllegalStateException(e);
352                 }
353             }
354             else
355             {
356                 //if an injection is already set up for this name, then the types must be compatible
357                 //JavaEE spec sec 5.2.4
358 
359                 Object value = webXmlInjection.lookupInjectedValue();
360                 if (!IntrospectionUtil.isTypeCompatible(type, value.getClass(), false))
361                     throw new IllegalStateException("Type of field="+type+" is not compatible with Resource type="+value.getClass());
362             }
363         }
364         catch (Exception e)
365         {
366             Log.warn(e);
367         }
368     }
369 }