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