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