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