View Javadoc
1   package org.apache.turbine.annotation;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.lang.annotation.Annotation;
23  import java.lang.reflect.AccessibleObject;
24  import java.lang.reflect.Field;
25  import java.util.List;
26  import java.util.concurrent.ConcurrentHashMap;
27  import java.util.concurrent.ConcurrentMap;
28  
29  import org.apache.commons.configuration2.Configuration;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.fulcrum.security.model.turbine.TurbineAccessControlList;
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.Logger;
34  import org.apache.turbine.Turbine;
35  import org.apache.turbine.modules.Loader;
36  import org.apache.turbine.services.ServiceManager;
37  import org.apache.turbine.services.TurbineServices;
38  import org.apache.turbine.services.assemblerbroker.AssemblerBrokerService;
39  import org.apache.turbine.util.TurbineException;
40  
41  /**
42   * AnnotationProcessor contains static helper methods that handle the
43   * Turbine annotations for objects
44   *
45   * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
46   * @version $Id: TurbineAssemblerBrokerService.java 1521103 2013-09-09 13:38:07Z tv $
47   */
48  public class AnnotationProcessor
49  {
50      /** Logging */
51      private static Logger log = LogManager.getLogger(AnnotationProcessor.class);
52  
53      /** Annotation cache */
54      private static ConcurrentMap<String, Annotation[]> annotationCache = new ConcurrentHashMap<String, Annotation[]>();
55  
56      /**
57       * Get cached annotations for field, class or method
58       *
59       * @param object a field, class or method
60       *
61       * @return the declared annotations for the object
62       */
63      public static Annotation[] getAnnotations(AccessibleObject object)
64      {
65          String key = object.getClass() + object.toString();
66          Annotation[] annotations = annotationCache.get(key);
67          if (annotations == null)
68          {
69              Annotation[] newAnnotations = object.getDeclaredAnnotations();
70              annotations = annotationCache.putIfAbsent(key, newAnnotations);
71              if (annotations == null)
72              {
73                  annotations = newAnnotations;
74              }
75          }
76          return annotations;
77      }
78  
79      public enum ConditionType
80      {
81          COMPOUND, ANY;
82      }
83  
84      /**
85       * Check if the object given is authorized to be executed based on its annotations
86       *
87       * The method will return false if one of the annotations denies execution
88       *
89       * @see #isAuthorized(AccessibleObject, TurbineAccessControlList, ConditionType)
90       *
91       * @param <A> ACL instance
92       * @param object accessible object to test
93       * @param acl access control list
94       * @return true if the execution is allowed
95       */
96      public static <A extends TurbineAccessControlList<?>> boolean isAuthorized(AccessibleObject object, A acl)
97      {
98          return isAuthorized( object, acl, ConditionType.COMPOUND );
99      }
100 
101     /**
102      * Check if the object given is authorized to be executed based on its annotations
103      * The method's return value depends on the conditonType, refer to the ConditionType
104      *
105      * @param <A> ACL instance
106      * @param object the object
107      * @param acl access control list
108      * @param conditonType either {@link ConditionType#COMPOUND}: The method will return false if one of the annotations denies execution
109      *                     or {@link ConditionType#ANY} : The method will return true if one of the annotations allows execution
110      * @return true if the execution is allowed
111      */
112     public static <A extends TurbineAccessControlList<?>> boolean isAuthorized(AccessibleObject object, A acl, ConditionType conditonType)
113     {
114         Annotation[] annotations = getAnnotations(object);
115 
116         for (Annotation annotation : annotations)
117         {
118             if (annotation instanceof TurbineRequiredRole)
119             {
120                 TurbineRequiredRole trr = (TurbineRequiredRole) annotation;
121                 String[] roleNames = trr.value();
122                 String group = trr.group();
123 
124                 if (StringUtils.isEmpty(group)) // global group
125                 {
126                     for (String roleName : roleNames)
127                     {
128                         switch ( conditonType ) {
129                             case COMPOUND: default:
130                                 if (!acl.hasRole(roleName))
131                                 {
132                                     return false;
133                                 }
134                                 break;
135                             case ANY:
136                                 if (acl.hasRole(roleName))
137                                 {
138                                     return true;
139                                 }
140                                 break;
141                         }
142                     }
143                     if (conditonType == ConditionType.ANY) { // nothing matched
144                         return false;
145                     }
146                 }
147                 else
148                 {
149                     for (String roleName : roleNames)
150                     {
151                         switch ( conditonType ) {
152                             case COMPOUND: default:
153                                 if (!acl.hasRole(roleName, group))
154                                 {
155                                     return false;
156                                 }
157                                 break;
158                             case ANY:
159                                 if (acl.hasRole(roleName, group))
160                                 {
161                                     return true;
162                                 }
163                                 break;
164                         }
165                     }
166                 }
167             }
168             else if (annotation instanceof TurbineRequiredPermission)
169             {
170                 TurbineRequiredPermission trp = (TurbineRequiredPermission) annotation;
171                 String[] permissionNames = trp.value();
172                 String group = trp.group();
173 
174                 if (StringUtils.isEmpty(group)) // global group
175                 {
176                     for (String permissionName : permissionNames)
177                     {
178                         switch ( conditonType ) {
179                             case COMPOUND: default:
180                                 if (!acl.hasPermission(permissionName))
181                                 {
182                                     return false;
183                                 }
184                                 break;
185                             case ANY:
186                                 if (acl.hasPermission(permissionName))
187                                 {
188                                     return true;
189                                 }
190                                 break;
191                         }
192                     }
193                 }
194                 else
195                 {
196                     for (String permissionName : permissionNames)
197                     {
198                         switch ( conditonType ) {
199                             case COMPOUND: default:
200                                 if (!acl.hasPermission(permissionName, group))
201                                 {
202                                     return false;
203                                 }
204                                 break;
205                             case ANY:
206                                 if (acl.hasPermission(permissionName, group))
207                                 {
208                                     return true;
209                                 }
210                                 break;
211                         }
212 
213                     }
214                 }
215             }
216         }
217 
218         return true;
219     }
220 
221     /**
222      * Search for annotated fields of the object and inject the appropriate
223      * objects
224      *
225      * @param object the object
226      * @throws TurbineException if the objects could not be injected
227      */
228     public static void process(Object object) throws TurbineException
229     {
230         ServiceManager manager = null;
231         Configuration config = null;
232         AssemblerBrokerService assembler = null;
233         Class<?> clazz = object.getClass();
234 
235         while (clazz != null)
236         {
237             Field[] fields = clazz.getDeclaredFields();
238 
239             for (Field field : fields)
240             {
241                 Annotation[] annotations = getAnnotations(field);
242 
243                 for (Annotation a : annotations)
244                 {
245                     if (a instanceof TurbineService)
246                     {
247                         if (manager == null)
248                         {
249                             manager = TurbineServices.getInstance();
250                         }
251                         injectTurbineService(object, manager, field, (TurbineService) a);
252                     }
253                     else if (a instanceof TurbineConfiguration)
254                     {
255                         if (config == null)
256                         {
257                             config = Turbine.getConfiguration();
258                         }
259                         injectTurbineConfiguration(object, config, field, (TurbineConfiguration) a);
260                     }
261                     else if (a instanceof TurbineLoader)
262                     {
263                         if (assembler == null)
264                         {
265                             assembler = (AssemblerBrokerService) TurbineServices.getInstance().
266                                 getService(AssemblerBrokerService.SERVICE_NAME);
267                         }
268                         injectTurbineLoader(object, assembler, field, (TurbineLoader) a);
269                     }
270                 }
271             }
272 
273             clazz = clazz.getSuperclass();
274         }
275     }
276 
277     /**
278      * Inject Turbine configuration into field of object
279      *
280      * @param object the object to process
281      * @param assembler AssemblerBrokerService, provides the loader
282      * @param field the field
283      * @param annotation the value of the annotation
284      *
285      * @throws TurbineException if loader cannot be set
286      */
287     private static void injectTurbineLoader(Object object, AssemblerBrokerService assembler, Field field, TurbineLoader annotation) throws TurbineException
288     {
289         Loader<?> loader = assembler.getLoader(annotation.value());
290         field.setAccessible(true);
291 
292         try
293         {
294             log.debug("Injection of {} into object {}", loader, object);
295 
296             field.set(object, loader);
297         }
298         catch (IllegalArgumentException | IllegalAccessException e)
299         {
300             throw new TurbineException("Could not inject loader "
301                     + loader + " into object " + object, e);
302         }
303     }
304 
305     /**
306      * Inject Turbine configuration into field of object
307      *
308      * @param object the object to process
309      * @param conf the configuration to use
310      * @param field the field
311      * @param annotation the value of the annotation
312      *
313      * @throws TurbineException if configuration cannot be set
314      */
315     @SuppressWarnings("boxing")
316     private static void injectTurbineConfiguration(Object object, Configuration conf, Field field, TurbineConfiguration annotation) throws TurbineException
317     {
318         Class<?> type = field.getType();
319         String key = annotation.value();
320 
321         try
322         {
323             if (Configuration.class.isAssignableFrom(type))
324             {
325                 final Configuration injectConfiguration;
326                 // Check for annotation value
327                 if (StringUtils.isNotEmpty(key))
328                 {
329                     injectConfiguration = conf.subset(key);
330                 }
331                 else
332                 {
333                     injectConfiguration = conf;
334                 }
335 
336                 log.debug("Injection of {} into object {}", injectConfiguration, object);
337 
338                 field.setAccessible(true);
339                 field.set(object, injectConfiguration);
340             }
341             else if (conf.containsKey(key))
342             {
343                 if ( String.class.isAssignableFrom( type ) )
344                 {
345                     String value = conf.getString(key);
346                     log.debug("Injection of {} into object {}", value, object);
347 
348                     field.setAccessible(true);
349                     field.set(object, value);
350                 }
351                 else if ( Boolean.TYPE.isAssignableFrom( type ) )
352                 {
353                     boolean value = conf.getBoolean(key);
354                     log.debug("Injection of {} into object {}", value, object);
355 
356                     field.setAccessible(true);
357                     field.setBoolean(object, value);
358                 }
359                 else if ( Integer.TYPE.isAssignableFrom( type ) )
360                 {
361                     int value = conf.getInt(key);
362                     log.debug("Injection of {} into object {}", value, object);
363 
364                     field.setAccessible(true);
365                     field.setInt(object, value);
366                 }
367                 else if ( Long.TYPE.isAssignableFrom( type ) )
368                 {
369                     long value = conf.getLong(key);
370                     log.debug("Injection of {} into object {}", value, object);
371 
372                     field.setAccessible(true);
373                     field.setLong(object, value);
374                 }
375                 else if ( Short.TYPE.isAssignableFrom( type ) )
376                 {
377                     short value = conf.getShort(key);
378                     log.debug("Injection of {} into object {}", value, object);
379 
380                     field.setAccessible(true);
381                     field.setShort(object, value);
382                 }
383                 else if ( Long.TYPE.isAssignableFrom( type ) )
384                 {
385                     long value = conf.getLong(key);
386                     log.debug("Injection of {} into object {}", value, object);
387 
388                     field.setAccessible(true);
389                     field.setLong(object, value);
390                 }
391                 else if ( Float.TYPE.isAssignableFrom( type ) )
392                 {
393                     float value = conf.getFloat(key);
394                     log.debug("Injection of {} into object {}", value, object);
395 
396                     field.setAccessible(true);
397                     field.setFloat(object, value);
398                 }
399                 else if ( Double.TYPE.isAssignableFrom( type ) )
400                 {
401                     double value = conf.getDouble(key);
402                     log.debug("Injection of {} into object {}", value, object);
403 
404                     field.setAccessible(true);
405                     field.setDouble(object, value);
406                 }
407                 else if ( Byte.TYPE.isAssignableFrom( type ) )
408                 {
409                     byte value = conf.getByte(key);
410                     log.debug("Injection of {} into object {}", value, object);
411 
412                     field.setAccessible(true);
413                     field.setByte(object, value);
414                 }
415                 else if ( List.class.isAssignableFrom( type ) )
416                 {
417                     List<Object> values = conf.getList(key);
418                     log.debug("Injection of {} into object {}", values, object);
419 
420                     field.setAccessible(true);
421                     field.set(object, values);
422                 }
423             }
424         }
425         catch (IllegalArgumentException | IllegalAccessException e)
426         {
427             throw new TurbineException("Could not inject configuration "
428                     + conf + " into object " + object, e);
429         }
430     }
431 
432     /**
433      * Inject Turbine service into field of object
434      *
435      * @param object the object to process
436      * @param manager the service manager
437      * @param field the field
438      * @param annotation the value of the annotation
439      *
440      * @throws TurbineException if service is not available
441      */
442     private static void injectTurbineService(Object object, ServiceManager manager, Field field, TurbineService annotation) throws TurbineException
443     {
444         String serviceName = null;
445         // Check for annotation value
446         if (StringUtils.isNotEmpty(annotation.value()))
447         {
448             serviceName = annotation.value();
449         }
450         // Check for fields SERVICE_NAME and ROLE
451         else
452         {
453             Field[] typeFields = field.getType().getFields();
454             for (Field f : typeFields)
455             {
456                 if (TurbineService.SERVICE_NAME.equals(f.getName()))
457                 {
458                     try
459                     {
460                         serviceName = (String)f.get(null);
461                     }
462                     catch (IllegalArgumentException | IllegalAccessException e)
463                     {
464                         continue;
465                     }
466                     break;
467                 }
468                 else if (TurbineService.ROLE.equals(f.getName()))
469                 {
470                     try
471                     {
472                         serviceName = (String)f.get(null);
473                     }
474                     catch (IllegalArgumentException | IllegalAccessException e)
475                     {
476                         continue;
477                     }
478                     break;
479                 }
480             }
481         }
482 
483         if (StringUtils.isEmpty(serviceName))
484         {
485             // Try interface class name
486             serviceName = field.getType().getName();
487         }
488 
489         log.debug("Looking up service for injection: {} for object {}", serviceName, object);
490 
491         Object service = manager.getService(serviceName); // throws Exception on unknown service
492         field.setAccessible(true);
493 
494         try
495         {
496             log.debug("Injection of {} into object {}", serviceName, object);
497 
498             field.set(object, service);
499         }
500         catch (IllegalArgumentException | IllegalAccessException e)
501         {
502             throw new TurbineException("Could not inject service "
503                     + serviceName + " into object " + object, e);
504         }
505     }
506 }