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