View Javadoc
1   /**
2    * Copyright 2005-2016 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.kuali.rice.core.api.CoreApiServiceLocator;
22  import org.kuali.rice.krad.service.DataDictionaryService;
23  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
24  import org.springframework.beans.BeansException;
25  import org.springframework.beans.MutablePropertyValues;
26  import org.springframework.beans.PropertyValue;
27  import org.springframework.beans.factory.config.BeanDefinition;
28  import org.springframework.beans.factory.config.BeanDefinitionHolder;
29  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
30  import org.springframework.beans.factory.config.TypedStringValue;
31  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
32  import org.springframework.beans.factory.support.ManagedArray;
33  import org.springframework.beans.factory.support.ManagedList;
34  import org.springframework.beans.factory.support.ManagedMap;
35  import org.springframework.beans.factory.support.ManagedSet;
36  
37  import java.util.ArrayList;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  import java.util.Stack;
42  
43  /**
44   * Post processor for the data dictionary bean factory
45   *
46   * <p>
47   * The 'driver' for other post processors. Essentially this iterates through each bean and its properties,
48   * making calls to the message and expression processors
49   * </p>
50   *
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   */
53  public class DictionaryBeanFactoryPostProcessor {
54      private static final Log LOG = LogFactory.getLog(DictionaryBeanFactoryPostProcessor.class);
55  
56      private DataDictionary dataDictionary;
57      private ConfigurableListableBeanFactory beanFactory;
58  
59      private List<DictionaryBeanProcessor> beanProcessors;
60  
61      /**
62       * Constructs a new processor for the given data dictionary and bean factory
63       *
64       * @param dataDictionary data dictionary instance that contains the bean factory
65       * @param beanFactory bean factory to process
66       */
67      public DictionaryBeanFactoryPostProcessor(DataDictionary dataDictionary,
68              ConfigurableListableBeanFactory beanFactory) {
69          this.dataDictionary = dataDictionary;
70          this.beanFactory = beanFactory;
71  
72          this.beanProcessors = new ArrayList<DictionaryBeanProcessor>();
73          this.beanProcessors.add(new MessageBeanProcessor(dataDictionary, beanFactory));
74      }
75  
76      /**
77       * Iterates through all beans in the factory and invokes processing of root bean definitions
78       *
79       * @throws org.springframework.beans.BeansException
80       */
81      public void postProcessBeanFactory() throws BeansException {
82          // check whether loading of external messages is enabled
83          boolean loadExternalMessages = CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsBoolean(
84                  "load.dictionary.external.messages");
85          if (!loadExternalMessages) {
86              return;
87          }
88  
89          LOG.info("Beginning dictionary bean post processing");
90  
91          List<DictionaryBeanProcessor> beanProcessors = new ArrayList<DictionaryBeanProcessor>();
92          beanProcessors.add(new MessageBeanProcessor(dataDictionary, beanFactory));
93  
94          String[] beanNames = beanFactory.getBeanDefinitionNames();
95          for (int i = 0; i < beanNames.length; i++) {
96              String beanName = beanNames[i];
97              BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
98  
99              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 }