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 }