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