001package org.apache.turbine.annotation;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.lang.annotation.Annotation;
023import java.lang.reflect.AccessibleObject;
024import java.lang.reflect.Field;
025import java.lang.reflect.InvocationTargetException;
026import java.lang.reflect.Method;
027import java.util.List;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.ConcurrentMap;
030
031import org.apache.commons.configuration2.Configuration;
032import org.apache.commons.lang3.StringUtils;
033import org.apache.fulcrum.security.model.turbine.TurbineAccessControlList;
034import org.apache.logging.log4j.LogManager;
035import org.apache.logging.log4j.Logger;
036import org.apache.turbine.Turbine;
037import org.apache.turbine.modules.Loader;
038import org.apache.turbine.services.ServiceManager;
039import org.apache.turbine.services.TurbineServices;
040import org.apache.turbine.services.assemblerbroker.AssemblerBrokerService;
041import org.apache.turbine.util.TurbineException;
042
043/**
044 * AnnotationProcessor contains static helper methods that handle the
045 * Turbine annotations for objects
046 *
047 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
048 * @version $Id: TurbineAssemblerBrokerService.java 1521103 2013-09-09 13:38:07Z tv $
049 */
050public class AnnotationProcessor
051{
052    /** Logging */
053    private static Logger log = LogManager.getLogger(AnnotationProcessor.class);
054
055    /** Annotation cache */
056    private static ConcurrentMap<String, Annotation[]> annotationCache = new ConcurrentHashMap<>();
057
058    /**
059     * Get cached annotations for field, class or method
060     *
061     * @param object a field, class or method
062     *
063     * @return the declared annotations for the object
064     */
065    public static Annotation[] getAnnotations(AccessibleObject object)
066    {
067        String key = object.getClass() + object.toString();
068        Annotation[] annotations = annotationCache.get(key);
069        if (annotations == null)
070        {
071            Annotation[] newAnnotations = object.getDeclaredAnnotations();
072            annotations = annotationCache.putIfAbsent(key, newAnnotations);
073            if (annotations == null)
074            {
075                annotations = newAnnotations;
076            }
077        }
078        return annotations;
079    }
080
081    public enum ConditionType
082    {
083        COMPOUND, ANY;
084    }
085
086    /**
087     * Check if the object given is authorized to be executed based on its annotations
088     *
089     * The method will return false if one of the annotations denies execution
090     *
091     * @see #isAuthorized(AccessibleObject, TurbineAccessControlList, ConditionType)
092     *
093     * @param <A> ACL instance
094     * @param object accessible object to test
095     * @param acl access control list
096     * @return true if the execution is allowed
097     */
098    public static <A extends TurbineAccessControlList<?>> boolean isAuthorized(AccessibleObject object, A acl)
099    {
100        return isAuthorized( object, acl, ConditionType.COMPOUND );
101    }
102
103    /**
104     * Check if the object given is authorized to be executed based on its annotations
105     * The method's return value depends on the conditonType, refer to the ConditionType
106     *
107     * @param <A> ACL instance
108     * @param object the object
109     * @param acl access control list
110     * @param conditonType either {@link ConditionType#COMPOUND}: The method will return false if one of the annotations denies execution
111     *                     or {@link ConditionType#ANY} : The method will return true if one of the annotations allows execution
112     * @return true if the execution is allowed
113     */
114    public static <A extends TurbineAccessControlList<?>> boolean isAuthorized(AccessibleObject object, A acl, ConditionType conditonType)
115    {
116        Annotation[] annotations = getAnnotations(object);
117
118        for (Annotation annotation : annotations)
119        {
120            if (annotation instanceof TurbineRequiredRole)
121            {
122                TurbineRequiredRole trr = (TurbineRequiredRole) annotation;
123                String[] roleNames = trr.value();
124                String group = trr.group();
125
126                if (StringUtils.isEmpty(group)) // global group
127                {
128                    for (String roleName : roleNames)
129                    {
130                        switch ( conditonType ) {
131                            case COMPOUND: default:
132                                if (!acl.hasRole(roleName))
133                                {
134                                    return false;
135                                }
136                                break;
137                            case ANY:
138                                if (acl.hasRole(roleName))
139                                {
140                                    return true;
141                                }
142                                break;
143                        }
144                    }
145                    if (conditonType == ConditionType.ANY) { // nothing matched
146                        return false;
147                    }
148                }
149                else
150                {
151                    for (String roleName : roleNames)
152                    {
153                        switch ( conditonType ) {
154                            case COMPOUND: default:
155                                if (!acl.hasRole(roleName, group))
156                                {
157                                    return false;
158                                }
159                                break;
160                            case ANY:
161                                if (acl.hasRole(roleName, group))
162                                {
163                                    return true;
164                                }
165                                break;
166                        }
167                    }
168                }
169            }
170            else if (annotation instanceof TurbineRequiredPermission)
171            {
172                TurbineRequiredPermission trp = (TurbineRequiredPermission) annotation;
173                String[] permissionNames = trp.value();
174                String group = trp.group();
175
176                if (StringUtils.isEmpty(group)) // global group
177                {
178                    for (String permissionName : permissionNames)
179                    {
180                        switch ( conditonType ) {
181                            case COMPOUND: default:
182                                if (!acl.hasPermission(permissionName))
183                                {
184                                    return false;
185                                }
186                                break;
187                            case ANY:
188                                if (acl.hasPermission(permissionName))
189                                {
190                                    return true;
191                                }
192                                break;
193                        }
194                    }
195                }
196                else
197                {
198                    for (String permissionName : permissionNames)
199                    {
200                        switch ( conditonType ) {
201                            case COMPOUND: default:
202                                if (!acl.hasPermission(permissionName, group))
203                                {
204                                    return false;
205                                }
206                                break;
207                            case ANY:
208                                if (acl.hasPermission(permissionName, group))
209                                {
210                                    return true;
211                                }
212                                break;
213                        }
214
215                    }
216                }
217            }
218        }
219
220        return true;
221    }
222
223    /**
224     * Search for annotated fields of the object and inject the appropriate
225     * objects
226     *
227     * @param object the object
228     * @throws TurbineException if the objects could not be injected
229     */
230    public static void process(Object object) throws TurbineException
231    {
232        process(object, false);
233    }
234
235    /**
236     * Search for annotated fields and optionally of method fields of the object and inject the appropriate
237     * objects
238     *
239     * @param object the object
240     * @throws TurbineException if the objects could not be injected
241     */
242    public static void process(Object object, Boolean hasTurbineServicesInMethodFields) throws TurbineException
243    {
244        ServiceManager manager = null;
245        Configuration config = null;
246        AssemblerBrokerService assembler = null;
247        Class<?> clazz = object.getClass();
248
249        while (clazz != null)
250        {
251            Field[] fields = clazz.getDeclaredFields();
252
253            for (Field field : fields)
254            {
255                Annotation[] annotations = getAnnotations(field);
256
257                for (Annotation a : annotations)
258                {
259                    if (a instanceof TurbineService)
260                    {
261                        if (manager == null)
262                        {
263                            manager = TurbineServices.getInstance();
264                        }
265                        injectTurbineService(object, manager, field, (TurbineService) a);
266                    }
267                    else if (a instanceof TurbineConfiguration)
268                    {
269                        if (config == null)
270                        {
271                            config = Turbine.getConfiguration();
272                        }
273                        injectTurbineConfiguration(object, config, field, (TurbineConfiguration) a);
274                    }
275                    else if (a instanceof TurbineLoader)
276                    {
277                        if (assembler == null)
278                        {
279                            assembler = (AssemblerBrokerService) TurbineServices.getInstance().
280                                getService(AssemblerBrokerService.SERVICE_NAME);
281                        }
282                        injectTurbineLoader(object, assembler, field, (TurbineLoader) a);
283                    }
284                }
285            }
286
287            if (hasTurbineServicesInMethodFields) {
288                manager = processMethods(object, manager, clazz);
289            }
290
291            clazz = clazz.getSuperclass();
292        }
293    }
294
295    private static ServiceManager processMethods(Object object, ServiceManager manager, Class<?> clazz) throws TurbineException {
296        Method[] methods = clazz.getMethods();
297
298        for (Method method : methods)
299        {
300            Annotation[] annotations = getAnnotations(method);
301            for (Annotation a : annotations)
302            {
303                if (a instanceof TurbineService)
304                {
305
306                    if (manager == null)
307                    {
308                        manager = TurbineServices.getInstance();
309                    }
310                    injectTurbineService(object, manager, method, (TurbineService) a);
311                }
312            }
313        }
314        return manager;
315    }
316
317    /**
318     * Inject Turbine configuration into field of object
319     *
320     * @param object the object to process
321     * @param assembler AssemblerBrokerService, provides the loader
322     * @param field the field
323     * @param annotation the value of the annotation
324     *
325     * @throws TurbineException if loader cannot be set
326     */
327    private static void injectTurbineLoader(Object object, AssemblerBrokerService assembler, Field field, TurbineLoader annotation) throws TurbineException
328    {
329        Loader<?> loader = assembler.getLoader(annotation.value());
330        field.setAccessible(true);
331
332        try
333        {
334            log.debug("Injection of {} into object {}", loader, object);
335
336            field.set(object, loader);
337        }
338        catch (IllegalArgumentException | IllegalAccessException e)
339        {
340            throw new TurbineException("Could not inject loader "
341                    + loader + " into object " + object, e);
342        }
343    }
344
345    /**
346     * Inject Turbine configuration into field of object
347     *
348     * @param object the object to process
349     * @param conf the configuration to use
350     * @param field the field
351     * @param annotation the value of the annotation
352     *
353     * @throws TurbineException if configuration cannot be set
354     */
355    @SuppressWarnings("boxing")
356    private static void injectTurbineConfiguration(Object object, Configuration conf, Field field, TurbineConfiguration annotation) throws TurbineException
357    {
358        Class<?> type = field.getType();
359        String key = annotation.value();
360
361        try
362        {
363            if (Configuration.class.isAssignableFrom(type))
364            {
365                final Configuration injectConfiguration;
366                // Check for annotation value
367                if (StringUtils.isNotEmpty(key))
368                {
369                    injectConfiguration = conf.subset(key);
370                }
371                else
372                {
373                    injectConfiguration = conf;
374                }
375
376                log.debug("Injection of {} into object {}", injectConfiguration, object);
377
378                field.setAccessible(true);
379                field.set(object, injectConfiguration);
380            }
381            else if (conf.containsKey(key))
382            {
383                if ( String.class.isAssignableFrom( type ) )
384                {
385                    String value = conf.getString(key);
386                    log.debug("Injection of {} into object {}", value, object);
387
388                    field.setAccessible(true);
389                    field.set(object, value);
390                }
391                else if ( Boolean.TYPE.isAssignableFrom( type ) )
392                {
393                    boolean value = conf.getBoolean(key);
394                    log.debug("Injection of {} into object {}", value, object);
395
396                    field.setAccessible(true);
397                    field.setBoolean(object, value);
398                }
399                else if ( Integer.TYPE.isAssignableFrom( type ) )
400                {
401                    int value = conf.getInt(key);
402                    log.debug("Injection of {} into object {}", value, object);
403
404                    field.setAccessible(true);
405                    field.setInt(object, value);
406                }
407                else if ( Long.TYPE.isAssignableFrom( type ) )
408                {
409                    long value = conf.getLong(key);
410                    log.debug("Injection of {} into object {}", value, object);
411
412                    field.setAccessible(true);
413                    field.setLong(object, value);
414                }
415                else if ( Short.TYPE.isAssignableFrom( type ) )
416                {
417                    short value = conf.getShort(key);
418                    log.debug("Injection of {} into object {}", value, object);
419
420                    field.setAccessible(true);
421                    field.setShort(object, value);
422                }
423                else if ( Long.TYPE.isAssignableFrom( type ) )
424                {
425                    long value = conf.getLong(key);
426                    log.debug("Injection of {} into object {}", value, object);
427
428                    field.setAccessible(true);
429                    field.setLong(object, value);
430                }
431                else if ( Float.TYPE.isAssignableFrom( type ) )
432                {
433                    float value = conf.getFloat(key);
434                    log.debug("Injection of {} into object {}", value, object);
435
436                    field.setAccessible(true);
437                    field.setFloat(object, value);
438                }
439                else if ( Double.TYPE.isAssignableFrom( type ) )
440                {
441                    double value = conf.getDouble(key);
442                    log.debug("Injection of {} into object {}", value, object);
443
444                    field.setAccessible(true);
445                    field.setDouble(object, value);
446                }
447                else if ( Byte.TYPE.isAssignableFrom( type ) )
448                {
449                    byte value = conf.getByte(key);
450                    log.debug("Injection of {} into object {}", value, object);
451
452                    field.setAccessible(true);
453                    field.setByte(object, value);
454                }
455                else if ( List.class.isAssignableFrom( type ) )
456                {
457                    List<Object> values = conf.getList(key);
458                    log.debug("Injection of {} into object {}", values, object);
459
460                    field.setAccessible(true);
461                    field.set(object, values);
462                }
463            }
464        }
465        catch (IllegalArgumentException | IllegalAccessException e)
466        {
467            throw new TurbineException("Could not inject configuration "
468                    + conf + " into object " + object, e);
469        }
470    }
471
472    /**
473     * Inject Turbine service into field of object
474     *
475     * @param object the object to process
476     * @param manager the service manager
477     * @param field the field
478     * @param annotation the value of the annotation
479     *
480     * @throws TurbineException if service is not available
481     */
482    private static void injectTurbineService(Object object, ServiceManager manager, Field field, TurbineService annotation) throws TurbineException
483    {
484        String serviceName = null;
485        // Check for annotation value
486        if (StringUtils.isNotEmpty(annotation.value()))
487        {
488            serviceName = annotation.value();
489        }
490        // Check for fields SERVICE_NAME and ROLE
491        else
492        {
493            Field[] typeFields = field.getType().getFields();
494            serviceName = checkServiceOrRoleInField(serviceName, typeFields);
495        }
496
497        if (StringUtils.isEmpty(serviceName))
498        {
499            // Try interface class name
500            serviceName = field.getType().getName();
501        }
502
503        log.debug("Looking up service for injection: {} for object {}", serviceName, object);
504
505        Object service = manager.getService(serviceName); // throws Exception on unknown service
506        field.setAccessible(true);
507
508        try
509        {
510            log.debug("Injection of {} into object {}", serviceName, object);
511
512            field.set(object, service);
513        }
514        catch (IllegalArgumentException | IllegalAccessException e)
515        {
516            throw new TurbineException("Could not inject service "
517                    + serviceName + " into object " + object, e);
518        }
519    }
520
521    private static void injectTurbineService(Object object, ServiceManager manager, Method method, TurbineService annotation) throws TurbineException
522    {
523        String serviceName = null;
524        // Check for annotation value
525        if (StringUtils.isNotEmpty(annotation.value()))
526        {
527            serviceName = annotation.value();
528        }
529        else
530        {
531            Class<?>[] classes = method.getParameterTypes();
532            for (Class<?> c : classes)
533            {
534                Field[] fields = c.getFields();
535                // Check for fields SERVICE_NAME and ROLE
536                serviceName = checkServiceOrRoleInField(serviceName, fields);
537            }
538        }
539
540        log.debug("Looking up service for injection: {} for object {}", serviceName, object);
541        if (StringUtils.isEmpty(serviceName))
542        {
543            // Try interface class name
544            serviceName = method.getName();
545        }
546
547        Object service = manager.getService(serviceName); // throws Exception on unknown service
548        method.setAccessible(true);
549
550        try
551        {
552            log.debug("Injection of {} into object {}", serviceName, object);
553
554            Object[] paramValues = new Object[1];
555            paramValues[0] = service;
556            method.invoke(object, paramValues);
557        }
558        catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e)
559        {
560            throw new TurbineException("Could not inject service "
561                    + serviceName + " into object " + object, e);
562        }
563    }
564
565    private static String checkServiceOrRoleInField(String serviceName, Field[] fields) {
566        for (Field f : fields)
567            if (TurbineService.SERVICE_NAME.equals(f.getName()))
568            {
569                try
570                {
571                    serviceName = (String)f.get(null);
572                }
573                catch (IllegalArgumentException | IllegalAccessException e)
574                {
575                    continue;
576                }
577                break;
578            }
579            else if (TurbineService.ROLE.equals(f.getName()))
580            {
581                try
582                {
583                    serviceName = (String)f.get(null);
584                }
585                catch (IllegalArgumentException | IllegalAccessException e)
586                {
587                    continue;
588                }
589                break;
590            }
591        return serviceName;
592    }
593}