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    }