/****************************************************************************** | |
* Copyright (c) 2006, 2010 VMware Inc. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v1.0 | |
* and Apache License v2.0 which accompanies this distribution. | |
* The Eclipse Public License is available at | |
* http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0 | |
* is available at http://www.opensource.org/licenses/apache2.0.php. | |
* You may elect to redistribute this code under either of these licenses. | |
* | |
* Contributors: | |
* VMware Inc. | |
*****************************************************************************/ | |
package org.eclipse.gemini.blueprint.blueprint.config.internal; | |
import org.apache.commons.logging.Log; | |
import org.apache.commons.logging.LogFactory; | |
import org.eclipse.gemini.blueprint.blueprint.config.internal.support.InstanceEqualityRuntimeBeanReference; | |
import org.springframework.beans.PropertyValue; | |
import org.springframework.beans.factory.config.BeanDefinition; | |
import org.springframework.beans.factory.config.BeanDefinitionHolder; | |
import org.springframework.beans.factory.config.ConstructorArgumentValues; | |
import org.springframework.beans.factory.config.RuntimeBeanNameReference; | |
import org.springframework.beans.factory.config.RuntimeBeanReference; | |
import org.springframework.beans.factory.config.TypedStringValue; | |
import org.springframework.beans.factory.parsing.BeanEntry; | |
import org.springframework.beans.factory.parsing.ConstructorArgumentEntry; | |
import org.springframework.beans.factory.parsing.ParseState; | |
import org.springframework.beans.factory.parsing.PropertyEntry; | |
import org.springframework.beans.factory.support.AbstractBeanDefinition; | |
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; | |
import org.springframework.beans.factory.support.ManagedArray; | |
import org.springframework.beans.factory.support.ManagedList; | |
import org.springframework.beans.factory.support.ManagedMap; | |
import org.springframework.beans.factory.support.ManagedProperties; | |
import org.springframework.beans.factory.support.ManagedSet; | |
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; | |
import org.springframework.beans.factory.xml.ParserContext; | |
import org.springframework.util.ClassUtils; | |
import org.springframework.util.CollectionUtils; | |
import org.springframework.util.StringUtils; | |
import org.springframework.util.xml.DomUtils; | |
import org.w3c.dom.Element; | |
import org.w3c.dom.Node; | |
import org.w3c.dom.NodeList; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.Iterator; | |
import java.util.LinkedHashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Properties; | |
import java.util.Set; | |
/** | |
* Stateful class that handles the parsing details of a <component> elements. Borrows heavily from | |
* {@link BeanDefinitionParserDelegate}. | |
* | |
* <b>Note</b>: Due to its stateful nature, this class is not thread safe. | |
* | |
* <b>Note</b>: Since the namespace is important when parsing elements and since mixed elements, from both rfc124 and | |
* Spring can coexist in the same file, reusing the {@link BeanDefinitionParserDelegate delegate} isn't entirely | |
* possible since the two state needs to be kept in synch. | |
* | |
* @author Costin Leau | |
*/ | |
public class BlueprintParser { | |
/** logger */ | |
private static final Log log = LogFactory.getLog(BlueprintParser.class); | |
public static final String BEAN = "bean"; | |
public static final String COMPONENT_ID_ATTR = "component-id"; | |
public static final String CONSTRUCTOR_ARG = "argument"; | |
private static final String FACTORY_REF_ATTR = "factory-ref"; | |
private static final String LAZY_INIT_ATTR = "activation"; | |
private static final String LAZY_INIT_VALUE = "lazy"; | |
private static final String EAGER_INIT_VALUE = "eager"; | |
public static final String NAMESPACE_URI = "http://www.osgi.org/xmlns/blueprint/v1.0.0"; | |
public static final String DECLARED_SCOPE = "org.eclipse.gemini.blueprint.blueprint.xml.bean.declared.scope"; | |
private final ParseState parseState; | |
private final Collection<String> usedNames; | |
private ParserContext parserContext; | |
private BlueprintDefaultsDefinition defaults; | |
public BlueprintParser() { | |
this(null, null); | |
} | |
/** | |
* Constructs a new <code>ComponentParser</code> instance. Used by certain reusable static methods. | |
* | |
* @param parserContext | |
*/ | |
private BlueprintParser(ParserContext parserContext) { | |
this(null, null); | |
this.parserContext = parserContext; | |
} | |
public BlueprintParser(ParseState parseState, Collection<String> usedNames) { | |
this.parseState = (parseState != null ? parseState : new ParseState()); | |
this.usedNames = (usedNames != null ? usedNames : new LinkedHashSet<String>()); | |
} | |
public BeanDefinitionHolder parseAsHolder(Element componentElement, ParserContext parserContext) { | |
// save parser context | |
this.parserContext = parserContext; | |
this.defaults = new BlueprintDefaultsDefinition(componentElement.getOwnerDocument(), parserContext); | |
// let Spring do its standard parsing | |
BeanDefinitionHolder bdHolder = parseComponentDefinitionElement(componentElement, null); | |
BeanDefinition bd = bdHolder.getBeanDefinition(); | |
if (bd != null) { | |
bd.setAttribute(ParsingUtils.BLUEPRINT_MARKER_NAME, Boolean.TRUE); | |
} | |
return bdHolder; | |
} | |
public BeanDefinition parse(Element componentElement, ParserContext parserContext) { | |
return parseAsHolder(componentElement, parserContext).getBeanDefinition(); | |
} | |
/** | |
* Parses the supplied <code><bean></code> element. May return <code>null</code> if there were errors during | |
* parse. Errors are reported to the {@link org.springframework.beans.factory.parsing.ProblemReporter}. | |
*/ | |
private BeanDefinitionHolder parseComponentDefinitionElement(Element ele, BeanDefinition containingBean) { | |
// extract bean name | |
String id = ele.getAttribute(BeanDefinitionParserDelegate.ID_ATTRIBUTE); | |
String nameAttr = ele.getAttribute(BeanDefinitionParserDelegate.NAME_ATTRIBUTE); | |
List<String> aliases = new ArrayList<String>(4); | |
if (StringUtils.hasLength(nameAttr)) { | |
String[] nameArr = | |
StringUtils.tokenizeToStringArray(nameAttr, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); | |
aliases.addAll(Arrays.asList(nameArr)); | |
} | |
String beanName = id; | |
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { | |
beanName = (String) aliases.remove(0); | |
if (log.isDebugEnabled()) { | |
log.debug("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases | |
+ " as aliases"); | |
} | |
} | |
if (containingBean == null) { | |
if (checkNameUniqueness(beanName, aliases, usedNames)) { | |
error("Bean name '" + beanName + "' is already used in this file", ele); | |
} | |
if (ParsingUtils.isReservedName(beanName, ele, parserContext)) { | |
error("Blueprint reserved name '" + beanName + "' cannot be used", ele); | |
} | |
} | |
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); | |
if (beanDefinition != null) { | |
if (!StringUtils.hasText(beanName)) { | |
try { | |
if (containingBean != null) { | |
beanName = | |
ParsingUtils.generateBlueprintBeanName(beanDefinition, parserContext.getRegistry(), | |
true); | |
} else { | |
beanName = | |
ParsingUtils.generateBlueprintBeanName(beanDefinition, parserContext.getRegistry(), | |
false); | |
// TODO: should we support 2.0 behaviour (see below): | |
// | |
// Register an alias for the plain bean class name, if still possible, | |
// if the generator returned the class name plus a suffix. | |
// This is expected for Spring 1.2/2.0 backwards compatibility. | |
} | |
if (log.isDebugEnabled()) { | |
log.debug("Neither XML 'id' nor 'name' specified - " + "using generated bean name [" + beanName | |
+ "]"); | |
} | |
} catch (Exception ex) { | |
error(ex.getMessage(), ele, ex); | |
return null; | |
} | |
} | |
return new BeanDefinitionHolder(beanDefinition, beanName); | |
} | |
return null; | |
} | |
/** | |
* Parse the bean definition itself, without regard to name or aliases. May return <code>null</code> if problems | |
* occurred during the parse of the bean definition. | |
*/ | |
private AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, | |
BeanDefinition containingBean) { | |
this.parseState.push(new BeanEntry(beanName)); | |
String className = null; | |
if (ele.hasAttribute(BeanDefinitionParserDelegate.CLASS_ATTRIBUTE)) { | |
className = ele.getAttribute(BeanDefinitionParserDelegate.CLASS_ATTRIBUTE).trim(); | |
} | |
try { | |
AbstractBeanDefinition beanDefinition = | |
BeanDefinitionReaderUtils.createBeanDefinition(null, className, parserContext.getReaderContext() | |
.getBeanClassLoader()); | |
// some early validation | |
String activation = ele.getAttribute(LAZY_INIT_ATTR); | |
String scope = ele.getAttribute(BeanDefinitionParserDelegate.SCOPE_ATTRIBUTE); | |
if (EAGER_INIT_VALUE.equals(activation) && BeanDefinition.SCOPE_PROTOTYPE.equals(scope)) { | |
error("Prototype beans cannot be eagerly activated", ele); | |
} | |
// add marker to indicate that the scope was present | |
if (StringUtils.hasText(scope)) { | |
beanDefinition.setAttribute(DECLARED_SCOPE, Boolean.TRUE); | |
} | |
// parse attributes | |
parseAttributes(ele, beanName, beanDefinition); | |
// inner beans get a predefined scope in RFC 124 | |
if (containingBean != null) { | |
beanDefinition.setLazyInit(true); | |
beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE); | |
} | |
// parse description | |
beanDefinition.setDescription(DomUtils.getChildElementValueByTagName(ele, | |
BeanDefinitionParserDelegate.DESCRIPTION_ELEMENT)); | |
parseConstructorArgElements(ele, beanDefinition); | |
parsePropertyElements(ele, beanDefinition); | |
beanDefinition.setResource(parserContext.getReaderContext().getResource()); | |
beanDefinition.setSource(extractSource(ele)); | |
return beanDefinition; | |
} catch (ClassNotFoundException ex) { | |
error("Bean class [" + className + "] not found", ele, ex); | |
} catch (NoClassDefFoundError err) { | |
error("Class that bean class [" + className + "] depends on not found", ele, err); | |
} catch (Throwable ex) { | |
error("Unexpected failure during bean definition parsing", ele, ex); | |
} finally { | |
this.parseState.pop(); | |
} | |
return null; | |
} | |
private AbstractBeanDefinition parseAttributes(Element ele, String beanName, AbstractBeanDefinition beanDefinition) { | |
AbstractBeanDefinition bd = | |
parserContext.getDelegate().parseBeanDefinitionAttributes(ele, beanName, null, beanDefinition); | |
// handle lazy flag (initialize) | |
String lazyInit = ele.getAttribute(LAZY_INIT_ATTR); | |
// check whether the value is "lazy" | |
if (StringUtils.hasText(lazyInit)) { | |
if (lazyInit.equalsIgnoreCase(LAZY_INIT_VALUE)) { | |
bd.setLazyInit(true); | |
} else { | |
bd.setLazyInit(false); | |
} | |
} else { | |
bd.setLazyInit(getDefaults(ele).getDefaultInitialization()); | |
} | |
// handle factory component | |
String componentFactory = ele.getAttribute(FACTORY_REF_ATTR); | |
if (StringUtils.hasText(componentFactory)) { | |
bd.setFactoryBeanName(componentFactory); | |
} | |
// check whether the bean is a prototype with destroy method | |
if (StringUtils.hasText(bd.getDestroyMethodName()) | |
&& BeanDefinition.SCOPE_PROTOTYPE.equalsIgnoreCase(bd.getScope())) { | |
error("Blueprint prototype beans cannot define destroy methods", ele); | |
} | |
return bd; | |
} | |
/** | |
* Validate that the specified bean name and aliases have not been used already. | |
*/ | |
private boolean checkNameUniqueness(String beanName, Collection<String> aliases, Collection<String> usedNames) { | |
String foundName = null; | |
if (StringUtils.hasText(beanName) && usedNames.contains(beanName)) { | |
foundName = beanName; | |
} | |
if (foundName == null) { | |
foundName = (String) CollectionUtils.findFirstMatch(usedNames, aliases); | |
} | |
usedNames.add(beanName); | |
usedNames.addAll(aliases); | |
return (foundName != null); | |
} | |
/** | |
* Parsers contructor arguments. | |
* | |
* @param ele | |
* @param beanDefinition | |
* @param parserContext | |
*/ | |
private void parseConstructorArgElements(Element ele, AbstractBeanDefinition beanDefinition) { | |
NodeList nl = ele.getChildNodes(); | |
for (int i = 0; i < nl.getLength(); i++) { | |
Node node = nl.item(i); | |
if (node instanceof Element && DomUtils.nodeNameEquals(node, CONSTRUCTOR_ARG)) { | |
parseConstructorArgElement((Element) node, beanDefinition); | |
} | |
} | |
} | |
private void parseConstructorArgElement(Element ele, AbstractBeanDefinition beanDefinition) { | |
String indexAttr = ele.getAttribute(BeanDefinitionParserDelegate.INDEX_ATTRIBUTE); | |
String typeAttr = ele.getAttribute(BeanDefinitionParserDelegate.TYPE_ATTRIBUTE); | |
boolean hasIndex = false; | |
int index = -1; | |
if (StringUtils.hasLength(indexAttr)) { | |
hasIndex = true; | |
try { | |
index = Integer.parseInt(indexAttr); | |
} catch (NumberFormatException ex) { | |
error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele); | |
} | |
if (index < 0) { | |
error("'index' cannot be lower than 0", ele); | |
} | |
} | |
try { | |
this.parseState.push(hasIndex ? new ConstructorArgumentEntry(index) : new ConstructorArgumentEntry()); | |
ConstructorArgumentValues values = beanDefinition.getConstructorArgumentValues(); | |
// Blueprint failure (index duplication) | |
Integer indexInt = Integer.valueOf(index); | |
if (values.getIndexedArgumentValues().containsKey(indexInt)) { | |
error("duplicate 'index' with value=[" + index + "] specified", ele); | |
} | |
Object value = parsePropertyValue(ele, beanDefinition, null); | |
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); | |
if (StringUtils.hasLength(typeAttr)) { | |
valueHolder.setType(typeAttr); | |
} | |
valueHolder.setSource(extractSource(ele)); | |
if (hasIndex) { | |
values.addIndexedArgumentValue(index, valueHolder); | |
} else { | |
values.addGenericArgumentValue(valueHolder); | |
} | |
// Blueprint failure (mixed index/non-indexed arguments) | |
if (!values.getGenericArgumentValues().isEmpty() && !values.getIndexedArgumentValues().isEmpty()) { | |
error("indexed and non-indexed constructor arguments are not supported by Blueprint; " | |
+ "consider using the Spring namespace instead", ele); | |
} | |
} finally { | |
this.parseState.pop(); | |
} | |
} | |
/** | |
* Parses property elements. | |
* | |
* @param ele | |
* @param beanDefinition | |
* @param parserContext | |
*/ | |
private void parsePropertyElements(Element ele, AbstractBeanDefinition beanDefinition) { | |
NodeList nl = ele.getChildNodes(); | |
for (int i = 0; i < nl.getLength(); i++) { | |
Node node = nl.item(i); | |
if (node instanceof Element && DomUtils.nodeNameEquals(node, BeanDefinitionParserDelegate.PROPERTY_ELEMENT)) { | |
parsePropertyElement((Element) node, beanDefinition); | |
} | |
} | |
} | |
private void parsePropertyElement(Element ele, BeanDefinition bd) { | |
String propertyName = ele.getAttribute(BeanDefinitionParserDelegate.NAME_ATTRIBUTE); | |
if (!StringUtils.hasLength(propertyName)) { | |
error("Tag 'property' must have a 'name' attribute", ele); | |
return; | |
} | |
this.parseState.push(new PropertyEntry(propertyName)); | |
try { | |
if (bd.getPropertyValues().contains(propertyName)) { | |
error("Multiple 'property' definitions for property '" + propertyName + "'", ele); | |
return; | |
} | |
Object val = parsePropertyValue(ele, bd, propertyName); | |
PropertyValue pv = new PropertyValue(propertyName, val); | |
pv.setSource(parserContext.extractSource(ele)); | |
bd.getPropertyValues().addPropertyValue(pv); | |
} finally { | |
this.parseState.pop(); | |
} | |
} | |
private Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) { | |
String elementName = | |
(propertyName != null) ? "<property> element for property '" + propertyName + "'" | |
: "<constructor-arg> element"; | |
// Should only have one child element: ref, value, list, etc. | |
NodeList nl = ele.getChildNodes(); | |
Element subElement = null; | |
for (int i = 0; i < nl.getLength(); i++) { | |
Node node = nl.item(i); | |
if (node instanceof Element | |
&& !DomUtils.nodeNameEquals(node, BeanDefinitionParserDelegate.DESCRIPTION_ELEMENT)) { | |
// Child element is what we're looking for. | |
if (subElement != null) { | |
error(elementName + " must not contain more than one sub-element", ele); | |
} else { | |
subElement = (Element) node; | |
} | |
} | |
} | |
boolean hasRefAttribute = ele.hasAttribute(BeanDefinitionParserDelegate.REF_ATTRIBUTE); | |
boolean hasValueAttribute = ele.hasAttribute(BeanDefinitionParserDelegate.VALUE_ATTRIBUTE); | |
if ((hasRefAttribute && hasValueAttribute) || ((hasRefAttribute || hasValueAttribute) && subElement != null)) { | |
error(elementName | |
+ " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); | |
} | |
if (hasRefAttribute) { | |
String refName = ele.getAttribute(BeanDefinitionParserDelegate.REF_ATTRIBUTE); | |
if (!StringUtils.hasText(refName)) { | |
error(elementName + " contains empty 'ref' attribute", ele); | |
} | |
RuntimeBeanReference ref = new RuntimeBeanReference(refName); | |
ref.setSource(parserContext.extractSource(ele)); | |
return ref; | |
} else if (hasValueAttribute) { | |
TypedStringValue valueHolder = | |
new TypedStringValue(ele.getAttribute(BeanDefinitionParserDelegate.VALUE_ATTRIBUTE)); | |
valueHolder.setSource(parserContext.extractSource(ele)); | |
return valueHolder; | |
} else if (subElement != null) { | |
return parsePropertySubElement(subElement, bd, null); | |
} else { | |
// Neither child element nor "ref" or "value" attribute found. | |
error(elementName + " must specify a ref or value", ele); | |
return null; | |
} | |
} | |
public static Object parsePropertySubElement(ParserContext parserContext, Element ele, BeanDefinition bd) { | |
return new BlueprintParser(parserContext).parsePropertySubElement(ele, bd, null); | |
} | |
public static Map<?, ?> parsePropertyMapElement(ParserContext parserContext, Element ele, BeanDefinition bd) { | |
return new BlueprintParser(parserContext).parseMapElement(ele, bd); | |
} | |
public static Set<?> parsePropertySetElement(ParserContext parserContext, Element ele, BeanDefinition bd) { | |
return new BlueprintParser(parserContext).parseSetElement(ele, bd); | |
} | |
/** | |
* Parse a value, ref or collection sub-element of a property or constructor-arg element. This method is called from | |
* several places to handle reusable elements such as idref, ref, null, value and so on. | |
* | |
* In fact, this method is the main reason why the BeanDefinitionParserDelegate is not used in full since the | |
* element namespace becomes important as mixed rfc124/bean content can coexist. | |
* | |
* @param ele subelement of property element; we don't know which yet | |
* @param defaultValueType the default type (class name) for any <code><value></code> tag that might be | |
* created | |
*/ | |
private Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) { | |
// skip other namespace | |
String namespaceUri = ele.getNamespaceURI(); | |
// check Spring own namespace | |
if (parserContext.getDelegate().isDefaultNamespace(namespaceUri)) { | |
return parserContext.getDelegate().parsePropertySubElement(ele, bd); | |
} | |
// let the delegate handle other ns | |
else if (!NAMESPACE_URI.equals(namespaceUri)) { | |
return parserContext.getDelegate().parseCustomElement(ele); | |
} | |
// | |
else { | |
if (DomUtils.nodeNameEquals(ele, BEAN)) { | |
BeanDefinitionHolder bdHolder = parseComponentDefinitionElement(ele, bd); | |
if (bdHolder != null) { | |
bdHolder = ParsingUtils.decorateBeanDefinitionIfRequired(ele, bdHolder, parserContext); | |
} | |
return bdHolder; | |
} | |
if (DomUtils.nodeNameEquals(ele, BeanDefinitionParserDelegate.REF_ELEMENT)) { | |
return parseRefElement(ele); | |
} else if (DomUtils.nodeNameEquals(ele, BeanDefinitionParserDelegate.IDREF_ELEMENT)) { | |
return parseIdRefElement(ele); | |
} else if (DomUtils.nodeNameEquals(ele, BeanDefinitionParserDelegate.VALUE_ELEMENT)) { | |
return parseValueElement(ele, defaultValueType); | |
} else if (DomUtils.nodeNameEquals(ele, BeanDefinitionParserDelegate.NULL_ELEMENT)) { | |
// It's a distinguished null value. Let's wrap it in a TypedStringValue | |
// object in order to preserve the source location. | |
TypedStringValue nullHolder = new TypedStringValue(null); | |
nullHolder.setSource(parserContext.extractSource(ele)); | |
return nullHolder; | |
} else if (DomUtils.nodeNameEquals(ele, BeanDefinitionParserDelegate.ARRAY_ELEMENT)) { | |
return parseArrayElement(ele, bd); | |
} else if (DomUtils.nodeNameEquals(ele, BeanDefinitionParserDelegate.LIST_ELEMENT)) { | |
return parseListElement(ele, bd); | |
} else if (DomUtils.nodeNameEquals(ele, BeanDefinitionParserDelegate.SET_ELEMENT)) { | |
return parseSetElement(ele, bd); | |
} else if (DomUtils.nodeNameEquals(ele, BeanDefinitionParserDelegate.MAP_ELEMENT)) { | |
return parseMapElement(ele, bd); | |
} else if (DomUtils.nodeNameEquals(ele, BeanDefinitionParserDelegate.PROPS_ELEMENT)) { | |
return parsePropsElement(ele); | |
} | |
// maybe it's a nested service/reference/ref-list/ref-set | |
return parserContext.getDelegate().parseCustomElement(ele, bd); | |
} | |
} | |
private Object parseRefElement(Element ele) { | |
// A generic reference to any name of any component. | |
String refName = ele.getAttribute(COMPONENT_ID_ATTR); | |
if (!StringUtils.hasLength(refName)) { | |
error("'" + COMPONENT_ID_ATTR + "' is required for <ref> element", ele); | |
return null; | |
} | |
if (!StringUtils.hasText(refName)) { | |
error("<ref> element contains empty target attribute", ele); | |
return null; | |
} | |
RuntimeBeanReference ref = new InstanceEqualityRuntimeBeanReference(refName); | |
ref.setSource(parserContext.extractSource(ele)); | |
return ref; | |
} | |
private Object parseIdRefElement(Element ele) { | |
// A generic reference to any name of any bean/component. | |
String refName = ele.getAttribute(COMPONENT_ID_ATTR); | |
if (!StringUtils.hasLength(refName)) { | |
error("'" + COMPONENT_ID_ATTR + "' is required for <idref> element", ele); | |
return null; | |
} | |
if (!StringUtils.hasText(refName)) { | |
error("<idref> element contains empty target attribute", ele); | |
return null; | |
} | |
RuntimeBeanNameReference ref = new RuntimeBeanNameReference(refName); | |
ref.setSource(parserContext.extractSource(ele)); | |
return ref; | |
} | |
/** | |
* Return a typed String value Object for the given value element. | |
* | |
* @param ele element | |
* @param defaultTypeName type class name | |
* @return typed String value Object | |
*/ | |
private Object parseValueElement(Element ele, String defaultTypeName) { | |
// It's a literal value. | |
String value = DomUtils.getTextValue(ele); | |
String specifiedTypeName = ele.getAttribute(BeanDefinitionParserDelegate.TYPE_ATTRIBUTE); | |
String typeName = specifiedTypeName; | |
if (!StringUtils.hasText(typeName)) { | |
typeName = defaultTypeName; | |
} | |
try { | |
TypedStringValue typedValue = buildTypedStringValue(value, typeName); | |
typedValue.setSource(extractSource(ele)); | |
typedValue.setSpecifiedTypeName(specifiedTypeName); | |
return typedValue; | |
} catch (ClassNotFoundException ex) { | |
error("Type class [" + typeName + "] not found for <value> element", ele, ex); | |
return value; | |
} | |
} | |
/** | |
* Build a typed String value Object for the given raw value. | |
* | |
* @see org.springframework.beans.factory.config.TypedStringValue | |
*/ | |
private TypedStringValue buildTypedStringValue(String value, String targetTypeName) throws ClassNotFoundException { | |
ClassLoader classLoader = parserContext.getReaderContext().getBeanClassLoader(); | |
TypedStringValue typedValue; | |
if (!StringUtils.hasText(targetTypeName)) { | |
typedValue = new TypedStringValue(value); | |
} else if (classLoader != null) { | |
Class<?> targetType = ClassUtils.forName(targetTypeName, classLoader); | |
typedValue = new TypedStringValue(value, targetType); | |
} else { | |
typedValue = new TypedStringValue(value, targetTypeName); | |
} | |
return typedValue; | |
} | |
/** | |
* Parse an array element. | |
*/ | |
public Object parseArrayElement(Element arrayEle, BeanDefinition bd) { | |
String elementType = arrayEle.getAttribute(BeanDefinitionParserDelegate.VALUE_TYPE_ATTRIBUTE); | |
NodeList nl = arrayEle.getChildNodes(); | |
ManagedArray target = new ManagedArray(elementType, nl.getLength()); | |
target.setSource(extractSource(arrayEle)); | |
target.setElementTypeName(elementType); | |
target.setMergeEnabled(parseMergeAttribute(arrayEle)); | |
parseCollectionElements(nl, target, bd, elementType); | |
return target; | |
} | |
/** | |
* Parse a list element. | |
*/ | |
public List<?> parseListElement(Element collectionEle, BeanDefinition bd) { | |
String defaultElementType = collectionEle.getAttribute(BeanDefinitionParserDelegate.VALUE_TYPE_ATTRIBUTE); | |
NodeList nl = collectionEle.getChildNodes(); | |
ManagedList<Object> target = new ManagedList<Object>(nl.getLength()); | |
target.setSource(extractSource(collectionEle)); | |
target.setElementTypeName(defaultElementType); | |
target.setMergeEnabled(parseMergeAttribute(collectionEle)); | |
parseCollectionElements(nl, target, bd, defaultElementType); | |
return target; | |
} | |
/** | |
* Parse a set element. | |
*/ | |
public Set<?> parseSetElement(Element collectionEle, BeanDefinition bd) { | |
String defaultElementType = collectionEle.getAttribute(BeanDefinitionParserDelegate.VALUE_TYPE_ATTRIBUTE); | |
NodeList nl = collectionEle.getChildNodes(); | |
ManagedSet<Object> target = new ManagedSet<Object>(nl.getLength()); | |
target.setSource(extractSource(collectionEle)); | |
target.setElementTypeName(defaultElementType); | |
target.setMergeEnabled(parseMergeAttribute(collectionEle)); | |
parseCollectionElements(nl, target, bd, defaultElementType); | |
return target; | |
} | |
protected void parseCollectionElements(NodeList elementNodes, Collection<Object> target, BeanDefinition bd, | |
String defaultElementType) { | |
for (int i = 0; i < elementNodes.getLength(); i++) { | |
Node node = elementNodes.item(i); | |
if (node instanceof Element | |
&& !DomUtils.nodeNameEquals(node, BeanDefinitionParserDelegate.DESCRIPTION_ELEMENT)) { | |
target.add(parsePropertySubElement((Element) node, bd, defaultElementType)); | |
} | |
} | |
} | |
/** | |
* Parse a map element. | |
*/ | |
public Map<?, ?> parseMapElement(Element mapEle, BeanDefinition bd) { | |
String defaultKeyType = mapEle.getAttribute(BeanDefinitionParserDelegate.KEY_TYPE_ATTRIBUTE); | |
String defaultValueType = mapEle.getAttribute(BeanDefinitionParserDelegate.VALUE_TYPE_ATTRIBUTE); | |
List<Element> entryEles = | |
DomUtils.getChildElementsByTagName(mapEle, BeanDefinitionParserDelegate.ENTRY_ELEMENT); | |
ManagedMap<Object, Object> map = new ManagedMap<Object, Object>(entryEles.size()); | |
map.setSource(extractSource(mapEle)); | |
map.setKeyTypeName(defaultKeyType); | |
map.setValueTypeName(defaultValueType); | |
map.setMergeEnabled(parseMergeAttribute(mapEle)); | |
for (Element entryEle : entryEles) { | |
// Should only have one value child element: ref, value, list, etc. | |
// Optionally, there might be a key child element. | |
NodeList entrySubNodes = entryEle.getChildNodes(); | |
Element keyEle = null; | |
Element valueEle = null; | |
for (int j = 0; j < entrySubNodes.getLength(); j++) { | |
Node node = entrySubNodes.item(j); | |
if (node instanceof Element) { | |
Element candidateEle = (Element) node; | |
if (DomUtils.nodeNameEquals(candidateEle, BeanDefinitionParserDelegate.KEY_ELEMENT)) { | |
if (keyEle != null) { | |
error("<entry> element is only allowed to contain one <key> sub-element", entryEle); | |
} else { | |
keyEle = candidateEle; | |
} | |
} else { | |
// Child element is what we're looking for. | |
if (valueEle != null) { | |
error("<entry> element must not contain more than one value sub-element", entryEle); | |
} else { | |
valueEle = candidateEle; | |
} | |
} | |
} | |
} | |
// Extract key from attribute or sub-element. | |
Object key = null; | |
boolean hasKeyAttribute = entryEle.hasAttribute(BeanDefinitionParserDelegate.KEY_ATTRIBUTE); | |
boolean hasKeyRefAttribute = entryEle.hasAttribute(BeanDefinitionParserDelegate.KEY_REF_ATTRIBUTE); | |
if ((hasKeyAttribute && hasKeyRefAttribute) || ((hasKeyAttribute || hasKeyRefAttribute)) && keyEle != null) { | |
error("<entry> element is only allowed to contain either " | |
+ "a 'key' attribute OR a 'key-ref' attribute OR a <key> sub-element", entryEle); | |
} | |
if (hasKeyAttribute) { | |
key = | |
buildTypedStringValueForMap(entryEle.getAttribute(BeanDefinitionParserDelegate.KEY_ATTRIBUTE), | |
defaultKeyType, entryEle); | |
} else if (hasKeyRefAttribute) { | |
String refName = entryEle.getAttribute(BeanDefinitionParserDelegate.KEY_REF_ATTRIBUTE); | |
if (!StringUtils.hasText(refName)) { | |
error("<entry> element contains empty 'key-ref' attribute", entryEle); | |
} | |
RuntimeBeanReference ref = new RuntimeBeanReference(refName); | |
ref.setSource(extractSource(entryEle)); | |
key = ref; | |
} else if (keyEle != null) { | |
key = parseKeyElement(keyEle, bd, defaultKeyType); | |
} else { | |
error("<entry> element must specify a key", entryEle); | |
} | |
// Extract value from attribute or sub-element. | |
Object value = null; | |
boolean hasValueAttribute = entryEle.hasAttribute(BeanDefinitionParserDelegate.VALUE_ATTRIBUTE); | |
boolean hasValueRefAttribute = entryEle.hasAttribute(BeanDefinitionParserDelegate.VALUE_REF_ATTRIBUTE); | |
if ((hasValueAttribute && hasValueRefAttribute) || ((hasValueAttribute || hasValueRefAttribute)) | |
&& valueEle != null) { | |
error("<entry> element is only allowed to contain either " | |
+ "'value' attribute OR 'value-ref' attribute OR <value> sub-element", entryEle); | |
} | |
if (hasValueAttribute) { | |
value = | |
buildTypedStringValueForMap( | |
entryEle.getAttribute(BeanDefinitionParserDelegate.VALUE_ATTRIBUTE), defaultValueType, | |
entryEle); | |
} else if (hasValueRefAttribute) { | |
String refName = entryEle.getAttribute(BeanDefinitionParserDelegate.VALUE_REF_ATTRIBUTE); | |
if (!StringUtils.hasText(refName)) { | |
error("<entry> element contains empty 'value-ref' attribute", entryEle); | |
} | |
RuntimeBeanReference ref = new RuntimeBeanReference(refName); | |
ref.setSource(extractSource(entryEle)); | |
value = ref; | |
} else if (valueEle != null) { | |
value = parsePropertySubElement(valueEle, bd, defaultValueType); | |
} else { | |
error("<entry> element must specify a value", entryEle); | |
} | |
// Add final key and value to the Map. | |
map.put(key, value); | |
} | |
return map; | |
} | |
/** | |
* Parse a props element. | |
*/ | |
public Properties parsePropsElement(Element propsEle) { | |
ManagedProperties props = new ManagedProperties(); | |
props.setSource(extractSource(propsEle)); | |
props.setMergeEnabled(parseMergeAttribute(propsEle)); | |
List propEles = DomUtils.getChildElementsByTagName(propsEle, BeanDefinitionParserDelegate.PROP_ELEMENT); | |
for (Iterator it = propEles.iterator(); it.hasNext();) { | |
Element propEle = (Element) it.next(); | |
String key = propEle.getAttribute(BeanDefinitionParserDelegate.KEY_ATTRIBUTE); | |
// Trim the text value to avoid unwanted whitespace | |
// caused by typical XML formatting. | |
String value = DomUtils.getTextValue(propEle).trim(); | |
TypedStringValue keyHolder = new TypedStringValue(key); | |
keyHolder.setSource(extractSource(propEle)); | |
TypedStringValue valueHolder = new TypedStringValue(value); | |
valueHolder.setSource(extractSource(propEle)); | |
props.put(keyHolder, valueHolder); | |
} | |
return props; | |
} | |
private boolean parseMergeAttribute(Element element) { | |
return parserContext.getDelegate().parseMergeAttribute(element); | |
} | |
/** | |
* Build a typed String value Object for the given raw value. | |
* | |
* @see org.springframework.beans.factory.config.TypedStringValue | |
*/ | |
private Object buildTypedStringValueForMap(String value, String defaultTypeName, Element entryEle) { | |
try { | |
TypedStringValue typedValue = buildTypedStringValue(value, defaultTypeName); | |
typedValue.setSource(extractSource(entryEle)); | |
return typedValue; | |
} catch (ClassNotFoundException ex) { | |
error("Type class [" + defaultTypeName + "] not found for Map key/value type", entryEle, ex); | |
return value; | |
} | |
} | |
/** | |
* Parse a key sub-element of a map element. | |
*/ | |
private Object parseKeyElement(Element keyEle, BeanDefinition bd, String defaultKeyTypeName) { | |
NodeList nl = keyEle.getChildNodes(); | |
Element subElement = null; | |
for (int i = 0; i < nl.getLength(); i++) { | |
Node node = nl.item(i); | |
if (node instanceof Element) { | |
// Child element is what we're looking for. | |
if (subElement != null) { | |
error("<key> element must not contain more than one value sub-element", keyEle); | |
} else { | |
subElement = (Element) node; | |
} | |
} | |
} | |
return parsePropertySubElement(subElement, bd, defaultKeyTypeName); | |
} | |
// util methods (used as shortcuts) | |
private Object extractSource(Element ele) { | |
return parserContext.extractSource(ele); | |
} | |
/** | |
* Reports an error with the given message for the given source element. | |
*/ | |
private void error(String message, Node source) { | |
parserContext.getReaderContext().error(message, source, parseState.snapshot()); | |
} | |
/** | |
* Reports an error with the given message for the given source element. | |
*/ | |
private void error(String message, Node source, Throwable cause) { | |
parserContext.getReaderContext().error(message, source, parseState.snapshot(), cause); | |
} | |
private BlueprintDefaultsDefinition getDefaults(Element ele) { | |
if (defaults == null) { | |
defaults = new BlueprintDefaultsDefinition(ele.getOwnerDocument(), parserContext); | |
} | |
return defaults; | |
} | |
} |