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