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}