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