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