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