View Javadoc

1   /*
2    * $Id: DefaultBeanAssembler.java 19191 2010-08-25 21:05:23Z tcarlson $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.config.spring.parsers.assembly;
12  
13  import org.mule.config.spring.MuleHierarchicalBeanDefinitionParserDelegate;
14  import org.mule.config.spring.parsers.assembly.configuration.PropertyConfiguration;
15  import org.mule.config.spring.parsers.assembly.configuration.SingleProperty;
16  import org.mule.config.spring.parsers.assembly.configuration.SinglePropertyLiteral;
17  import org.mule.config.spring.parsers.assembly.configuration.SinglePropertyWrapper;
18  import org.mule.config.spring.parsers.collection.ChildListEntryDefinitionParser;
19  import org.mule.config.spring.parsers.collection.ChildMapEntryDefinitionParser;
20  import org.mule.config.spring.util.SpringXMLUtils;
21  import org.mule.util.ClassUtils;
22  import org.mule.util.MapCombiner;
23  
24  import java.lang.reflect.Method;
25  import java.util.Collection;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.StringTokenizer;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.springframework.beans.MutablePropertyValues;
33  import org.springframework.beans.PropertyValue;
34  import org.springframework.beans.PropertyValues;
35  import org.springframework.beans.factory.config.BeanDefinition;
36  import org.springframework.beans.factory.config.MapFactoryBean;
37  import org.springframework.beans.factory.config.RuntimeBeanReference;
38  import org.springframework.beans.factory.support.BeanDefinitionBuilder;
39  import org.springframework.beans.factory.support.ManagedList;
40  import org.springframework.beans.factory.support.ManagedMap;
41  import org.w3c.dom.Attr;
42  
43  public class DefaultBeanAssembler implements BeanAssembler
44  {
45  
46      private static Log logger = LogFactory.getLog(DefaultBeanAssembler.class);
47      private PropertyConfiguration beanConfig;
48      protected BeanDefinitionBuilder bean;
49      protected PropertyConfiguration targetConfig;
50      protected BeanDefinition target;
51  
52      public DefaultBeanAssembler(PropertyConfiguration beanConfig, BeanDefinitionBuilder bean,
53                                  PropertyConfiguration targetConfig, BeanDefinition target)
54      {
55          this.beanConfig = beanConfig;
56          this.bean = bean;
57          this.targetConfig = targetConfig;
58          this.target = target;
59      }
60  
61      public BeanDefinitionBuilder getBean()
62      {
63          return bean;
64      }
65  
66      protected void setBean(BeanDefinitionBuilder bean)
67      {
68          this.bean = bean;
69      }
70  
71      public BeanDefinition getTarget()
72      {
73          return target;
74      }
75  
76      protected PropertyConfiguration getBeanConfig()
77      {
78          return beanConfig;
79      }
80  
81      protected PropertyConfiguration getTargetConfig()
82      {
83          return targetConfig;
84      }
85  
86      /**
87       * Add a property defined by an attribute to the bean we are constructing.
88       *
89       * <p>Since an attribute value is always a string, we don't have to deal with complex types
90       * here - the only issue is whether or not we have a reference.  References are detected
91       * by explicit annotation or by the "-ref" at the end of an attribute name.  We do not
92       * check the Spring repo to see if a name already exists since that could lead to
93       * unpredictable behaviour.
94       * (see {@link org.mule.config.spring.parsers.assembly.configuration.PropertyConfiguration})
95       * @param attribute The attribute to add
96       */
97      public void extendBean(Attr attribute)
98      {
99          String oldName = SpringXMLUtils.attributeName(attribute);
100         if (!beanConfig.isIgnored(oldName))
101         {
102             logger.debug(attribute + " for " + bean.getBeanDefinition().getBeanClassName());
103             String oldValue = attribute.getNodeValue();
104             String newName = bestGuessName(beanConfig, oldName, bean.getBeanDefinition().getBeanClassName());
105             Object newValue = beanConfig.translateValue(oldName, oldValue);
106             addPropertyWithReference(bean.getBeanDefinition().getPropertyValues(),
107                     beanConfig.getSingleProperty(oldName), newName, newValue);
108         }
109     }
110 
111     /**
112      * Allow direct access to bean for major hacks
113      *
114      * @param newName The property name to add
115      * @param newValue The property value to add
116      * @param isReference If true, a bean reference is added (and newValue must be a String)
117      */
118     public void extendBean(String newName, Object newValue, boolean isReference)
119     {
120         addPropertyWithReference(bean.getBeanDefinition().getPropertyValues(),
121                 new SinglePropertyLiteral(isReference), newName, newValue);
122     }
123 
124     /**
125      * Add a property defined by an attribute to the parent of the bean we are constructing.
126      *
127      * <p>This is unusual.  Normally you want {@link #extendBean(org.w3c.dom.Attr)}.
128      * @param attribute The attribute to add
129      */
130     public void extendTarget(Attr attribute)
131     {
132         String oldName = SpringXMLUtils.attributeName(attribute);
133         String oldValue = attribute.getNodeValue();
134         String newName = bestGuessName(targetConfig, oldName, bean.getBeanDefinition().getBeanClassName());
135         Object newValue = targetConfig.translateValue(oldName, oldValue);
136         addPropertyWithReference(target.getPropertyValues(),
137                 targetConfig.getSingleProperty(oldName), newName, newValue);
138     }
139 
140     /**
141      * Allow direct access to target for major hacks
142      *
143      * @param newName The property name to add
144      * @param newValue The property value to add
145      * @param isReference If true, a bean reference is added (and newValue must be a String)
146      */
147     public void extendTarget(String newName, Object newValue, boolean isReference)
148     {
149         assertTargetPresent();
150         addPropertyWithReference(target.getPropertyValues(),
151                 new SinglePropertyLiteral(isReference), newName, newValue);
152     }
153     
154     public void extendTarget(String oldName, String newName, Object newValue)
155     {
156         assertTargetPresent();
157         addPropertyWithReference(target.getPropertyValues(),
158                 new SinglePropertyWrapper(oldName, getTargetConfig()), newName, newValue);
159     }
160 
161     /**
162      * Insert the bean we have built into the target (typically the parent bean).
163      *
164      * <p>This is the most complex case because the bean can have an aribtrary type.
165      * 
166      * @param oldName The identifying the bean (typically element name).
167      */
168     public void insertBeanInTarget(String oldName)
169     {
170         logger.debug("insert " + bean.getBeanDefinition().getBeanClassName() + " -> " + target.getBeanClassName());
171         assertTargetPresent();
172         String beanClass = bean.getBeanDefinition().getBeanClassName();
173         PropertyValues sourceProperties = bean.getRawBeanDefinition().getPropertyValues();
174         String newName = bestGuessName(targetConfig, oldName, target.getBeanClassName());
175         MutablePropertyValues targetProperties = target.getPropertyValues();
176         PropertyValue pv = targetProperties.getPropertyValue(newName);
177         Object oldValue = null == pv ? null : pv.getValue();
178 
179         if (! targetConfig.isIgnored(oldName))
180         {
181             if (targetConfig.isCollection(oldName) ||
182                     beanClass.equals(ChildListEntryDefinitionParser.ListEntry.class.getName()))
183             {
184                 if (null == oldValue)
185                 {
186                     if (beanClass.equals(ChildMapEntryDefinitionParser.KeyValuePair.class.getName()) ||
187                             beanClass.equals(MapEntryCombiner.class.getName()) ||
188                             beanClass.equals(MapFactoryBean.class.getName()))
189                     {
190                         // a collection of maps requires an extra intermediate object that does the
191                         // lazy combination/caching of maps when first used
192                         BeanDefinitionBuilder combiner = BeanDefinitionBuilder.rootBeanDefinition(MapCombiner.class);
193                         targetProperties.addPropertyValue(newName, combiner.getBeanDefinition());
194                         MutablePropertyValues combinerProperties = combiner.getBeanDefinition().getPropertyValues();
195                         oldValue = new ManagedList();
196                         pv = new PropertyValue(MapCombiner.LIST, oldValue);
197                         combinerProperties.addPropertyValue(pv);
198                     }
199                     else
200                     {
201                         oldValue = new ManagedList();
202                         pv = new PropertyValue(newName, oldValue);
203                         targetProperties.addPropertyValue(pv);
204                     }
205                 }
206 
207                 List list = retrieveList(oldValue);
208                 if (ChildMapEntryDefinitionParser.KeyValuePair.class.getName().equals(beanClass))
209                 {
210                     list.add(new ManagedMap());
211                     retrieveMap(list.get(list.size() - 1)).put(
212                             sourceProperties.getPropertyValue(ChildMapEntryDefinitionParser.KEY).getValue(),
213                             sourceProperties.getPropertyValue(ChildMapEntryDefinitionParser.VALUE).getValue());
214                 }
215                 else if (beanClass.equals(ChildListEntryDefinitionParser.ListEntry.class.getName()))
216                 {
217                     list.add(sourceProperties.getPropertyValue(ChildListEntryDefinitionParser.VALUE).getValue());
218                 }
219                 else
220                 {
221                     list.add(bean.getBeanDefinition());
222                 }
223             }
224             else
225             {
226                 // not a collection
227 
228                 if (ChildMapEntryDefinitionParser.KeyValuePair.class.getName().equals(beanClass))
229                 {
230                     if (null == pv || null == oldValue)
231                     {
232                         pv = new PropertyValue(newName, new ManagedMap());
233                         targetProperties.addPropertyValue(pv);
234                     }
235                     retrieveMap(pv.getValue()).put(
236                             sourceProperties.getPropertyValue(ChildMapEntryDefinitionParser.KEY).getValue(),
237                             sourceProperties.getPropertyValue(ChildMapEntryDefinitionParser.VALUE).getValue());
238                 }
239                 else
240                 {
241                     targetProperties.addPropertyValue(newName, bean.getBeanDefinition());
242                 }
243             }
244         }
245     }
246     
247     public void insertSingletonBeanInTarget(String propertyName, String singletonName)
248     {
249         String newName = bestGuessName(targetConfig, propertyName, target.getBeanClassName());
250 
251         MutablePropertyValues targetProperties = target.getPropertyValues();
252         PropertyValue pv = targetProperties.getPropertyValue(newName);
253         Object oldValue = null == pv ? null : pv.getValue();
254 
255         if (!targetConfig.isIgnored(propertyName))
256         {
257             if (targetConfig.isCollection(propertyName))
258             {
259                 if (null == oldValue)
260                 {
261                     oldValue = new ManagedList();
262                     pv = new PropertyValue(newName, oldValue);
263                     targetProperties.addPropertyValue(pv);
264                 }
265 
266                 List list = retrieveList(oldValue);
267                 list.add(new RuntimeBeanReference(singletonName));
268             }
269             else
270             {
271                 // not a collection
272                 targetProperties.addPropertyValue(newName, new RuntimeBeanReference(singletonName));
273             }
274         }
275         // getTarget().getPropertyValues().addPropertyValue(newName, new RuntimeBeanReference(singletonName));
276     }
277     
278     protected void insertInTarget(String oldName){
279         
280     }
281 
282     protected static List retrieveList(Object value)
283     {
284         if (value instanceof List)
285         {
286             return (List) value;
287         }
288         else if (isDefinitionOf(value, MapCombiner.class))
289         {
290             return (List) unpackDefinition(value, MapCombiner.LIST);
291         }
292         else
293         {
294             throw new ClassCastException("Collection not of expected type: " + value);
295         }
296     }
297 
298     private static Map retrieveMap(Object value)
299     {
300         if (value instanceof Map)
301         {
302             return (Map) value;
303         }
304         else if (isDefinitionOf(value, MapFactoryBean.class))
305         {
306             return (Map) unpackDefinition(value, "sourceMap");
307         }
308         else
309         {
310             throw new ClassCastException("Map not of expected type: " + value);
311         }
312     }
313 
314     private static boolean isDefinitionOf(Object value, Class clazz)
315     {
316         return value instanceof BeanDefinition &&
317                 ((BeanDefinition) value).getBeanClassName().equals(clazz.getName());
318     }
319 
320     private static Object unpackDefinition(Object definition, String name)
321     {
322         return ((BeanDefinition) definition).getPropertyValues().getPropertyValue(name).getValue();
323     }
324 
325 
326     /**
327      * Copy the properties from the bean we have been building into the target (typically
328      * the parent bean).  In other words, the bean is a facade for the target.
329      *
330      * <p>This assumes that the source bean has been constructed correctly (ie the decisions about
331      * what is ignored, a map, a list, etc) have already been made.   All it does (apart from a
332      * direct copy) is merge collections with those on the target when necessary.
333      */
334     public void copyBeanToTarget()
335     {
336         logger.debug("copy " + bean.getBeanDefinition().getBeanClassName() + " -> " + target.getBeanClassName());
337         assertTargetPresent();
338         MutablePropertyValues targetProperties = target.getPropertyValues();
339         MutablePropertyValues beanProperties = bean.getBeanDefinition().getPropertyValues();
340         for (int i=0;i < beanProperties.size(); i++)
341         {
342             PropertyValue propertyValue = beanProperties.getPropertyValues()[i];
343             addPropertyWithoutReference(targetProperties, new SinglePropertyLiteral(),
344                     propertyValue.getName(), propertyValue.getValue());
345         }
346     }
347 
348     public void setBeanFlag(String flag)
349     {
350         MuleHierarchicalBeanDefinitionParserDelegate.setFlag(bean.getRawBeanDefinition(), flag);
351     }
352 
353     protected void assertTargetPresent()
354     {
355         if (null == target)
356         {
357             throw new IllegalStateException("Bean assembler does not have a target");
358         }
359     }
360 
361     protected void addPropertyWithReference(MutablePropertyValues properties, SingleProperty config,
362                                             String name, Object value)
363     {
364         if (!config.isIgnored())
365         {
366             if (config.isReference())
367             {
368                 if (value instanceof String)
369                 {
370                     if (((String) value).trim().indexOf(" ") > -1)
371                     {
372                         config.setCollection();
373                     }
374                     for (StringTokenizer refs = new StringTokenizer((String) value); refs.hasMoreTokens();)
375                     {
376                         String ref = refs.nextToken();
377                         if (logger.isDebugEnabled())
378                         {
379                             logger.debug("possible non-dependent reference: " + name + "/" + ref);
380                         }
381                         addPropertyWithoutReference(properties, config, name, new RuntimeBeanReference(ref));
382                     }
383                 }
384                 else
385                 {
386                     throw new IllegalArgumentException("Bean reference must be a String: " + name + "/" + value);
387                 }
388             }
389             else
390             {
391                 addPropertyWithoutReference(properties, config, name, value);
392             }
393         }
394     }
395 
396     protected void addPropertyWithoutReference(MutablePropertyValues properties, SingleProperty config,
397                                                String name, Object value)
398     {
399         if (!config.isIgnored())
400         {
401             if (logger.isDebugEnabled())
402             {
403                 logger.debug(name + ": " + value);
404             }
405             Object oldValue = null;
406             if (properties.contains(name))
407             {
408                 oldValue = properties.getPropertyValue(name).getValue();
409             }
410             // merge collections
411             if (config.isCollection() || oldValue instanceof Collection || value instanceof Collection)
412             {
413                 Collection values = new ManagedList();
414                 if (null != oldValue)
415                 {
416                     properties.removePropertyValue(name);
417                     if (oldValue instanceof Collection)
418                     {
419                         values.addAll((Collection) oldValue);
420                     }
421                     else
422                     {
423                         values.add(oldValue);
424                     }
425                 }
426                 if (value instanceof Collection)
427                 {
428                     values.addAll((Collection) value);
429                 }
430                 else
431                 {
432                     values.add(value);
433                 }
434                 properties.addPropertyValue(name, values);
435             }
436             else
437             {
438                 properties.addPropertyValue(name, value);
439             }
440         }
441     }
442 
443     protected static String bestGuessName(PropertyConfiguration config, String oldName, String className)
444     {
445         String newName = config.translateName(oldName);
446         if (! methodExists(className, newName))
447         {
448             String plural = newName + "s";
449             if (methodExists(className, plural))
450             {
451                 // this lets us avoid setting addCollection in the majority of cases
452                 config.addCollection(oldName);
453                 return plural;
454             }
455             if (newName.endsWith("y"))
456             {
457                 String pluraly = newName.substring(0, newName.length()-1) + "ies";
458                 if (methodExists(className, pluraly))
459                 {
460                     // this lets us avoid setting addCollection in the majority of cases
461                     config.addCollection(oldName);
462                     return pluraly;
463                 }
464             }
465         }
466         return newName;
467     }
468 
469     protected static boolean methodExists(String className, String newName)
470     {
471         try
472         {
473             // is there a better way than this?!
474             // BeanWrapperImpl instantiates an instance, which we don't want.
475             // if there really is no better way, i guess it should go in
476             // class or bean utils.
477             Class clazz = ClassUtils.getClass(className);
478             Method[] methods = clazz.getMethods();
479             String setter = "set" + newName;
480             for (int i = 0; i < methods.length; ++i)
481             {
482                 if (methods[i].getName().equalsIgnoreCase(setter))
483                 {
484                     return true;
485                 }
486             }
487         }
488         catch (Exception e)
489         {
490             logger.debug("Could not access bean class " + className, e);
491         }
492         return false;
493     }
494 
495 
496 }