View Javadoc

1   /***
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.xbean.spring.context.v2c;
18  
19  import java.beans.BeanInfo;
20  import java.beans.PropertyDescriptor;
21  import java.beans.PropertyEditor;
22  import java.io.ByteArrayInputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.Enumeration;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Properties;
35  import java.util.Set;
36  
37  import javax.xml.XMLConstants;
38  
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  import org.apache.xbean.spring.context.impl.MappingMetaData;
42  import org.apache.xbean.spring.context.impl.NamedConstructorArgs;
43  import org.apache.xbean.spring.context.impl.NamespaceHelper;
44  import org.springframework.beans.PropertyValue;
45  import org.springframework.beans.PropertyEditorRegistrar;
46  import org.springframework.beans.PropertyEditorRegistry;
47  import org.springframework.beans.factory.BeanDefinitionStoreException;
48  import org.springframework.beans.factory.config.BeanDefinition;
49  import org.springframework.beans.factory.config.BeanDefinitionHolder;
50  import org.springframework.beans.factory.config.RuntimeBeanReference;
51  import org.springframework.beans.factory.parsing.BeanComponentDefinition;
52  import org.springframework.beans.factory.support.AbstractBeanDefinition;
53  import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
54  import org.springframework.beans.factory.support.DefaultListableBeanFactory;
55  import org.springframework.beans.factory.support.ManagedList;
56  import org.springframework.beans.factory.support.ManagedMap;
57  import org.springframework.beans.factory.support.RootBeanDefinition;
58  import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
59  import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
60  import org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader;
61  import org.springframework.beans.factory.xml.NamespaceHandler;
62  import org.springframework.beans.factory.xml.ParserContext;
63  import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
64  import org.springframework.context.support.AbstractApplicationContext;
65  import org.springframework.util.StringUtils;
66  
67  import org.w3c.dom.Attr;
68  import org.w3c.dom.Element;
69  import org.w3c.dom.NamedNodeMap;
70  import org.w3c.dom.Node;
71  import org.w3c.dom.NodeList;
72  import org.w3c.dom.Text;
73  
74  /***
75   * An enhanced XML parser capable of handling custom XML schemas.
76   *
77   * @author James Strachan
78   * @version $Id$
79   * @since 2.0
80   */
81  public class XBeanNamespaceHandler implements NamespaceHandler {
82  
83      public static final String SPRING_SCHEMA = "http://xbean.apache.org/schemas/spring/1.0";
84      public static final String SPRING_SCHEMA_COMPAT = "http://xbean.org/schemas/spring/1.0";
85  
86      private static final Log log = LogFactory.getLog(XBeanNamespaceHandler.class);
87  
88      private static final String QNAME_ELEMENT = "qname";
89      
90      private static final String DESCRIPTION_ELEMENT = "description";
91  
92      /***
93       * All the reserved Spring XML element names which cannot be overloaded by
94       * an XML extension
95       */
96      protected static final String[] RESERVED_ELEMENT_NAMES = { 
97              "beans", 
98              DESCRIPTION_ELEMENT, 
99              DefaultBeanDefinitionDocumentReader.IMPORT_ELEMENT,
100             DefaultBeanDefinitionDocumentReader.ALIAS_ELEMENT, 
101             DefaultBeanDefinitionDocumentReader.BEAN_ELEMENT, 
102             BeanDefinitionParserDelegate.CONSTRUCTOR_ARG_ELEMENT, 
103             BeanDefinitionParserDelegate.PROPERTY_ELEMENT, 
104             BeanDefinitionParserDelegate.LOOKUP_METHOD_ELEMENT,
105             BeanDefinitionParserDelegate.REPLACED_METHOD_ELEMENT, 
106             BeanDefinitionParserDelegate.ARG_TYPE_ELEMENT, 
107             BeanDefinitionParserDelegate.REF_ELEMENT, 
108             BeanDefinitionParserDelegate.IDREF_ELEMENT, 
109             BeanDefinitionParserDelegate.VALUE_ELEMENT, 
110             BeanDefinitionParserDelegate.NULL_ELEMENT,
111             BeanDefinitionParserDelegate.LIST_ELEMENT, 
112             BeanDefinitionParserDelegate.SET_ELEMENT, 
113             BeanDefinitionParserDelegate.MAP_ELEMENT, 
114             BeanDefinitionParserDelegate.ENTRY_ELEMENT, 
115             BeanDefinitionParserDelegate.KEY_ELEMENT, 
116             BeanDefinitionParserDelegate.PROPS_ELEMENT, 
117             BeanDefinitionParserDelegate.PROP_ELEMENT,
118             QNAME_ELEMENT };
119 
120     protected static final String[] RESERVED_BEAN_ATTRIBUTE_NAMES = { 
121             AbstractBeanDefinitionParser.ID_ATTRIBUTE, 
122             BeanDefinitionParserDelegate.NAME_ATTRIBUTE, 
123             BeanDefinitionParserDelegate.CLASS_ATTRIBUTE,
124             BeanDefinitionParserDelegate.PARENT_ATTRIBUTE, 
125             BeanDefinitionParserDelegate.DEPENDS_ON_ATTRIBUTE, 
126             BeanDefinitionParserDelegate.FACTORY_METHOD_ATTRIBUTE, 
127             BeanDefinitionParserDelegate.FACTORY_BEAN_ATTRIBUTE,
128             BeanDefinitionParserDelegate.DEPENDENCY_CHECK_ATTRIBUTE, 
129             BeanDefinitionParserDelegate.AUTOWIRE_ATTRIBUTE, 
130             BeanDefinitionParserDelegate.INIT_METHOD_ATTRIBUTE, 
131             BeanDefinitionParserDelegate.DESTROY_METHOD_ATTRIBUTE,
132             BeanDefinitionParserDelegate.ABSTRACT_ATTRIBUTE, 
133             BeanDefinitionParserDelegate.SINGLETON_ATTRIBUTE, 
134             BeanDefinitionParserDelegate.LAZY_INIT_ATTRIBUTE };
135 
136     private static final String JAVA_PACKAGE_PREFIX = "java://";
137 
138     private static final String BEAN_REFERENCE_PREFIX = "#";
139     private static final String NULL_REFERENCE = "#null";
140 
141     private Set reservedElementNames = new HashSet(Arrays.asList(RESERVED_ELEMENT_NAMES));
142     private Set reservedBeanAttributeNames = new HashSet(Arrays.asList(RESERVED_BEAN_ATTRIBUTE_NAMES));
143     protected final NamedConstructorArgs namedConstructorArgs = new NamedConstructorArgs();
144 
145     private ParserContext parserContext;
146     
147     private XBeanQNameHelper qnameHelper;
148 
149     public void init() {
150     }
151 
152     public BeanDefinition parse(Element element, ParserContext parserContext) {
153         this.parserContext = parserContext;
154         this.qnameHelper = new XBeanQNameHelper(parserContext.getReaderContext());
155         BeanDefinitionHolder holder = parseBeanFromExtensionElement(element);
156         // Only register components: i.e. first level beans (or root element if no <beans> element
157         if (element.getParentNode() == element.getOwnerDocument() || 
158             element.getParentNode().getParentNode() == element.getOwnerDocument()) {
159             BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());
160             BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
161             parserContext.getReaderContext().fireComponentRegistered(componentDefinition);
162         }
163         return holder.getBeanDefinition();
164     }
165 
166     public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
167         if (node instanceof org.w3c.dom.Attr && XMLConstants.XMLNS_ATTRIBUTE.equals(node.getLocalName())) {
168             return definition; // Ignore xmlns="xxx" attributes
169         }
170         throw new IllegalArgumentException("Cannot locate BeanDefinitionDecorator for "
171                         + (node instanceof Element ? "element" : "attribute") + " [" +
172                         node.getLocalName() + "].");
173     }
174 
175     /***
176      * Configures the XmlBeanDefinitionReader to work nicely with extensible XML
177      * using this reader implementation.
178      */
179     public static void configure(AbstractApplicationContext context, XmlBeanDefinitionReader reader) {
180         reader.setNamespaceAware(true);
181         reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);
182     }
183 
184     /***
185      * Registers whatever custom editors we need
186      */
187     public static void registerCustomEditors(DefaultListableBeanFactory beanFactory) {
188         PropertyEditorRegistrar registrar = new PropertyEditorRegistrar() {
189             public void registerCustomEditors(PropertyEditorRegistry registry) {
190                 registry.registerCustomEditor(java.io.File.class, new org.apache.xbean.spring.context.impl.FileEditor());
191                 registry.registerCustomEditor(java.net.URI.class, new org.apache.xbean.spring.context.impl.URIEditor());
192                 registry.registerCustomEditor(java.util.Date.class, new org.apache.xbean.spring.context.impl.DateEditor());
193                 registry.registerCustomEditor(javax.management.ObjectName.class, new org.apache.xbean.spring.context.impl.ObjectNameEditor());
194             }
195         };
196 
197         beanFactory.addPropertyEditorRegistrar(registrar);
198     }
199 
200     /***
201      * Parses the non-standard XML element as a Spring bean definition
202      */
203     protected BeanDefinitionHolder parseBeanFromExtensionElement(Element element, String parentClass, String property) {
204         String uri = element.getNamespaceURI();
205         String localName = getLocalName(element);
206 
207         MappingMetaData metadata = findNamespaceProperties(uri, localName);
208         if (metadata != null) {
209             // lets see if we configured the localName to a bean class
210             String className = getPropertyDescriptor(parentClass, property).getPropertyType().getName();
211             if (className != null) {
212                 return parseBeanFromExtensionElement(element, metadata, className);
213             }
214         }
215         return null;
216     }
217 
218     private BeanDefinitionHolder parseBeanFromExtensionElement(Element element, MappingMetaData metadata, String className) {
219         Element original = cloneElement(element);
220         // lets assume the class name == the package name plus the
221         element.setAttributeNS(null, "class", className);
222         addSpringAttributeValues(className, element);
223         BeanDefinitionHolder definition = parserContext.getDelegate().parseBeanDefinitionElement(element, null);
224         addAttributeProperties(definition, metadata, className, original);
225         addContentProperty(definition, metadata, element);
226         addNestedPropertyElements(definition, metadata, className, element);
227         qnameHelper.coerceNamespaceAwarePropertyValues(definition.getBeanDefinition(), element);
228         declareLifecycleMethods(definition, metadata, element);
229         resolveBeanClass((AbstractBeanDefinition) definition.getBeanDefinition(), definition.getBeanName());
230         namedConstructorArgs.processParameters(definition, metadata);
231         return definition;
232     }
233 
234     protected Class resolveBeanClass(AbstractBeanDefinition bd, String beanName) {
235         if (bd.hasBeanClass()) {
236             return bd.getBeanClass();
237         }
238         try {
239             ClassLoader cl = parserContext.getReaderContext().getReader().getBeanClassLoader();
240             if (cl == null) {
241                 cl = Thread.currentThread().getContextClassLoader();
242             }
243             if (cl == null) {
244                 cl = getClass().getClassLoader();
245             }
246             return bd.resolveBeanClass(cl);
247         }
248         catch (ClassNotFoundException ex) {
249             throw new BeanDefinitionStoreException(bd.getResourceDescription(),
250                     beanName, "Bean class [" + bd.getBeanClassName() + "] not found", ex);
251         }
252         catch (NoClassDefFoundError err) {
253             throw new BeanDefinitionStoreException(bd.getResourceDescription(),
254                     beanName, "Class that bean class [" + bd.getBeanClassName() + "] depends on not found", err);
255         }
256     }
257 
258     
259     /***
260      * Parses the non-standard XML element as a Spring bean definition
261      */
262     protected BeanDefinitionHolder parseBeanFromExtensionElement(Element element) {
263         String uri = element.getNamespaceURI();
264         String localName = getLocalName(element);
265 
266         MappingMetaData metadata = findNamespaceProperties(uri, localName);
267         if (metadata != null) {
268             // lets see if we configured the localName to a bean class
269             String className = metadata.getClassName(localName);
270             if (className != null) {
271                 return parseBeanFromExtensionElement(element, metadata, className);
272             } else {
273                 throw new BeanDefinitionStoreException("Unrecognized xbean element mapping: " + localName + " in namespace " + uri);
274             }
275         } else {
276             if (uri == null) throw new BeanDefinitionStoreException("Unrecognized Spring element: " + localName);
277             else throw new BeanDefinitionStoreException("Unrecognized xbean namespace mapping: " + uri);
278         }
279     }
280 
281     protected void addSpringAttributeValues(String className, Element element) {
282         NamedNodeMap attributes = element.getAttributes();
283         for (int i = 0, size = attributes.getLength(); i < size; i++) {
284             Attr attribute = (Attr) attributes.item(i);
285             String uri = attribute.getNamespaceURI();
286             String localName = attribute.getLocalName();
287 
288             if (uri != null && (uri.equals(SPRING_SCHEMA) || uri.equals(SPRING_SCHEMA_COMPAT))) {
289                 element.setAttributeNS(null, localName, attribute.getNodeValue());
290             }
291         }
292     }
293 
294     /***
295      * Creates a clone of the element and its attribute (though not its content)
296      */
297     protected Element cloneElement(Element element) {
298         Element answer = element.getOwnerDocument().createElementNS(element.getNamespaceURI(), element.getNodeName());
299         NamedNodeMap attributes = element.getAttributes();
300         for (int i = 0, size = attributes.getLength(); i < size; i++) {
301             Attr attribute = (Attr) attributes.item(i);
302             String uri = attribute.getNamespaceURI();
303             answer.setAttributeNS(uri, attribute.getName(), attribute.getNodeValue());
304         }
305         return answer;
306     }
307 
308     /***
309      * Parses attribute names and values as being bean property expressions
310      */
311     protected void addAttributeProperties(BeanDefinitionHolder definition, MappingMetaData metadata, String className,
312             Element element) {
313         NamedNodeMap attributes = element.getAttributes();
314         // First pass on attributes with no namespaces
315         for (int i = 0, size = attributes.getLength(); i < size; i++) {
316             Attr attribute = (Attr) attributes.item(i);
317             String uri = attribute.getNamespaceURI();
318             String localName = attribute.getLocalName();
319             // Skip namespaces
320             if (localName == null || localName.equals("xmlns") || localName.startsWith("xmlns:")) {
321                 continue;
322             }
323             // Add attributes with no namespaces
324             if (isEmpty(uri) && !localName.equals("class")) {
325                 boolean addProperty = true;
326                 if (reservedBeanAttributeNames.contains(localName)) {
327                     // should we allow the property to shine through?
328                     PropertyDescriptor descriptor = getPropertyDescriptor(className, localName);
329                     addProperty = descriptor != null;
330                 }
331                 if (addProperty) {
332                     addAttributeProperty(definition, metadata, element, attribute);
333                 }
334             }
335         }
336         // Second pass on attributes with namespaces
337         for (int i = 0, size = attributes.getLength(); i < size; i++) {
338             Attr attribute = (Attr) attributes.item(i);
339             String uri = attribute.getNamespaceURI();
340             String localName = attribute.getLocalName();
341             // Skip namespaces
342             if (localName == null || localName.equals("xmlns") || localName.startsWith("xmlns:")) {
343                 continue;
344             }
345             // Add attributs with namespaces matching the element ns
346             if (!isEmpty(uri) && uri.equals(element.getNamespaceURI())) {
347                 boolean addProperty = true;
348                 if (reservedBeanAttributeNames.contains(localName)) {
349                     // should we allow the property to shine through?
350                     PropertyDescriptor descriptor = getPropertyDescriptor(className, localName);
351                     addProperty = descriptor != null;
352                 }
353                 if (addProperty) {
354                     addAttributeProperty(definition, metadata, element, attribute);
355                 }
356             }
357         }
358     }
359 
360     protected void addContentProperty(BeanDefinitionHolder definition, MappingMetaData metadata, Element element) {
361         String name = metadata.getContentProperty(getLocalName(element));
362         if (name != null) {
363             String value = getElementText(element);
364             addProperty(definition, metadata, element, name, value);
365         }
366         else {
367             StringBuffer buffer = new StringBuffer();
368             NodeList childNodes = element.getChildNodes();
369             for (int i = 0, size = childNodes.getLength(); i < size; i++) {
370                 Node node = childNodes.item(i);
371                 if (node instanceof Text) {
372                     buffer.append(((Text) node).getData());
373                 }
374             }
375 
376             ByteArrayInputStream in = new ByteArrayInputStream(buffer.toString().getBytes());
377             Properties properties = new Properties();
378             try {
379                 properties.load(in);
380             }
381             catch (IOException e) {
382                 return;
383             }
384             Enumeration enumeration = properties.propertyNames();
385             while (enumeration.hasMoreElements()) {
386                 String propertyName = (String) enumeration.nextElement();
387                 String propertyEditor = metadata.getPropertyEditor(getLocalName(element), propertyName);
388                 
389                 Object value = getValue(properties.getProperty(propertyName), propertyEditor);
390                 definition.getBeanDefinition().getPropertyValues().addPropertyValue(propertyName, value);
391             }
392         }
393     }
394 
395     protected void addAttributeProperty(BeanDefinitionHolder definition, MappingMetaData metadata, Element element,
396             Attr attribute) {
397         String localName = attribute.getLocalName();
398         String value = attribute.getValue();
399         addProperty(definition, metadata, element, localName, value);
400     }
401 
402     /***
403      * Add a property onto the current BeanDefinition.
404      */
405     protected void addProperty(BeanDefinitionHolder definition, MappingMetaData metadata, Element element,
406             String localName, String value) {
407         String propertyName = metadata.getPropertyName(getLocalName(element), localName);
408         String propertyEditor = metadata.getPropertyEditor(getLocalName(element), propertyName);
409         if (propertyName != null) {
410             definition.getBeanDefinition().getPropertyValues().addPropertyValue(
411                             propertyName, getValue(value,propertyEditor));
412         }
413     }
414 
415     protected Object getValue(String value, String propertyEditor) {
416         if (value == null)  return null;
417 
418         //
419         // If value is #null then we are explicitly setting the value null instead of an empty string
420         //
421         if (NULL_REFERENCE.equals(value)) {
422             return null;
423         }
424 
425         //
426         // If value starts with # then we have a ref
427         //
428         if (value.startsWith(BEAN_REFERENCE_PREFIX)) {
429             // strip off the #
430             value = value.substring(BEAN_REFERENCE_PREFIX.length());
431 
432             // if the new value starts with a #, then we had an excaped value (e.g. ##value)
433             if (!value.startsWith(BEAN_REFERENCE_PREFIX)) {
434                 return new RuntimeBeanReference(value);
435             }
436         }
437 
438         if( propertyEditor!=null ) {
439         	PropertyEditor p = createPropertyEditor(propertyEditor);
440         	
441         	RootBeanDefinition def = new RootBeanDefinition();
442         	def.setBeanClass(PropertyEditorFactory.class);
443         	def.getPropertyValues().addPropertyValue("propertyEditor", p);
444         	def.getPropertyValues().addPropertyValue("value", value);
445         	
446         	return def;
447         }
448         
449         //
450         // Neither null nor a reference
451         //
452         return value;
453     }
454 
455     protected PropertyEditor createPropertyEditor(String propertyEditor) {    	
456     	ClassLoader cl = Thread.currentThread().getContextClassLoader();
457     	if( cl==null ) {
458     		cl = XBeanNamespaceHandler.class.getClassLoader();
459     	}
460     	
461     	try {
462     		return (PropertyEditor)cl.loadClass(propertyEditor).newInstance();
463     	} catch (Throwable e){
464     		throw (IllegalArgumentException)new IllegalArgumentException("Could not load property editor: "+propertyEditor).initCause(e);
465     	}
466 	}
467 
468     protected String getLocalName(Element element) {
469         String localName = element.getLocalName();
470         if (localName == null) {
471             localName = element.getNodeName();
472         }
473         return localName;
474     }
475 
476     /***
477      * Lets iterate through the children of this element and create any nested
478      * child properties
479      */
480     protected void addNestedPropertyElements(BeanDefinitionHolder definition, MappingMetaData metadata,
481             String className, Element element) {
482         NodeList nl = element.getChildNodes();
483 
484         for (int i = 0; i < nl.getLength(); i++) {
485             Node node = nl.item(i);
486             if (node instanceof Element) {
487                 Element childElement = (Element) node;
488                 String uri = childElement.getNamespaceURI();
489                 String localName = childElement.getLocalName();
490 
491                 if (!isDefaultNamespace(uri) || !reservedElementNames.contains(localName)) {
492                     // we could be one of the following
493                     // * the child element maps to a <property> tag with inner
494                     // tags being the bean
495                     // * the child element maps to a <property><list> tag with
496                     // inner tags being the contents of the list
497                     // * the child element maps to a <property> tag and is the
498                     // bean tag too
499                     // * the child element maps to a <property> tag and is a simple
500                     // type (String, Class, int, etc).
501                     Object value = null;
502                     String propertyName = metadata.getNestedListProperty(getLocalName(element), localName);
503                     if (propertyName != null) {
504                         value = parseListElement(childElement, propertyName);
505                     }
506                     else {
507                         propertyName = metadata.getFlatCollectionProperty(getLocalName(element), localName);
508                         if (propertyName != null) {
509                             Object def = parserContext.getDelegate().parseCustomElement(childElement);
510                             PropertyValue pv = definition.getBeanDefinition().getPropertyValues().getPropertyValue(propertyName);
511                             if (pv != null) {
512                                 Collection l = (Collection) pv.getValue();
513                                 l.add(def);
514                                 continue;
515                             } else {
516                                 ManagedList l = new ManagedList();
517                                 l.add(def);
518                                 value = l;
519                             }
520                         } else {
521                             propertyName = metadata.getNestedProperty(getLocalName(element), localName);
522                             if (propertyName != null) {
523                                 // lets find the first child bean that parses fine
524                                 value = parseChildExtensionBean(childElement);
525                             }
526                         }
527                     }
528 
529                     if (propertyName == null && metadata.isFlatProperty(getLocalName(element), localName)) {
530                        value = parseBeanFromExtensionElement(childElement, className, localName);
531                        propertyName = localName;
532                     }
533 
534                     if (propertyName == null) {
535                         value = tryParseNestedPropertyViaIntrospection(metadata, className, childElement);
536                         propertyName = localName;
537                     }
538 
539                     if (value != null) {
540                         definition.getBeanDefinition().getPropertyValues().addPropertyValue(propertyName, value);
541                     }
542                     else
543                     {
544                         /***
545                          * In this case there is no nested property, so just do a normal
546                          * addProperty like we do with attributes.
547                          */
548                         String text = getElementText(childElement);
549 
550                         if (text != null) {
551                             addProperty(definition, metadata, element, localName, text);
552                         }
553                     }
554                 }
555             }
556         }
557     }
558 
559     /***
560      * Attempts to use introspection to parse the nested property element.
561      */
562     protected Object tryParseNestedPropertyViaIntrospection(MappingMetaData metadata, String className, Element element) {
563         String localName = getLocalName(element);
564         PropertyDescriptor descriptor = getPropertyDescriptor(className, localName);
565         if (descriptor != null) {
566             return parseNestedPropertyViaIntrospection(metadata, element, descriptor.getName(), descriptor.getPropertyType());
567         } else {
568             return parseNestedPropertyViaIntrospection(metadata, element, localName, Object.class);
569         }
570     }
571 
572     /***
573      * Looks up the property decriptor for the given class and property name
574      */
575     protected PropertyDescriptor getPropertyDescriptor(String className, String localName) {
576         BeanInfo beanInfo = qnameHelper.getBeanInfo(className);
577         if (beanInfo != null) {
578             PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
579             for (int i = 0; i < descriptors.length; i++) {
580                 PropertyDescriptor descriptor = descriptors[i];
581                 String name = descriptor.getName();
582                 if (name.equals(localName)) {
583                     return descriptor;
584                 }
585             }
586         }
587         return null;
588     }
589 
590     /***
591      * Attempts to use introspection to parse the nested property element.
592      */
593     private Object parseNestedPropertyViaIntrospection(MappingMetaData metadata, Element element, String propertyName, Class propertyType) {
594         if (isMap(propertyType)) {
595             return parseCustomMapElement(metadata, element, propertyName);
596         } else if (isCollection(propertyType)) {
597             return parseListElement(element, propertyName);
598         } else {
599             return parseChildExtensionBean(element);
600         }
601     }
602 
603     protected Object parseListElement(Element element, String name) {
604         return parserContext.getDelegate().parseListElement(element, null);
605     }
606 
607     protected Object parseCustomMapElement(MappingMetaData metadata, Element element, String name) {
608         Map map = new ManagedMap();
609 
610         Element parent = (Element) element.getParentNode();
611         String entryName = metadata.getMapEntryName(getLocalName(parent), name);
612         String keyName = metadata.getMapKeyName(getLocalName(parent), name);
613         String dups = metadata.getMapDupsMode(getLocalName(parent), name);
614         boolean flat = metadata.isFlatMap(getLocalName(parent), name);
615         String defaultKey = metadata.getMapDefaultKey(getLocalName(parent), name);
616 
617         if (entryName == null) entryName = "property";
618         if (keyName == null) keyName = "key";
619         if (dups == null) dups = "replace";
620 
621         // TODO : support further customizations
622         //String valueName = "value";
623         //boolean keyIsAttr = true;
624         //boolean valueIsAttr = false;
625         NodeList nl = element.getChildNodes();
626         for (int i = 0; i < nl.getLength(); i++) {
627             Node node = nl.item(i);
628             if (node instanceof Element) {
629                 Element childElement = (Element) node;
630 
631                 String localName = childElement.getLocalName();
632                 String uri = childElement.getNamespaceURI();
633                 if (localName == null || localName.equals("xmlns") || localName.startsWith("xmlns:")) {
634                     continue;
635                 }
636 
637                 // we could use namespaced attributes to differentiate real spring
638                 // attributes from namespace-specific attributes
639                 if (!flat && !isEmpty(uri) && localName.equals(entryName)) {
640                     String key = childElement.getAttribute(keyName);
641                     if (key == null || key.length() == 0) {
642                         key = defaultKey;
643                     }
644                     if (key == null) {
645                         throw new RuntimeException("No key defined for map " + entryName);
646                     }
647 
648                     Object keyValue = getValue(key, null);
649 
650                     Element valueElement = getFirstChildElement(childElement);
651                     Object value;
652                     if (valueElement != null) {
653                         String valueElUri = valueElement.getNamespaceURI();
654                         String valueElLocalName = valueElement.getLocalName();
655                         if (valueElUri == null || 
656                             valueElUri.equals(SPRING_SCHEMA) || 
657                             valueElUri.equals(SPRING_SCHEMA_COMPAT) ||
658                             valueElUri.equals(BeanDefinitionParserDelegate.BEANS_NAMESPACE_URI)) {
659                             if (BeanDefinitionParserDelegate.BEAN_ELEMENT.equals(valueElLocalName)) {
660                                 value = parserContext.getDelegate().parseBeanDefinitionElement(valueElement, null);
661                             } else {
662                                 value = parserContext.getDelegate().parsePropertySubElement(valueElement, null);
663                             }
664                         } else {
665                             value = parserContext.getDelegate().parseCustomElement(valueElement);
666                         }
667                     } else {
668                         value = getElementText(childElement);
669                     }
670 
671                     addValueToMap(map, keyValue, value, dups);
672                 } else if (flat && !isEmpty(uri)) {
673                     String key = childElement.getAttribute(keyName);
674                     if (key == null || key.length() == 0) {
675                         key = defaultKey;
676                     }
677                     if (key == null) {
678                         throw new RuntimeException("No key defined for map entry " + entryName);
679                     }
680                     Object keyValue = getValue(key, null);
681                     childElement.removeAttribute(keyName);
682                     BeanDefinitionHolder bdh = parseBeanFromExtensionElement(childElement);
683                     addValueToMap(map, keyValue, bdh, dups);
684                 }
685             }
686         }
687         return map;
688     }
689     
690     protected void addValueToMap(Map map, Object keyValue, Object value, String dups) {
691         if (map.containsKey(keyValue)) {
692             if ("discard".equalsIgnoreCase(dups)) {
693                 // Do nothing
694             } else if ("replace".equalsIgnoreCase(dups)) {
695                 map.put(keyValue, value);
696             } else if ("allow".equalsIgnoreCase(dups)) {
697                 List l = new ManagedList();
698                 l.add(map.get(keyValue));
699                 l.add(value);
700                 map.put(keyValue, l);
701             } else if ("always".equalsIgnoreCase(dups)) {
702                 List l = (List) map.get(keyValue);
703                 l.add(value);
704             }
705         } else {
706             if ("always".equalsIgnoreCase(dups)) {
707                 List l = (List) map.get(keyValue);
708                 if (l == null) {
709                     l = new ManagedList();
710                     map.put(keyValue, l);
711                 }
712                 l.add(value);
713             } else {
714                 map.put(keyValue, value);
715             }
716         }
717     }
718 
719     protected Element getFirstChildElement(Element element) {
720         NodeList nl = element.getChildNodes();
721         for (int i = 0; i < nl.getLength(); i++) {
722             Node node = nl.item(i);
723             if (node instanceof Element) {
724                 return (Element) node;
725             }
726         }
727         return null;
728     }
729 
730     protected boolean isMap(Class type) {
731         return Map.class.isAssignableFrom(type);
732     }
733 
734     /***
735      * Returns true if the given type is a collection type or an array
736      */
737     protected boolean isCollection(Class type) {
738         return type.isArray() || Collection.class.isAssignableFrom(type);
739     }
740 
741     /***
742      * Iterates the children of this element to find the first nested bean
743      */
744     protected Object parseChildExtensionBean(Element element) {
745         NodeList nl = element.getChildNodes();
746         for (int i = 0; i < nl.getLength(); i++) {
747             Node node = nl.item(i);
748             if (node instanceof Element) {
749                 Element childElement = (Element) node;
750                 String uri = childElement.getNamespaceURI();
751                 String localName = childElement.getLocalName();
752 
753                 if (uri == null || 
754                     uri.equals(SPRING_SCHEMA) || 
755                     uri.equals(SPRING_SCHEMA_COMPAT) ||
756                     uri.equals(BeanDefinitionParserDelegate.BEANS_NAMESPACE_URI)) {
757                     if (BeanDefinitionParserDelegate.BEAN_ELEMENT.equals(localName)) {
758                         return parserContext.getDelegate().parseBeanDefinitionElement(childElement, null);
759                     } else {
760                         return parserContext.getDelegate().parsePropertySubElement(childElement, null);
761                     }
762                 } else {
763                     Object value = parserContext.getDelegate().parseCustomElement(childElement);
764                     if (value != null) {
765                         return value;
766                     }
767                 }
768             }
769         }
770         return null;
771     }
772 
773     /***
774      * Uses META-INF/services discovery to find a Properties file with the XML
775      * marshaling configuration
776      *
777      * @param namespaceURI
778      *            the namespace URI of the element
779      * @param localName
780      *            the local name of the element
781      * @return the properties configuration of the namespace or null if none
782      *         could be found
783      */
784     protected MappingMetaData findNamespaceProperties(String namespaceURI, String localName) {
785         // lets look for the magic prefix
786         if (namespaceURI != null && namespaceURI.startsWith(JAVA_PACKAGE_PREFIX)) {
787             String packageName = namespaceURI.substring(JAVA_PACKAGE_PREFIX.length());
788             return new MappingMetaData(packageName);
789         }
790 
791         String uri = NamespaceHelper.createDiscoveryPathName(namespaceURI, localName);
792         InputStream in = loadResource(uri);
793         if (in == null) {
794             if (namespaceURI != null && namespaceURI.length() > 0) {
795                 uri = NamespaceHelper.createDiscoveryPathName(namespaceURI);
796                 in = loadResource(uri);
797                 if (in == null) {
798                     uri = NamespaceHelper.createDiscoveryOldPathName(namespaceURI);
799                     in = loadResource(uri);
800                 }
801             }
802         }
803 
804         if (in != null) {
805             try {
806                 Properties properties = new Properties();
807                 properties.load(in);
808                 return new MappingMetaData(properties);
809             }
810             catch (IOException e) {
811                 log.warn("Failed to load resource from uri: " + uri, e);
812             }
813         }
814         return null;
815     }
816 
817     /***
818      * Loads the resource from the given URI
819      */
820     protected InputStream loadResource(String uri) {
821         if (System.getProperty("xbean.dir") != null) {
822             File f = new File(System.getProperty("xbean.dir") + uri);
823             try {
824                 return new FileInputStream(f);
825             } catch (FileNotFoundException e) {
826                 // Ignore
827             }
828         }
829         // lets try the thread context class loader first
830         InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(uri);
831         if (in == null) {
832             ClassLoader cl = parserContext.getReaderContext().getReader().getBeanClassLoader();
833             if (cl != null) {
834                 in = cl.getResourceAsStream(uri);
835             }
836             if (in == null) {
837                 in = getClass().getClassLoader().getResourceAsStream(uri);
838                 if (in == null) {
839                     log.debug("Could not find resource: " + uri);
840                 }
841             }
842         }
843         return in;
844     }
845 
846     protected boolean isEmpty(String uri) {
847         return uri == null || uri.length() == 0;
848     }
849 
850     protected boolean isDefaultNamespace(String namespaceUri) {
851         return (!StringUtils.hasLength(namespaceUri) ||
852                BeanDefinitionParserDelegate.BEANS_NAMESPACE_URI.equals(namespaceUri)) ||
853                SPRING_SCHEMA.equals(namespaceUri) ||
854                SPRING_SCHEMA_COMPAT.equals(namespaceUri);
855     }
856 
857     protected void declareLifecycleMethods(BeanDefinitionHolder definitionHolder, MappingMetaData metaData,
858             Element element) {
859         BeanDefinition definition = definitionHolder.getBeanDefinition();
860         if (definition instanceof AbstractBeanDefinition) {
861             AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) definition;
862             if (beanDefinition.getInitMethodName() == null) {
863                 beanDefinition.setInitMethodName(metaData.getInitMethodName(getLocalName(element)));
864             }
865             if (beanDefinition.getDestroyMethodName() == null) {
866                 beanDefinition.setDestroyMethodName(metaData.getDestroyMethodName(getLocalName(element)));
867             }
868             if (beanDefinition.getFactoryMethodName() == null) {
869                 beanDefinition.setFactoryMethodName(metaData.getFactoryMethodName(getLocalName(element)));
870             }
871         }
872     }
873 
874     // -------------------------------------------------------------------------
875     //
876     // TODO we could apply the following patches into the Spring code -
877     // though who knows if it'll ever make it into a release! :)
878     //
879     // -------------------------------------------------------------------------
880     /*
881     protected int parseBeanDefinitions(Element root) throws BeanDefinitionStoreException {
882         int beanDefinitionCount = 0;
883         if (isEmpty(root.getNamespaceURI()) || root.getLocalName().equals("beans")) {
884             NodeList nl = root.getChildNodes();
885             for (int i = 0; i < nl.getLength(); i++) {
886                 Node node = nl.item(i);
887                 if (node instanceof Element) {
888                     Element ele = (Element) node;
889                     if (IMPORT_ELEMENT.equals(node.getNodeName())) {
890                         importBeanDefinitionResource(ele);
891                     }
892                     else if (ALIAS_ELEMENT.equals(node.getNodeName())) {
893                         String name = ele.getAttribute(NAME_ATTRIBUTE);
894                         String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
895                         getBeanDefinitionReader().getBeanFactory().registerAlias(name, alias);
896                     }
897                     else if (BEAN_ELEMENT.equals(node.getNodeName())) {
898                         beanDefinitionCount++;
899                         BeanDefinitionHolder bdHolder = parseBeanDefinitionElement(ele, false);
900                         BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getBeanDefinitionReader()
901                                 .getBeanFactory());
902                     }
903                     else {
904                         BeanDefinitionHolder bdHolder = parseBeanFromExtensionElement(ele);
905                         if (bdHolder != null) {
906                             beanDefinitionCount++;
907                             BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getBeanDefinitionReader()
908                                     .getBeanFactory());
909                         }
910                         else {
911                             log.debug("Ignoring unknown element namespace: " + ele.getNamespaceURI() + " localName: "
912                                     + ele.getLocalName());
913                         }
914                     }
915                 }
916             }
917         } else {
918             BeanDefinitionHolder bdHolder = parseBeanFromExtensionElement(root);
919             if (bdHolder != null) {
920                 beanDefinitionCount++;
921                 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getBeanDefinitionReader()
922                         .getBeanFactory());
923             }
924             else {
925                 log.debug("Ignoring unknown element namespace: " + root.getNamespaceURI() + " localName: " + root.getLocalName());
926             }
927         }
928         return beanDefinitionCount;
929     }
930 
931     protected BeanDefinitionHolder parseBeanDefinitionElement(Element ele, boolean isInnerBean) throws BeanDefinitionStoreException {
932         
933         BeanDefinitionHolder bdh = super.parseBeanDefinitionElement(ele, isInnerBean);
934         coerceNamespaceAwarePropertyValues(bdh, ele);
935         return bdh;
936     }
937 
938     protected Object parsePropertySubElement(Element element, String beanName) throws BeanDefinitionStoreException {
939         String uri = element.getNamespaceURI();
940         String localName = getLocalName(element);
941 
942         if ((!isEmpty(uri) && !(uri.equals(SPRING_SCHEMA) || uri.equals(SPRING_SCHEMA_COMPAT)))
943                 || !reservedElementNames.contains(localName)) {
944             Object answer = parseBeanFromExtensionElement(element);
945             if (answer != null) {
946                 return answer;
947             }
948         }
949         if (QNAME_ELEMENT.equals(localName) && isQnameIsOnClassPath()) {
950             Object answer = parseQNameElement(element);
951             if (answer != null) {
952                 return answer;
953             }
954         }
955         return super.parsePropertySubElement(element, beanName);
956     }
957 
958     protected Object parseQNameElement(Element element) {
959         return QNameReflectionHelper.createQName(element, getElementText(element));
960     }
961     */
962 
963     /***
964      * Returns the text of the element
965      */
966     protected String getElementText(Element element) {
967         StringBuffer buffer = new StringBuffer();
968         NodeList nodeList = element.getChildNodes();
969         for (int i = 0, size = nodeList.getLength(); i < size; i++) {
970             Node node = nodeList.item(i);
971             if (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE) {
972                 buffer.append(node.getNodeValue());
973             }
974         }
975         return buffer.toString();
976     }
977 }