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