001/**
002 * Copyright 2005-2018 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 */
016package org.kuali.rice.krad.datadictionary.uif;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021import org.kuali.rice.krad.uif.UifConstants;
022import org.kuali.rice.krad.uif.UifPropertyPaths;
023import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
024import org.springframework.beans.BeansException;
025import org.springframework.beans.MutablePropertyValues;
026import org.springframework.beans.PropertyValue;
027import org.springframework.beans.factory.config.BeanDefinition;
028import org.springframework.beans.factory.config.BeanDefinitionHolder;
029import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
030import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
031import org.springframework.beans.factory.config.TypedStringValue;
032import org.springframework.beans.factory.support.ManagedArray;
033import org.springframework.beans.factory.support.ManagedList;
034import org.springframework.beans.factory.support.ManagedMap;
035import org.springframework.beans.factory.support.ManagedSet;
036
037import java.util.HashMap;
038import java.util.HashSet;
039import java.util.List;
040import java.util.Map;
041import java.util.Set;
042
043/**
044 * Post processes the bean factory to handle UIF property expressions and IDs on inner beans
045 *
046 * <p>
047 * Conditional logic can be implemented with the UIF dictionary by means of property expressions. These are
048 * expressions that follow SPEL and can be given as the value for a property using the @{} placeholder. Since such
049 * a value would cause an exception when creating the object if the property is a non-string type (value cannot be
050 * converted), we need to move those expressions to a Map for processing, and then remove the original property
051 * configuration containing the expression. The expressions are then evaluated during the view apply model phase and
052 * the result is set as the value for the corresponding property.
053 * </p>
054 *
055 * <p>
056 * Spring will not register inner beans with IDs so that the bean definition can be retrieved through the factory,
057 * therefore this post processor adds them as top level registered beans
058 * </p>
059 *
060 * TODO: convert to dictionary bean processor
061 *
062 * @author Kuali Rice Team (rice.collab@kuali.org)
063 */
064public class UifBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
065    private static final Log LOG = LogFactory.getLog(UifBeanFactoryPostProcessor.class);
066
067    public UifBeanFactoryPostProcessor() {
068    }
069
070    /**
071     * Iterates through all beans in the factory and invokes processing
072     *
073     * @param beanFactory bean factory instance to process
074     * @throws org.springframework.beans.BeansException
075     */
076    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
077        Set<String> processedBeanNames = new HashSet<String>();
078
079        LOG.info("Beginning post processing of bean factory for UIF expressions");
080
081        String[] beanNames = beanFactory.getBeanDefinitionNames();
082        for (int i = 0; i < beanNames.length; i++) {
083            String beanName = beanNames[i];
084            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
085
086            processBeanDefinition(beanName, beanDefinition, beanFactory, processedBeanNames);
087        }
088
089        LOG.info("Finished post processing of bean factory for UIF expressions");
090    }
091
092    /**
093     * Processes a top level (non nested) bean definition for expressions
094     *
095     * <p>
096     * A bean that is non nested (or top of a collection) will hold all the expressions for the graph. A new
097     * expression graph is initialized and expressions are collected as the bean and all its children are processed.
098     * The expression graph is then set as a property on the top bean definition
099     * </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}