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