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.util.List;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ConcurrentMap;
028
029import org.apache.commons.configuration2.Configuration;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.fulcrum.security.model.turbine.TurbineAccessControlList;
032import org.apache.logging.log4j.LogManager;
033import org.apache.logging.log4j.Logger;
034import org.apache.turbine.Turbine;
035import org.apache.turbine.modules.Loader;
036import org.apache.turbine.services.ServiceManager;
037import org.apache.turbine.services.TurbineServices;
038import org.apache.turbine.services.assemblerbroker.AssemblerBrokerService;
039import org.apache.turbine.util.TurbineException;
040
041/**
042 * AnnotationProcessor contains static helper methods that handle the
043 * Turbine annotations for objects
044 *
045 * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
046 * @version $Id: TurbineAssemblerBrokerService.java 1521103 2013-09-09 13:38:07Z tv $
047 */
048public class AnnotationProcessor
049{
050    /** Logging */
051    private static Logger log = LogManager.getLogger(AnnotationProcessor.class);
052
053    /** Annotation cache */
054    private static ConcurrentMap<String, Annotation[]> annotationCache = new ConcurrentHashMap<>();
055
056    /**
057     * Get cached annotations for field, class or method
058     *
059     * @param object a field, class or method
060     *
061     * @return the declared annotations for the object
062     */
063    public static Annotation[] getAnnotations(AccessibleObject object)
064    {
065        String key = object.getClass() + object.toString();
066        Annotation[] annotations = annotationCache.get(key);
067        if (annotations == null)
068        {
069            Annotation[] newAnnotations = object.getDeclaredAnnotations();
070            annotations = annotationCache.putIfAbsent(key, newAnnotations);
071            if (annotations == null)
072            {
073                annotations = newAnnotations;
074            }
075        }
076        return annotations;
077    }
078
079    public enum ConditionType
080    {
081        COMPOUND, ANY;
082    }
083
084    /**
085     * Check if the object given is authorized to be executed based on its annotations
086     *
087     * The method will return false if one of the annotations denies execution
088     *
089     * @see #isAuthorized(AccessibleObject, TurbineAccessControlList, ConditionType)
090     *
091     * @param <A> ACL instance
092     * @param object accessible object to test
093     * @param acl access control list
094     * @return true if the execution is allowed
095     */
096    public static <A extends TurbineAccessControlList<?>> boolean isAuthorized(AccessibleObject object, A acl)
097    {
098        return isAuthorized( object, acl, ConditionType.COMPOUND );
099    }
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}