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