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}