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