001 /** 002 * Copyright 2005-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.krad.datadictionary; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.apache.commons.logging.Log; 020 import org.apache.commons.logging.LogFactory; 021 import org.kuali.rice.core.api.CoreApiServiceLocator; 022 import org.kuali.rice.krad.service.DataDictionaryService; 023 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 024 import org.springframework.beans.BeansException; 025 import org.springframework.beans.MutablePropertyValues; 026 import org.springframework.beans.PropertyValue; 027 import org.springframework.beans.factory.config.BeanDefinition; 028 import org.springframework.beans.factory.config.BeanDefinitionHolder; 029 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 030 import org.springframework.beans.factory.config.TypedStringValue; 031 import org.springframework.beans.factory.support.BeanDefinitionRegistry; 032 import org.springframework.beans.factory.support.ManagedArray; 033 import org.springframework.beans.factory.support.ManagedList; 034 import org.springframework.beans.factory.support.ManagedMap; 035 import org.springframework.beans.factory.support.ManagedSet; 036 037 import java.util.ArrayList; 038 import java.util.List; 039 import java.util.Map; 040 import java.util.Set; 041 import java.util.Stack; 042 043 /** 044 * Post processor for the data dictionary bean factory 045 * 046 * <p> 047 * The 'driver' for other post processors. Essentially this iterates through each bean and its properties, 048 * making calls to the message and expression processors 049 * </p> 050 * 051 * @author Kuali Rice Team (rice.collab@kuali.org) 052 */ 053 public class DictionaryBeanFactoryPostProcessor { 054 private static final Log LOG = LogFactory.getLog(DictionaryBeanFactoryPostProcessor.class); 055 056 private DataDictionary dataDictionary; 057 private ConfigurableListableBeanFactory beanFactory; 058 059 private List<DictionaryBeanProcessor> beanProcessors; 060 061 /** 062 * Constructs a new processor for the given data dictionary and bean factory 063 * 064 * @param dataDictionary data dictionary instance that contains the bean factory 065 * @param beanFactory bean factory to process 066 */ 067 public DictionaryBeanFactoryPostProcessor(DataDictionary dataDictionary, 068 ConfigurableListableBeanFactory beanFactory) { 069 this.dataDictionary = dataDictionary; 070 this.beanFactory = beanFactory; 071 072 this.beanProcessors = new ArrayList<DictionaryBeanProcessor>(); 073 this.beanProcessors.add(new MessageBeanProcessor(dataDictionary, beanFactory)); 074 } 075 076 /** 077 * Iterates through all beans in the factory and invokes processing of root bean definitions 078 * 079 * @throws org.springframework.beans.BeansException 080 */ 081 public void postProcessBeanFactory() throws BeansException { 082 // check whether loading of external messages is enabled 083 boolean loadExternalMessages = CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsBoolean( 084 "load.dictionary.external.messages"); 085 if (!loadExternalMessages) { 086 return; 087 } 088 089 LOG.info("Beginning dictionary bean post processing"); 090 091 List<DictionaryBeanProcessor> beanProcessors = new ArrayList<DictionaryBeanProcessor>(); 092 beanProcessors.add(new MessageBeanProcessor(dataDictionary, beanFactory)); 093 094 String[] beanNames = beanFactory.getBeanDefinitionNames(); 095 for (int i = 0; i < beanNames.length; i++) { 096 String beanName = beanNames[i]; 097 BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); 098 099 processRootBeanDefinition(beanName, beanDefinition); 100 } 101 102 LOG.info("Finished dictionary bean post processing"); 103 } 104 105 /** 106 * Invokes processors to handle the root bean definition then processes the bean properties 107 * 108 * @param beanName name of the bean within the factory 109 * @param beanDefinition root bean definition to process 110 */ 111 protected void processRootBeanDefinition(String beanName, BeanDefinition beanDefinition) { 112 for (DictionaryBeanProcessor beanProcessor : beanProcessors) { 113 beanProcessor.processRootBeanDefinition(beanName, beanDefinition); 114 } 115 116 Stack<BeanDefinitionHolder> nestedBeanStack = new Stack<BeanDefinitionHolder>(); 117 nestedBeanStack.push(new BeanDefinitionHolder(beanDefinition, beanName)); 118 119 processBeanProperties(beanDefinition, nestedBeanStack); 120 } 121 122 /** 123 * Invokes the processors to handle the given nested bean definition 124 * 125 * <p> 126 * A check is also made to determine if the nested bean has a non-generated id which is not registered in the 127 * factory, if so the bean is added as a registered bean (so it can be found by id) 128 * </p> 129 * 130 * @param beanName name of the nested bean definition in the bean factory 131 * @param beanDefinition nested bean definition to process 132 * @param nestedPropertyPath the property path to the nested bean from the parent bean definition 133 * @param isCollectionBean indicates whether the nested bean is in a collection, if so a different handler 134 * method is called on the processors 135 * @param nestedBeanStack the stack of bean containers(those beans which contain the bean) 136 */ 137 public void processNestedBeanDefinition(String beanName, BeanDefinition beanDefinition, String nestedPropertyPath, 138 boolean isCollectionBean, Stack<BeanDefinitionHolder> nestedBeanStack) { 139 // if bean name is given and factory does not have it registered we need to add it (inner beans that 140 // were given an id) 141 if (StringUtils.isNotBlank(beanName) && !StringUtils.contains(beanName, "$") && !StringUtils.contains(beanName, 142 "#") && !beanFactory.containsBean(beanName)) { 143 ((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(beanName, beanDefinition); 144 } 145 146 // invoke the processors to handle the nested bean 147 for (DictionaryBeanProcessor beanProcessor : beanProcessors) { 148 if (isCollectionBean) { 149 beanProcessor.processCollectionBeanDefinition(beanName, beanDefinition, nestedPropertyPath, 150 nestedBeanStack); 151 } else { 152 beanProcessor.processNestedBeanDefinition(beanName, beanDefinition, nestedPropertyPath, 153 nestedBeanStack); 154 } 155 } 156 157 BeanDefinitionHolder nestedBeanDefinitionHolder = new BeanDefinitionHolder(beanDefinition, beanName); 158 nestedBeanStack.push(nestedBeanDefinitionHolder); 159 160 processBeanProperties(beanDefinition, nestedBeanStack); 161 162 nestedBeanStack.pop(); 163 } 164 165 /** 166 * Invokes the processors to handle the string value (which may be changed) 167 * 168 * @param propertyName name of the property that is being processed 169 * @param propertyValue the string property value to process 170 * @param nestedBeanStack the stack of bean containers, including the bean that contains the property 171 * @return String new property value (possibly modified by processors) 172 */ 173 protected String processStringPropertyValue(String propertyName, String propertyValue, 174 Stack<BeanDefinitionHolder> nestedBeanStack) { 175 String processedStringValue = propertyValue; 176 177 for (DictionaryBeanProcessor beanProcessor : beanProcessors) { 178 processedStringValue = beanProcessor.processStringPropertyValue(propertyName, processedStringValue, 179 nestedBeanStack); 180 } 181 182 return processedStringValue; 183 } 184 185 /** 186 * Invokes the processors to handle an array string value (which may be changed) 187 * 188 * @param propertyName name of the property that is being processed 189 * @param propertyValue the array which contains the string 190 * @param elementValue the string element value 191 * @param elementIndex the index of the string within the array 192 * @param nestedBeanStack the stack of bean containers, including the bean that contains the property 193 * @return String new property value (possibly modified by processors) 194 */ 195 protected String processArrayStringPropertyValue(String propertyName, Object[] propertyValue, String elementValue, 196 int elementIndex, Stack<BeanDefinitionHolder> nestedBeanStack, 197 List<DictionaryBeanProcessor> beanProcessors) { 198 String processedStringValue = elementValue; 199 200 for (DictionaryBeanProcessor beanProcessor : beanProcessors) { 201 processedStringValue = beanProcessor.processArrayStringPropertyValue(propertyName, propertyValue, 202 elementValue, elementIndex, nestedBeanStack); 203 } 204 205 return processedStringValue; 206 } 207 208 /** 209 * Invokes the processors to handle an list string value (which may be changed) 210 * 211 * @param propertyName name of the property that is being processed 212 * @param propertyValue the list which contains the string 213 * @param elementValue the string element value 214 * @param elementIndex the index of the string within the list 215 * @param nestedBeanStack the stack of bean containers, including the bean that contains the property 216 * @return String new property value (possibly modified by processors) 217 */ 218 protected String processListStringPropertyValue(String propertyName, List<?> propertyValue, String elementValue, 219 int elementIndex, Stack<BeanDefinitionHolder> nestedBeanStack, 220 List<DictionaryBeanProcessor> beanProcessors) { 221 String processedStringValue = elementValue; 222 223 for (DictionaryBeanProcessor beanProcessor : beanProcessors) { 224 processedStringValue = beanProcessor.processListStringPropertyValue(propertyName, propertyValue, 225 elementValue, elementIndex, nestedBeanStack); 226 } 227 228 return processedStringValue; 229 } 230 231 /** 232 * Invokes the processors to handle an set string value (which may be changed) 233 * 234 * @param propertyName name of the property that is being processed 235 * @param propertyValue the set which contains the string 236 * @param elementValue the string element value 237 * @param nestedBeanStack the stack of bean containers, including the bean that contains the property 238 * @return String new property value (possibly modified by processors) 239 */ 240 protected String processSetStringPropertyValue(String propertyName, Set<?> propertyValue, String elementValue, 241 Stack<BeanDefinitionHolder> nestedBeanStack, List<DictionaryBeanProcessor> beanProcessors) { 242 String processedStringValue = elementValue; 243 244 for (DictionaryBeanProcessor beanProcessor : beanProcessors) { 245 processedStringValue = beanProcessor.processSetStringPropertyValue(propertyName, propertyValue, 246 elementValue, nestedBeanStack); 247 } 248 249 return processedStringValue; 250 } 251 252 /** 253 * Invokes the processors to handle an map string value (which may be changed) 254 * 255 * @param propertyName name of the property that is being processed 256 * @param propertyValue the map which contains the string 257 * @param elementValue the string element value 258 * @param elementKey the key for the string within the map 259 * @param nestedBeanStack the stack of bean containers, including the bean that contains the property 260 * @return String new property value (possibly modified by processors) 261 */ 262 protected String processMapStringPropertyValue(String propertyName, Map<?, ?> propertyValue, String elementValue, 263 Object elementKey, Stack<BeanDefinitionHolder> nestedBeanStack, 264 List<DictionaryBeanProcessor> beanProcessors) { 265 String processedStringValue = elementValue; 266 267 for (DictionaryBeanProcessor beanProcessor : beanProcessors) { 268 processedStringValue = beanProcessor.processMapStringPropertyValue(propertyName, propertyValue, 269 elementValue, elementKey, nestedBeanStack); 270 } 271 272 return processedStringValue; 273 } 274 275 /** 276 * Iterates through the properties defined for the bean definition and invokes helper methods to process 277 * the property value 278 * 279 * @param beanDefinition bean definition whose properties will be processed 280 * @param nestedBeanStack stack of beans which contain the given bean 281 */ 282 protected void processBeanProperties(BeanDefinition beanDefinition, Stack<BeanDefinitionHolder> nestedBeanStack) { 283 // iterate through properties and check for any configured message keys within the value 284 MutablePropertyValues pvs = beanDefinition.getPropertyValues(); 285 PropertyValue[] pvArray = pvs.getPropertyValues(); 286 for (PropertyValue pv : pvArray) { 287 Object newPropertyValue = null; 288 if (isStringValue(pv.getValue())) { 289 newPropertyValue = processStringPropertyValue(pv.getName(), getString(pv.getValue()), nestedBeanStack); 290 } else { 291 newPropertyValue = visitPropertyValue(pv.getName(), pv.getValue(), nestedBeanStack); 292 } 293 294 pvs.removePropertyValue(pv.getName()); 295 pvs.addPropertyValue(pv.getName(), newPropertyValue); 296 } 297 } 298 299 /** 300 * Determines if the property value is a bean or collection, and calls the appropriate helper method 301 * to process further. Ultimately this invokes the processors to modify the property value if necessary 302 * 303 * @param propertyName name for the property being processed 304 * @param propertyValue value for the property to process 305 * @param nestedBeanStack stack of beans which contain the property 306 * @return Object the new property value (which may be modified0 307 */ 308 protected Object visitPropertyValue(String propertyName, Object propertyValue, 309 Stack<BeanDefinitionHolder> nestedBeanStack) { 310 if (isBeanDefinitionValue(propertyValue)) { 311 String beanName = getBeanName(propertyValue); 312 BeanDefinition beanDefinition = getBeanDefinition(propertyValue); 313 314 processNestedBeanDefinition(beanName, beanDefinition, propertyName, false, nestedBeanStack); 315 } else if (isCollectionValue(propertyValue)) { 316 visitCollection(propertyValue, propertyName, nestedBeanStack); 317 } 318 319 return propertyValue; 320 } 321 322 /** 323 * Determines what kind of collection (or array) the given value is and call handlers based on the determined type 324 * 325 * @param value collection value to process 326 * @param propertyName name of the property which has the collection value 327 * @param nestedBeanStack stack of bean containers which contains the collection property 328 */ 329 protected void visitCollection(Object value, String propertyName, Stack<BeanDefinitionHolder> nestedBeanStack) { 330 if (value instanceof Object[] || (value instanceof ManagedArray)) { 331 visitArray(value, propertyName, nestedBeanStack); 332 } else if (value instanceof List) { 333 visitList((List<?>) value, propertyName, nestedBeanStack); 334 } else if (value instanceof Set) { 335 visitSet((Set<?>) value, propertyName, nestedBeanStack); 336 } else if (value instanceof Map) { 337 visitMap((Map<?, ?>) value, propertyName, nestedBeanStack); 338 } 339 } 340 341 /** 342 * Iterates through the array values and calls helpers to process the value 343 * 344 * @param array the array to process 345 * @param propertyName name of the property which has the array value 346 * @param nestedBeanStack stack of bean containers which contains the array property 347 */ 348 protected void visitArray(Object array, String propertyName, Stack<BeanDefinitionHolder> nestedBeanStack) { 349 Object[] arrayVal = null; 350 if (array instanceof ManagedArray) { 351 arrayVal = (Object[]) ((ManagedArray) array).getSource(); 352 } else { 353 arrayVal = (Object[]) array; 354 } 355 356 Object[] newArray = new Object[arrayVal.length]; 357 for (int i = 0; i < arrayVal.length; i++) { 358 Object elem = arrayVal[i]; 359 360 if (isStringValue(elem)) { 361 elem = processArrayStringPropertyValue(propertyName, arrayVal, getString(elem), i, nestedBeanStack, 362 beanProcessors); 363 } else { 364 elem = visitPropertyValue(propertyName, elem, nestedBeanStack); 365 } 366 367 newArray[i] = elem; 368 } 369 370 if (array instanceof ManagedArray) { 371 ((ManagedArray) array).setSource(newArray); 372 } else { 373 array = newArray; 374 } 375 } 376 377 /** 378 * Iterates through the list values and calls helpers to process the value 379 * 380 * @param listVal the list to process 381 * @param propertyName name of the property which has the list value 382 * @param nestedBeanStack stack of bean containers which contains the list property 383 */ 384 protected void visitList(List<?> listVal, String propertyName, Stack<BeanDefinitionHolder> nestedBeanStack) { 385 boolean isMergeEnabled = false; 386 if (listVal instanceof ManagedList) { 387 isMergeEnabled = ((ManagedList) listVal).isMergeEnabled(); 388 } 389 390 ManagedList newList = new ManagedList(); 391 newList.setMergeEnabled(isMergeEnabled); 392 393 for (int i = 0; i < listVal.size(); i++) { 394 Object elem = listVal.get(i); 395 396 if (isStringValue(elem)) { 397 elem = processListStringPropertyValue(propertyName, listVal, getString(elem), i, nestedBeanStack, 398 beanProcessors); 399 } else { 400 elem = visitPropertyValue(propertyName, elem, nestedBeanStack); 401 } 402 403 newList.add(i, elem); 404 } 405 406 listVal.clear(); 407 listVal.addAll(newList); 408 } 409 410 /** 411 * Iterates through the set values and calls helpers to process the value 412 * 413 * @param setVal the set to process 414 * @param propertyName name of the property which has the set value 415 * @param nestedBeanStack stack of bean containers which contains the set property 416 */ 417 protected void visitSet(Set setVal, String propertyName, Stack<BeanDefinitionHolder> nestedBeanStack) { 418 boolean isMergeEnabled = false; 419 if (setVal instanceof ManagedSet) { 420 isMergeEnabled = ((ManagedSet) setVal).isMergeEnabled(); 421 } 422 423 ManagedSet newSet = new ManagedSet(); 424 newSet.setMergeEnabled(isMergeEnabled); 425 426 for (Object elem : setVal) { 427 if (isStringValue(elem)) { 428 elem = processSetStringPropertyValue(propertyName, setVal, getString(elem), nestedBeanStack, 429 beanProcessors); 430 } else { 431 elem = visitPropertyValue(propertyName, elem, nestedBeanStack); 432 } 433 434 newSet.add(elem); 435 } 436 437 setVal.clear(); 438 setVal.addAll(newSet); 439 } 440 441 /** 442 * Iterates through the map values and calls helpers to process the value 443 * 444 * @param mapVal the set to process 445 * @param propertyName name of the property which has the map value 446 * @param nestedBeanStack stack of bean containers which contains the map property 447 */ 448 protected void visitMap(Map<?, ?> mapVal, String propertyName, Stack<BeanDefinitionHolder> nestedBeanStack) { 449 boolean isMergeEnabled = false; 450 if (mapVal instanceof ManagedMap) { 451 isMergeEnabled = ((ManagedMap) mapVal).isMergeEnabled(); 452 } 453 454 ManagedMap newMap = new ManagedMap(); 455 newMap.setMergeEnabled(isMergeEnabled); 456 457 for (Map.Entry entry : mapVal.entrySet()) { 458 Object key = entry.getKey(); 459 Object val = entry.getValue(); 460 461 if (isStringValue(val)) { 462 val = processMapStringPropertyValue(propertyName, mapVal, getString(val), key, nestedBeanStack, 463 beanProcessors); 464 } else { 465 val = visitPropertyValue(propertyName, val, nestedBeanStack); 466 } 467 468 newMap.put(key, val); 469 } 470 471 mapVal.clear(); 472 mapVal.putAll(newMap); 473 } 474 475 /** 476 * Indicate whether the given value is a string or holds a string 477 * 478 * @param value value to test 479 * @return boolean true if the value is a string, false if not 480 */ 481 protected boolean isStringValue(Object value) { 482 boolean isString = false; 483 484 if (value instanceof TypedStringValue || (value instanceof String)) { 485 isString = true; 486 } 487 488 return isString; 489 } 490 491 /** 492 * Determines whether the given value is of String type and if so returns the string value 493 * 494 * @param value object value to check 495 * @return String string value for object or null if object is not a string type 496 */ 497 protected String getString(Object value) { 498 String stringValue = null; 499 500 if (value instanceof TypedStringValue || (value instanceof String)) { 501 if (value instanceof TypedStringValue) { 502 TypedStringValue typedStringValue = (TypedStringValue) value; 503 stringValue = typedStringValue.getValue(); 504 } else { 505 stringValue = (String) value; 506 } 507 } 508 509 return stringValue; 510 } 511 512 /** 513 * Indicate whether the given value is a bean definition (or holder) 514 * 515 * @param value value to test 516 * @return boolean true if the value is a bean definition, false if not 517 */ 518 protected boolean isBeanDefinitionValue(Object value) { 519 boolean isBean = false; 520 521 if ((value instanceof BeanDefinition) || (value instanceof BeanDefinitionHolder)) { 522 isBean = true; 523 } 524 525 return isBean; 526 } 527 528 /** 529 * Returns the given value as a bean definition (parsing from holder if necessary) 530 * 531 * @param value value to convert 532 * @return BeanDefinition converted bean definition 533 */ 534 protected BeanDefinition getBeanDefinition(Object value) { 535 BeanDefinition beanDefinition = null; 536 537 if ((value instanceof BeanDefinition) || (value instanceof BeanDefinitionHolder)) { 538 if (value instanceof BeanDefinition) { 539 beanDefinition = (BeanDefinition) value; 540 } else { 541 beanDefinition = ((BeanDefinitionHolder) value).getBeanDefinition(); 542 } 543 } 544 545 return beanDefinition; 546 } 547 548 /** 549 * Gets the bean name from the given value which is assumed to be a bean definition holder 550 * 551 * @param value value retrieve bean name from 552 * @return String bean name, or null if value is not a bean definition holder 553 */ 554 protected String getBeanName(Object value) { 555 String beanName = null; 556 557 if (value instanceof BeanDefinitionHolder) { 558 beanName = ((BeanDefinitionHolder) value).getBeanName(); 559 } 560 561 return beanName; 562 } 563 564 /** 565 * Indicate whether the given value is a collection 566 * 567 * @param value value to test 568 * @return boolean true if the value is a collection, false if not 569 */ 570 protected boolean isCollectionValue(Object value) { 571 boolean isCollection = false; 572 573 if (value instanceof Object[] || (value instanceof ManagedArray) || (value instanceof List) || 574 (value instanceof Set) || (value instanceof Map)) { 575 isCollection = true; 576 } 577 578 return isCollection; 579 } 580 581 /** 582 * Retrieves the data dictionary service using the KRAD service locator 583 * 584 * @return DataDictionaryService instance 585 */ 586 protected DataDictionaryService getDataDictionaryService() { 587 return KRADServiceLocatorWeb.getDataDictionaryService(); 588 } 589 590 }