View Javadoc

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