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