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.uif.util;
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.krad.datadictionary.uif.UifDictionaryBean;
022    import org.kuali.rice.krad.uif.UifConstants;
023    import org.kuali.rice.krad.uif.UifPropertyPaths;
024    import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
025    import org.springframework.beans.BeansException;
026    import org.springframework.beans.MutablePropertyValues;
027    import org.springframework.beans.PropertyValue;
028    import org.springframework.beans.factory.config.BeanDefinition;
029    import org.springframework.beans.factory.config.BeanDefinitionHolder;
030    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
031    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
032    import org.springframework.beans.factory.config.TypedStringValue;
033    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
034    import org.springframework.beans.factory.support.ManagedArray;
035    import org.springframework.beans.factory.support.ManagedList;
036    import org.springframework.beans.factory.support.ManagedMap;
037    import org.springframework.beans.factory.support.ManagedSet;
038    
039    import java.util.HashMap;
040    import java.util.HashSet;
041    import java.util.List;
042    import java.util.Map;
043    import java.util.Set;
044    
045    /**
046     * Post processes the bean factory to handle UIF property expressions and IDs on inner beans
047     *
048     * <p>
049     * Conditional logic can be implemented with the UIF dictionary by means of property expressions. These are
050     * expressions that follow SPEL and can be given as the value for a property using the @{} placeholder. Since such
051     * a value would cause an exception when creating the object if the property is a non-string type (value cannot be
052     * converted), we need to move those expressions to a Map for processing, and then remove the original property
053     * configuration containing the expression. The expressions are then evaluated during the view apply model phase and
054     * the result is set as the value for the corresponding property.
055     * </p>
056     *
057     * <p>
058     * Spring will not register inner beans with IDs so that the bean definition can be retrieved through the factory,
059     * therefore this post processor adds them as top level registered beans
060     * </p>
061     *
062     * TODO: convert to dictionary bean processor
063     *
064     * @author Kuali Rice Team (rice.collab@kuali.org)
065     */
066    public class UifBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
067        private static final Log LOG = LogFactory.getLog(UifBeanFactoryPostProcessor.class);
068    
069        /**
070         * Iterates through all beans in the factory and invokes processing
071         *
072         * @param beanFactory bean factory instance to process
073         * @throws org.springframework.beans.BeansException
074         */
075        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
076            Set<String> processedBeanNames = new HashSet<String>();
077    
078            LOG.info("Beginning post processing of bean factory for UIF expressions");
079    
080            String[] beanNames = beanFactory.getBeanDefinitionNames();
081            for (int i = 0; i < beanNames.length; i++) {
082                String beanName = beanNames[i];
083                BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
084    
085                processBeanDefinition(beanName, beanDefinition, beanFactory, processedBeanNames);
086            }
087    
088            LOG.info("Finished post processing of bean factory for UIF expressions");
089        }
090    
091        /**
092         * Processes a top level (non nested) bean definition for expressions
093         *
094         * <p>
095         * A bean that is non nested (or top of a collection) will hold all the expressions for the graph. A new
096         * expression graph is initialized and expressions are collected as the bean and all its children are processed.
097         * The expression graph is then set as a property on the top bean definition
098         * </p>
099         *
100         * @param beanName name of the bean to process
101         * @param beanDefinition bean definition to process
102         * @param beanFactory factory holding all the bean definitions
103         * @param processedBeanNames set of bean names that have already been processed
104         */
105        protected void processBeanDefinition(String beanName, BeanDefinition beanDefinition,
106                ConfigurableListableBeanFactory beanFactory, Set<String> processedBeanNames) {
107            Class<?> beanClass = getBeanClass(beanDefinition, beanFactory);
108            if ((beanClass == null) || !UifDictionaryBean.class.isAssignableFrom(beanClass) || processedBeanNames.contains(
109                    beanName)) {
110                  return;
111            }
112    
113            // process bean definition and all nested definitions for expressions
114            ManagedMap<String, String> expressionGraph = new ManagedMap<String, String>();
115            MutablePropertyValues pvs = beanDefinition.getPropertyValues();
116            if (pvs.contains(UifPropertyPaths.EXPRESSION_GRAPH)) {
117                expressionGraph = (ManagedMap<String, String>) pvs.getPropertyValue(UifPropertyPaths.EXPRESSION_GRAPH)
118                        .getValue();
119                if (expressionGraph == null) {
120                    expressionGraph = new ManagedMap<String, String>();
121                }
122            }
123    
124            expressionGraph.setMergeEnabled(false);
125            processNestedBeanDefinition(beanName, beanDefinition, "", expressionGraph, beanFactory, processedBeanNames);
126    
127            // add property for expression graph
128            pvs = beanDefinition.getPropertyValues();
129            pvs.addPropertyValue(UifPropertyPaths.EXPRESSION_GRAPH, expressionGraph);
130        }
131    
132        /**
133         * If the bean class is type UifDictionaryBean, iterate through configured property values
134         * and check for expressions
135         *
136         * @param beanName name of the bean in the factory (only set for top level beans, not nested)
137         * @param beanDefinition bean definition to process for expressions
138         * @param nestedPropertyName
139         * @param expressionGraph
140         * @param beanFactory bean factory being processed
141         * @param processedBeanNames
142         */
143        protected void processNestedBeanDefinition(String beanName, BeanDefinition beanDefinition,
144                String nestedPropertyName, Map<String, String> expressionGraph,
145                ConfigurableListableBeanFactory beanFactory, Set<String> processedBeanNames) {
146            Class<?> beanClass = getBeanClass(beanDefinition, beanFactory);
147            if ((beanClass == null) || !UifDictionaryBean.class.isAssignableFrom(beanClass) || processedBeanNames.contains(
148                    beanName)) {
149                return;
150            }
151    
152            LOG.debug("Processing bean name '" + beanName + "'");
153    
154            Map<String, String> parentExpressionGraph = getExpressionGraphFromParent(beanDefinition.getParentName(),
155                    beanFactory, processedBeanNames);
156    
157            // process expressions on property values
158            MutablePropertyValues pvs = beanDefinition.getPropertyValues();
159            PropertyValue[] pvArray = pvs.getPropertyValues();
160            for (PropertyValue pv : pvArray) {
161                if (pv.getName().equals(UifPropertyPaths.EXPRESSION_GRAPH)) {
162                    continue;
163                }
164    
165                String propertyPath = pv.getName();
166                if (StringUtils.isNotBlank(nestedPropertyName)) {
167                    propertyPath = nestedPropertyName + "." + propertyPath;
168                }
169    
170                // for reloading, need to remove the property from the previously loaded bean definition
171                if (expressionGraph.containsKey(propertyPath)) {
172                    expressionGraph.remove(propertyPath);
173                }
174    
175                if (hasExpression(pv.getValue())) {
176                    // process expression
177                    String strValue = getStringValue(pv.getValue());
178                    expressionGraph.put(propertyPath, strValue);
179    
180                    // remove property value so expression will not cause binding exception
181                    pvs.removePropertyValue(pv.getName());
182                } else {
183                    // process nested objects
184                    Object newValue = processPropertyValue(propertyPath, pv.getName(), pv.getValue(), beanDefinition,
185                            parentExpressionGraph, expressionGraph, beanFactory, processedBeanNames);
186    
187                    pvs.removePropertyValue(pv.getName());
188                    pvs.addPropertyValue(pv.getName(), newValue);
189                }
190    
191                // removed expression (if exists) from parent map since the property was set on child
192                if (parentExpressionGraph.containsKey(pv.getName())) {
193                    parentExpressionGraph.remove(pv.getName());
194                }
195            }
196    
197            // if nested bean set expression graph to null so it is not inherited from parent definition
198            if (StringUtils.isNotBlank(nestedPropertyName)) {
199                pvs.addPropertyValue(UifPropertyPaths.EXPRESSION_GRAPH, null);
200            }
201    
202            // add remaining expressions from parent to expression graph
203            for (Map.Entry<String, String> parentExpression : parentExpressionGraph.entrySet()) {
204                String expressionPath = parentExpression.getKey();
205                if (StringUtils.isNotBlank(nestedPropertyName)) {
206                    expressionPath = nestedPropertyName + "." + expressionPath;
207                }
208                expressionGraph.put(expressionPath, parentExpression.getValue());
209            }
210    
211            // if bean name is given and factory does not have it registered we need to add it (inner beans that
212            // were given an id)
213            if (StringUtils.isNotBlank(beanName) && !StringUtils.contains(beanName, "$") && !StringUtils.contains(beanName,
214                    "#") && !beanFactory.containsBean(beanName)) {
215                ((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(beanName, beanDefinition);
216            }
217    
218            if (StringUtils.isNotBlank(beanName)) {
219                processedBeanNames.add(beanName);
220            }
221        }
222    
223        /**
224         * Retrieves the class for the object that will be created from the bean definition. Since the class might not
225         * be configured on the bean definition, but by a parent, each parent bean definition is recursively checked for
226         * a class until one is found
227         *
228         * @param beanDefinition bean definition to get class for
229         * @param beanFactory bean factory that contains the bean definition
230         * @return class configured for the bean definition, or null
231         */
232        protected Class<?> getBeanClass(BeanDefinition beanDefinition, ConfigurableListableBeanFactory beanFactory) {
233            if (StringUtils.isNotBlank(beanDefinition.getBeanClassName())) {
234                try {
235                    return Class.forName(beanDefinition.getBeanClassName());
236                } catch (ClassNotFoundException e) {
237                    // swallow exception and return null so bean is not processed
238                    return null;
239                }
240            } else if (StringUtils.isNotBlank(beanDefinition.getParentName())) {
241                BeanDefinition parentBeanDefinition = beanFactory.getBeanDefinition(beanDefinition.getParentName());
242                if (parentBeanDefinition != null) {
243                    return getBeanClass(parentBeanDefinition, beanFactory);
244                }
245            }
246    
247            return null;
248        }
249    
250        /**
251         * Retrieves the expression graph map set on the bean with given name. If the bean has not been processed
252         * by the bean factory post processor, that is done before retrieving the map
253         *
254         * @param parentBeanName name of the parent bean to retrieve map for (if empty a new map will be returned)
255         * @param beanFactory bean factory to retrieve bean definition from
256         * @param processedBeanNames set of bean names that have been processed so far
257         * @return expression graph map from parent or new instance
258         */
259        protected Map<String, String> getExpressionGraphFromParent(String parentBeanName,
260                ConfigurableListableBeanFactory beanFactory, Set<String> processedBeanNames) {
261            Map<String, String> expressionGraph = new HashMap<String, String>();
262            if (StringUtils.isBlank(parentBeanName) || !beanFactory.containsBeanDefinition(parentBeanName)) {
263                return expressionGraph;
264            }
265    
266            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(parentBeanName);
267            if (!processedBeanNames.contains(parentBeanName)) {
268                processBeanDefinition(parentBeanName, beanDefinition, beanFactory, processedBeanNames);
269            }
270    
271            MutablePropertyValues pvs = beanDefinition.getPropertyValues();
272            PropertyValue propertyExpressionsPV = pvs.getPropertyValue(UifPropertyPaths.EXPRESSION_GRAPH);
273            if (propertyExpressionsPV != null) {
274                Object value = propertyExpressionsPV.getValue();
275                if ((value != null) && (value instanceof ManagedMap)) {
276                    expressionGraph.putAll((ManagedMap) value);
277                }
278            }
279    
280            return expressionGraph;
281        }
282    
283        /**
284         * Checks whether the given property value is of String type, and if so whether it contains the expression
285         * placholder(s)
286         *
287         * @param propertyValue value to check for expressions
288         * @return true if the property value contains expression(s), false if it does not
289         */
290        protected boolean hasExpression(Object propertyValue) {
291            if (propertyValue != null) {
292                // if value is string, check for el expression
293                String strValue = getStringValue(propertyValue);
294                if (strValue != null) {
295                    String elPlaceholder = StringUtils.substringBetween(strValue, UifConstants.EL_PLACEHOLDER_PREFIX,
296                            UifConstants.EL_PLACEHOLDER_SUFFIX);
297                    if (StringUtils.isNotBlank(elPlaceholder)) {
298                        return true;
299                    }
300                }
301            }
302    
303            return false;
304        }
305    
306        /**
307         * Processes the given property name/value pair for complex objects, such as bean definitions or collections,
308         * which if found will be processed for contained property expression values
309         *
310         * @param nestedPropertyName nested path of the property whose value is being processed
311         * @param propertyName name of the property in the bean definition being processed
312         * @param propertyValue value to check
313         * @param beanDefinition bean definition the property belongs to
314         * @param parentExpressionGraph map that holds property expressions for the parent bean definition, used for
315         * merging
316         * @param expressionGraph map that holds property expressions for the bean definition being processed
317         * @param beanFactory bean factory that contains the bean definition being processed
318         * @param processedBeanNames set of bean names that have been processed so far
319         * @return new value to set for property
320         */
321        protected Object processPropertyValue(String nestedPropertyName, String propertyName, Object propertyValue,
322                BeanDefinition beanDefinition, Map<String, String> parentExpressionGraph,
323                Map<String, String> expressionGraph, ConfigurableListableBeanFactory beanFactory,
324                Set<String> processedBeanNames) {
325            boolean clearExpressionsForNull = false;
326            if (propertyValue instanceof TypedStringValue) {
327                TypedStringValue typedStringValue = (TypedStringValue) propertyValue;
328    
329                String value = typedStringValue.getValue();
330                if (value == null) {
331                    clearExpressionsForNull = true;
332                }
333            } else if (propertyValue == null) {
334                clearExpressionsForNull = true;
335            }
336    
337            // if property is object and set to null, clear any parent expressions for the property
338            if (clearExpressionsForNull) {
339                removeExpressionsByPrefix(nestedPropertyName, expressionGraph);
340                removeExpressionsByPrefix(propertyName, parentExpressionGraph);
341    
342                return propertyValue;
343            }
344    
345            // process nested bean definitions
346            if ((propertyValue instanceof BeanDefinition) || (propertyValue instanceof BeanDefinitionHolder)) {
347                String beanName = null;
348                BeanDefinition beanDefinitionValue;
349                if (propertyValue instanceof BeanDefinition) {
350                    beanDefinitionValue = (BeanDefinition) propertyValue;
351                } else {
352                    beanDefinitionValue = ((BeanDefinitionHolder) propertyValue).getBeanDefinition();
353                    beanName = ((BeanDefinitionHolder) propertyValue).getBeanName();
354                }
355    
356                // since overriding the entire bean, clear any expressions from parent that start with the bean property
357                removeExpressionsByPrefix(nestedPropertyName, expressionGraph);
358                removeExpressionsByPrefix(propertyName, parentExpressionGraph);
359    
360                processNestedBeanDefinition(beanName, beanDefinitionValue, nestedPropertyName, expressionGraph, beanFactory,
361                        processedBeanNames);
362    
363                return propertyValue;
364            }
365    
366            // recurse into collections
367            if (propertyValue instanceof Object[]) {
368                visitArray(nestedPropertyName, parentExpressionGraph, expressionGraph, (Object[]) propertyValue, beanFactory,
369                        processedBeanNames);
370            } else if (propertyValue instanceof List) {
371                visitList(nestedPropertyName, propertyName, beanDefinition, parentExpressionGraph, expressionGraph,
372                        (List) propertyValue, beanFactory, processedBeanNames);
373            } else if (propertyValue instanceof Set) {
374                visitSet(nestedPropertyName, parentExpressionGraph, expressionGraph, (Set) propertyValue, beanFactory,
375                        processedBeanNames);
376            } else if (propertyValue instanceof Map) {
377                visitMap(nestedPropertyName, parentExpressionGraph, expressionGraph, (Map) propertyValue, beanFactory,
378                        processedBeanNames);
379            }
380    
381            // others (primitive) just return value as is
382            return propertyValue;
383        }
384    
385        /**
386         * Removes entries from the given expressions map whose key starts with the given prefix
387         *
388         * @param propertyNamePrefix prefix to search for and remove
389         * @param expressionGraph map of property expressions to filter
390         */
391        protected void removeExpressionsByPrefix(String propertyNamePrefix, Map<String, String> expressionGraph) {
392            Map<String, String> adjustedExpressionGraph = new HashMap<String, String>();
393            for (String propertyName : expressionGraph.keySet()) {
394                if (!propertyName.startsWith(propertyNamePrefix + ".")) {
395                    adjustedExpressionGraph.put(propertyName, expressionGraph.get(propertyName));
396                }
397            }
398    
399            expressionGraph.clear();
400            expressionGraph.putAll(adjustedExpressionGraph);
401        }
402    
403        /**
404         * Determines whether the given value is of String type and if so returns the string value
405         *
406         * @param value object value to check
407         * @return string value for object or null if object is not a string type
408         */
409        protected String getStringValue(Object value) {
410            if (value instanceof TypedStringValue) {
411                TypedStringValue typedStringValue = (TypedStringValue) value;
412                return typedStringValue.getValue();
413            } else if (value instanceof String) {
414                return (String) value;
415            }
416    
417            return null;
418        }
419    
420        @SuppressWarnings("unchecked")
421        protected void visitArray(String propertyName, Map<String, String> parentExpressionGraph,
422                Map<String, String> expressionGraph, Object array, ConfigurableListableBeanFactory beanFactory,
423                Set<String> processedBeanNames) {
424            Object newArray = null;
425            Object[] arrayVal = null;
426    
427            boolean isMergeEnabled = false;
428            if (array instanceof ManagedArray) {
429                isMergeEnabled = ((ManagedArray) array).isMergeEnabled();
430                arrayVal = (Object[]) ((ManagedArray) array).getSource();
431    
432                newArray = new ManagedArray(((ManagedArray) array).getElementTypeName(), arrayVal.length);
433                ((ManagedArray) newArray).setMergeEnabled(isMergeEnabled);
434            } else {
435                arrayVal = (Object[]) array;
436                newArray = new Object[arrayVal.length];
437            }
438    
439            for (int i = 0; i < arrayVal.length; i++) {
440                Object elem = arrayVal[i];
441                String elemPropertyName = propertyName + "[" + i + "]";
442    
443                if (hasExpression(elem)) {
444                    String strValue = getStringValue(elem);
445                    expressionGraph.put(elemPropertyName, strValue);
446                    arrayVal[i] = null;
447                } else {
448                    // process set value bean definition as a top level bean
449                    if ((elem instanceof BeanDefinition) || (elem instanceof BeanDefinitionHolder)) {
450                        String beanName = null;
451                        BeanDefinition beanDefinition;
452                        if (elem instanceof BeanDefinition) {
453                            beanDefinition = (BeanDefinition) elem;
454                        } else {
455                            beanDefinition = ((BeanDefinitionHolder) elem).getBeanDefinition();
456                            beanName = ((BeanDefinitionHolder) elem).getBeanName();
457                        }
458    
459                        processBeanDefinition(beanName, beanDefinition, beanFactory, processedBeanNames);
460                    }
461    
462                    arrayVal[i] = elem;
463                }
464    
465                if (isMergeEnabled && parentExpressionGraph.containsKey(elemPropertyName)) {
466                    parentExpressionGraph.remove(elemPropertyName);
467                }
468            }
469    
470            // determine if we need to clear any parent expressions for this list
471            if (!isMergeEnabled) {
472                // clear any expressions that match the property name minus index
473                Map<String, String> adjustedParentExpressionGraph = new HashMap<String, String>();
474                for (Map.Entry<String, String> parentExpression : parentExpressionGraph.entrySet()) {
475                    if (!parentExpression.getKey().startsWith(propertyName + "[")) {
476                        adjustedParentExpressionGraph.put(parentExpression.getKey(), parentExpression.getValue());
477                    }
478                }
479    
480                parentExpressionGraph.clear();
481                parentExpressionGraph.putAll(adjustedParentExpressionGraph);
482            }
483    
484            if (array instanceof ManagedArray) {
485                ((ManagedArray) array).setSource(newArray);
486            } else {
487                array = newArray;
488            }
489        }
490    
491        @SuppressWarnings("unchecked")
492        protected void visitList(String nestedPropertyName, String propertyName, BeanDefinition beanDefinition,
493                Map<String, String> parentExpressionGraph, Map<String, String> expressionGraph, List listVal,
494                ConfigurableListableBeanFactory beanFactory, Set<String> processedBeanNames) {
495            boolean isMergeEnabled = false;
496            if (listVal instanceof ManagedList) {
497                isMergeEnabled = ((ManagedList) listVal).isMergeEnabled();
498            }
499    
500            ManagedList newList = new ManagedList();
501            newList.setMergeEnabled(isMergeEnabled);
502    
503            // if merging, need to find size of parent list so we can know which element to set
504            // when evaluating expressions
505            int parentListSize = 0;
506            if (isMergeEnabled && StringUtils.isNotBlank(beanDefinition.getParentName())) {
507                BeanDefinition parentBeanDefinition = beanFactory.getMergedBeanDefinition(beanDefinition.getParentName());
508                PropertyValue parentListPropertyValue = parentBeanDefinition.getPropertyValues().getPropertyValue(
509                        propertyName);
510                if (parentListPropertyValue != null) {
511                    List parentList = (List) parentListPropertyValue.getValue();
512                    parentListSize = parentList.size();
513                }
514            }
515    
516            for (int i = 0; i < listVal.size(); i++) {
517                Object elem = listVal.get(i);
518    
519                int elementPosition = i + parentListSize;
520                String elemPropertyName = nestedPropertyName + "[" + elementPosition + "]";
521    
522                if (hasExpression(elem)) {
523                    String strValue = getStringValue(elem);
524    
525                    expressionGraph.put(elemPropertyName, strValue);
526                    newList.add(i, null);
527                } else {
528                    // process list value bean definition as a top level bean
529                    if ((elem instanceof BeanDefinition) || (elem instanceof BeanDefinitionHolder)) {
530                        String beanName = null;
531                        BeanDefinition beanDefinitionValue;
532                        if (elem instanceof BeanDefinition) {
533                            beanDefinitionValue = (BeanDefinition) elem;
534                        } else {
535                            beanDefinitionValue = ((BeanDefinitionHolder) elem).getBeanDefinition();
536                            beanName = ((BeanDefinitionHolder) elem).getBeanName();
537                        }
538    
539                        processBeanDefinition(beanName, beanDefinitionValue, beanFactory, processedBeanNames);
540                    }
541    
542                    newList.add(i, elem);
543                }
544            }
545    
546            // determine if we need to clear any parent expressions for this list
547            if (!isMergeEnabled) {
548                // clear any expressions that match the property name minus index
549                Map<String, String> adjustedParentExpressionGraph = new HashMap<String, String>();
550                for (Map.Entry<String, String> parentExpression : parentExpressionGraph.entrySet()) {
551                    if (!parentExpression.getKey().startsWith(nestedPropertyName + "[")) {
552                        adjustedParentExpressionGraph.put(parentExpression.getKey(), parentExpression.getValue());
553                    }
554                }
555    
556                parentExpressionGraph.clear();
557                parentExpressionGraph.putAll(adjustedParentExpressionGraph);
558            }
559    
560            listVal.clear();
561            listVal.addAll(newList);
562        }
563    
564        @SuppressWarnings("unchecked")
565        protected void visitSet(String propertyName, Map<String, String> parentPropertyExpressions,
566                Map<String, String> propertyExpressions, Set setVal, ConfigurableListableBeanFactory beanFactory,
567                Set<String> processedBeanNames) {
568            boolean isMergeEnabled = false;
569            if (setVal instanceof ManagedSet) {
570                isMergeEnabled = ((ManagedSet) setVal).isMergeEnabled();
571            }
572    
573            ManagedSet newSet = new ManagedSet();
574            newSet.setMergeEnabled(isMergeEnabled);
575    
576            for (Object elem : setVal) {
577                if (hasExpression(elem)) {
578                    String strValue = getStringValue(elem);
579                    propertyExpressions.put(propertyName + ExpressionEvaluatorService.EMBEDDED_PROPERTY_NAME_ADD_INDICATOR,
580                            strValue);
581                } else {
582                    // process set value bean definition as a top level bean
583                    if ((elem instanceof BeanDefinition) || (elem instanceof BeanDefinitionHolder)) {
584                        String beanName = null;
585                        BeanDefinition beanDefinition;
586                        if (elem instanceof BeanDefinition) {
587                            beanDefinition = (BeanDefinition) elem;
588                        } else {
589                            beanDefinition = ((BeanDefinitionHolder) elem).getBeanDefinition();
590                            beanName = ((BeanDefinitionHolder) elem).getBeanName();
591                        }
592    
593                        processBeanDefinition(beanName, beanDefinition, beanFactory, processedBeanNames);
594                    }
595    
596                    newSet.add(elem);
597                }
598            }
599    
600            // determine if we need to clear any parent expressions for this list
601            if (!isMergeEnabled) {
602                // clear any expressions that match the property name minus index
603                Map<String, String> adjustedParentExpressions = new HashMap<String, String>();
604                for (Map.Entry<String, String> parentExpression : parentPropertyExpressions.entrySet()) {
605                    if (!parentExpression.getKey().startsWith(
606                            propertyName + ExpressionEvaluatorService.EMBEDDED_PROPERTY_NAME_ADD_INDICATOR)) {
607                        adjustedParentExpressions.put(parentExpression.getKey(), parentExpression.getValue());
608                    }
609                }
610    
611                parentPropertyExpressions.clear();
612                parentPropertyExpressions.putAll(adjustedParentExpressions);
613            }
614    
615            setVal.clear();
616            setVal.addAll(newSet);
617        }
618    
619        @SuppressWarnings("unchecked")
620        protected void visitMap(String propertyName, Map<String, String> parentExpressionGraph,
621                Map<String, String> expressionGraph, Map<?, ?> mapVal, ConfigurableListableBeanFactory beanFactory,
622                Set<String> processedBeanNames) {
623            boolean isMergeEnabled = false;
624            if (mapVal instanceof ManagedMap) {
625                isMergeEnabled = ((ManagedMap) mapVal).isMergeEnabled();
626            }
627    
628            ManagedMap newMap = new ManagedMap();
629            newMap.setMergeEnabled(isMergeEnabled);
630    
631            for (Map.Entry entry : mapVal.entrySet()) {
632                Object key = entry.getKey();
633                Object val = entry.getValue();
634    
635                String keyStr = getStringValue(key);
636                String elemPropertyName = propertyName + "['" + keyStr + "']";
637    
638                if (hasExpression(val)) {
639                    String strValue = getStringValue(val);
640                    expressionGraph.put(elemPropertyName, strValue);
641                } else {
642                    // process map value bean definition as a top level bean
643                    if ((val instanceof BeanDefinition) || (val instanceof BeanDefinitionHolder)) {
644                        String beanName = null;
645                        BeanDefinition beanDefinition;
646                        if (val instanceof BeanDefinition) {
647                            beanDefinition = (BeanDefinition) val;
648                        } else {
649                            beanDefinition = ((BeanDefinitionHolder) val).getBeanDefinition();
650                            beanName = ((BeanDefinitionHolder) val).getBeanName();
651                        }
652    
653                        processBeanDefinition(beanName, beanDefinition, beanFactory, processedBeanNames);
654                    }
655    
656                    newMap.put(key, val);
657                }
658    
659                if (isMergeEnabled && parentExpressionGraph.containsKey(elemPropertyName)) {
660                    parentExpressionGraph.remove(elemPropertyName);
661                }
662            }
663    
664            if (!isMergeEnabled) {
665                // clear any expressions that match the property minus key
666                Map<String, String> adjustedParentExpressionGraph = new HashMap<String, String>();
667                for (Map.Entry<String, String> parentExpression : parentExpressionGraph.entrySet()) {
668                    if (!parentExpression.getKey().startsWith(propertyName + "[")) {
669                        adjustedParentExpressionGraph.put(parentExpression.getKey(), parentExpression.getValue());
670                    }
671                }
672    
673                parentExpressionGraph.clear();
674                parentExpressionGraph.putAll(adjustedParentExpressionGraph);
675            }
676    
677            mapVal.clear();
678            mapVal.putAll(newMap);
679        }
680    }