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 }