blob: f0fcd49f93ca55993c4f52c0209040f1baaed5e3 [file] [log] [blame]
/******************************************************************************
* 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 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;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.blueprint.config.internal.support.InstanceEqualityRuntimeBeanReference;
import org.eclipse.gemini.blueprint.blueprint.reflect.internal.support.OrderedManagedProperties;
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;
/**
* 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>&lt;bean&gt;</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>&lt;value&gt;</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 OrderedManagedProperties();
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;
}
}