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