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