View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.config.annotation;
20  
21  import javax.naming.NamingException;
22  import javax.naming.Context;
23  import javax.annotation.Resource;
24  import java.lang.reflect.Method;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Field;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.WeakHashMap;
30  import org.apache.myfaces.shared.util.ClassUtils;
31  
32  // TODO @Resources
33  public class ResourceAnnotationLifecycleProvider extends NoInjectionAnnotationLifecycleProvider
34  {
35      /**
36       * Cache the Method instances per ClassLoader using the Class-Name.
37       * NOTE that we do it this way, because the only other valid way in order to support a shared
38       * classloader scenario would be to use a WeakHashMap<Class<?>, Method[]>, but this
39       * creates a cyclic reference between the key and the value of the WeakHashMap which will
40       * most certainly cause a memory leak! Furthermore we can manually cleanup the Map when
41       * the webapp is undeployed just by removing the Map for the current ClassLoader. 
42       */
43      private volatile static WeakHashMap<ClassLoader, Map<Class,Field[]> > declaredFieldBeans = 
44              new WeakHashMap<ClassLoader, Map<Class, Field[]>>();
45  
46      protected Context context;
47      private static final String JAVA_COMP_ENV = "java:comp/env/";
48  
49      public ResourceAnnotationLifecycleProvider(Context context)
50      {
51          this.context = context;
52      }
53  
54      private static Map<Class,Field[]> getDeclaredFieldBeansMap()
55      {
56          ClassLoader cl = ClassUtils.getContextClassLoader();
57          
58          Map<Class,Field[]> metadata = (Map<Class,Field[]>)
59                  declaredFieldBeans.get(cl);
60  
61          if (metadata == null)
62          {
63              // Ensure thread-safe put over _metadata, and only create one map
64              // per classloader to hold metadata.
65              synchronized (declaredFieldBeans)
66              {
67                  metadata = createDeclaredFieldBeansMap(cl, metadata);
68              }
69          }
70  
71          return metadata;
72      }
73      
74      private static Map<Class,Field[]> createDeclaredFieldBeansMap(
75              ClassLoader cl, Map<Class,Field[]> metadata)
76      {
77          metadata = (Map<Class,Field[]>) declaredFieldBeans.get(cl);
78          if (metadata == null)
79          {
80              metadata = new HashMap<Class,Field[]>();
81              declaredFieldBeans.put(cl, metadata);
82          }
83          return metadata;
84      }
85  
86      /**
87       * Inject resources in specified instance.
88       */
89      @Override
90      protected void processAnnotations(Object instance)
91              throws IllegalAccessException, InvocationTargetException, NamingException
92      {
93  
94          if (context == null)
95          {
96              // No resource injection
97              return;
98          }
99  
100         checkAnnotation(instance.getClass(), instance);
101 
102         /* 
103          * May be only check non private fields and methods
104          * for @Resource (JSR 250), if used all superclasses MUST be examined
105          * to discover all uses of this annotation.
106          */
107 
108         Class superclass = instance.getClass().getSuperclass();
109         while (superclass != null && (!superclass.equals(Object.class)))
110         {
111             checkAnnotation(superclass, instance);
112             superclass = superclass.getSuperclass();
113         } 
114     }
115     
116     Field[] getDeclaredFieldBeans(Class clazz)
117     {
118         Map<Class,Field[]> declaredFieldBeansMap = getDeclaredFieldBeansMap();
119         Field[] fields = declaredFieldBeansMap.get(clazz);
120         if (fields == null)
121         {
122             fields = clazz.getDeclaredFields();
123             synchronized(declaredFieldBeansMap)
124             {
125                 declaredFieldBeansMap.put(clazz, fields);
126             }
127         }
128         return fields;
129     }
130 
131     private void checkAnnotation(Class<?> clazz, Object instance)
132             throws NamingException, IllegalAccessException, InvocationTargetException
133     {
134         // Initialize fields annotations
135         Field[] fields = getDeclaredFieldBeans(clazz);
136         for (int i = 0; i < fields.length; i++)
137         {
138             Field field = fields[i];
139             checkFieldAnnotation(field, instance);
140         }
141 
142         // Initialize methods annotations
143         Method[] methods = getDeclaredMethods(clazz);
144         for (int i = 0; i < methods.length; i++)
145         {
146             Method method = methods[i];
147             checkMethodAnnotation(method, instance);
148         }
149     }
150 
151     protected void checkMethodAnnotation(Method method, Object instance)
152             throws NamingException, IllegalAccessException, InvocationTargetException
153     {
154         if (method.isAnnotationPresent(Resource.class))
155         {
156             Resource annotation = method.getAnnotation(Resource.class);
157             lookupMethodResource(context, instance, method, annotation.name());
158         }
159     }
160 
161     protected void checkFieldAnnotation(Field field, Object instance)
162             throws NamingException, IllegalAccessException
163     {
164         if (field.isAnnotationPresent(Resource.class))
165         {
166             Resource annotation = field.getAnnotation(Resource.class);
167             lookupFieldResource(context, instance, field, annotation.name());
168         }
169     }
170 
171     /**
172      * Inject resources in specified field.
173      */
174     protected static void lookupFieldResource(javax.naming.Context context,
175             Object instance, Field field, String name)
176             throws NamingException, IllegalAccessException
177     {
178 
179         Object lookedupResource;
180 
181         if ((name != null) && (name.length() > 0))
182         {
183             // TODO local or global JNDI
184             lookedupResource = context.lookup(JAVA_COMP_ENV + name);
185         }
186         else
187         {
188             // TODO local or global JNDI 
189             lookedupResource = context.lookup(JAVA_COMP_ENV + instance.getClass().getName() + "/" + field.getName());
190         }
191 
192         boolean accessibility = field.isAccessible();
193         field.setAccessible(true);
194         field.set(instance, lookedupResource);
195         field.setAccessible(accessibility);
196     }
197 
198 
199     /**
200      * Inject resources in specified method.
201      */
202     protected static void lookupMethodResource(javax.naming.Context context,
203             Object instance, Method method, String name)
204             throws NamingException, IllegalAccessException, InvocationTargetException
205     {
206 
207         if (!method.getName().startsWith("set")
208                 || method.getParameterTypes().length != 1
209                 || !method.getReturnType().getName().equals("void"))
210         {
211             throw new IllegalArgumentException("Invalid method resource injection annotation");
212         }
213 
214         Object lookedupResource;
215 
216         if ((name != null) && (name.length() > 0))
217         {
218             // TODO local or global JNDI
219             lookedupResource = context.lookup(JAVA_COMP_ENV + name);
220         }
221         else
222         {
223             // TODO local or global JNDI
224             lookedupResource =
225                     context.lookup(JAVA_COMP_ENV + instance.getClass().getName() + "/" + getFieldName(method));
226         }
227 
228         boolean accessibility = method.isAccessible();
229         method.setAccessible(true);
230         method.invoke(instance, lookedupResource);
231         method.setAccessible(accessibility);
232     }
233 
234     /**
235      * Returns the field name for the given Method.
236      * E.g. setName() will be "name". 
237      *
238      * @param setter the setter method
239      * @return the field name of the given setter method
240      */
241     protected static String getFieldName(Method setter)
242     {
243         StringBuilder name = new StringBuilder(setter.getName());
244 
245         // remove 'set'
246         name.delete(0, 3);
247 
248         // lowercase first char
249         name.setCharAt(0, Character.toLowerCase(name.charAt(0)));
250 
251         return name.toString();
252     }
253 
254 }