/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.yarn.config;
import java.util.List;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.Conventions;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Shared utility methods for yarn namespace parsers.
*
* @author Janne Valkealahti
*
*/
public class YarnNamespaceUtils {
static final String REF_ATTRIBUTE = "ref";
/**
* Configures the provided bean definition builder with a property value
* corresponding to the attribute whose name is provided if that attribute
* is defined in the given element.
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be used to populate the property
* @param propertyName the name of the property to be populated
*/
public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName, String propertyName) {
setValueIfAttributeDefined(builder, element, attributeName, propertyName, false);
}
/**
* Configures the provided bean definition builder with a property value
* corresponding to the attribute whose name is provided if that attribute
* is defined in the given element.
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be used to populate the property
* @param propertyName the name of the property to be populated
* @param defaultPropertyValue Property value to use as default
*/
public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName, String propertyName, String defaultPropertyValue) {
setValueIfAttributeDefined(builder, element, attributeName, propertyName, false, defaultPropertyValue);
}
/**
* Configures the provided bean definition builder with a property value
* corresponding to the attribute whose name is provided if that attribute
* is defined in the given element.
* <p>
* The property name will be the camel-case equivalent of the lower case
* hyphen separated attribute (e.g. the "foo-bar" attribute would match the
* "fooBar" property).
*
* @see Conventions#attributeNameToPropertyName(String)
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be set on the property
*/
public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, Element element, String attributeName) {
setValueIfAttributeDefined(builder, element, attributeName, false);
}
/**
* Configures the provided bean definition builder with a property value
* corresponding to the attribute whose name is provided if that attribute
* is defined in the given element.
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be
* used to populate the property
* @param propertyName the name of the property to be populated
* @param emptyStringAllowed if true, the value is set, even if an empty String (""); if
* false, an empty String is treated as if the attribute wasn't provided.
*/
public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName, String propertyName, boolean emptyStringAllowed) {
String attributeValue = element.getAttribute(attributeName);
if (StringUtils.hasText(attributeValue) || (emptyStringAllowed && element.hasAttribute(attributeName))) {
builder.addPropertyValue(propertyName, new TypedStringValue(attributeValue));
}
}
/**
* Configures the provided bean definition builder with a property value
* corresponding to the attribute whose name is provided if that attribute
* is defined in the given element.
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be
* used to populate the property
* @param propertyName the name of the property to be populated
* @param emptyStringAllowed if true, the value is set, even if an empty String (""); if
* false, an empty String is treated as if the attribute wasn't provided.
* @param defaultPropertyValue Property value to use as default
*/
public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName, String propertyName, boolean emptyStringAllowed, String defaultPropertyValue) {
String attributeValue = element.getAttribute(attributeName);
if (StringUtils.hasText(attributeValue) || (emptyStringAllowed && element.hasAttribute(attributeName))) {
builder.addPropertyValue(propertyName, new TypedStringValue(attributeValue));
} else if (StringUtils.hasText(defaultPropertyValue)) {
builder.addPropertyValue(propertyName, new TypedStringValue(defaultPropertyValue));
}
}
/**
* Configures the provided bean definition builder with a property value
* corresponding to the attribute whose name is provided if that attribute
* is defined in the given element.
* <p>
* The property name will be the camel-case equivalent of the lower case
* hyphen separated attribute (e.g. the "foo-bar" attribute would match the
* "fooBar" property).
*
* @see Conventions#attributeNameToPropertyName(String)
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be set on the property
* @param emptyStringAllowed if true, the value is set, even if an empty String (""); if
* false, an empty String is treated as if the attribute wasn't provided.
*/
public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName, boolean emptyStringAllowed) {
setValueIfAttributeDefined(builder, element, attributeName,
Conventions.attributeNameToPropertyName(attributeName), emptyStringAllowed);
}
/**
* Configures the provided bean definition builder with a property value
* corresponding to the attribute whose name is provided if that attribute
* is defined in the given element.
* <p>
* The property name will be the camel-case equivalent of the lower case
* hyphen separated attribute (e.g. the "foo-bar" attribute would match the
* "fooBar" property).
*
* @see Conventions#attributeNameToPropertyName(String)
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be set on the property
* @param emptyStringAllowed if true, the value is set, even if an empty String (""); if
* false, an empty String is treated as if the attribute wasn't provided.
* @param defaultPropertyValue Property value to use as default
*/
public static void setValueIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName, boolean emptyStringAllowed, String defaultPropertyValue) {
setValueIfAttributeDefined(builder, element, attributeName,
Conventions.attributeNameToPropertyName(attributeName), emptyStringAllowed, defaultPropertyValue);
}
/**
* Configures the provided bean definition builder with a property reference
* to a bean. The bean reference is identified by the value from the
* attribute whose name is provided if that attribute is defined in the
* given element.
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be used as a bean
* reference to populate the property
* @param defaultName the default bean reference name to use
* @param propertyName the name of the property to be populated
*/
public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName, String propertyName, String defaultName) {
String attributeValue = element.getAttribute(attributeName);
if (StringUtils.hasText(attributeValue)) {
builder.addPropertyReference(propertyName, attributeValue);
} else if (StringUtils.hasText(defaultName)) {
builder.addPropertyReference(propertyName, defaultName);
}
}
/**
* Configures the provided bean definition builder with a property reference
* to a bean. The bean reference is identified by the value from the
* attribute whose name is provided if that attribute is defined in the
* given element.
* <p>
* The property name will be the camel-case equivalent of the lower case
* hyphen separated attribute (e.g. the "foo-bar" attribute would match the
* "fooBar" property).
*
* @see Conventions#attributeNameToPropertyName(String)
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be used as a bean
* reference to populate the property
*
* @see Conventions#attributeNameToPropertyName(String)
*/
public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName) {
setReferenceIfAttributeDefined(builder, element, attributeName,
Conventions.attributeNameToPropertyName(attributeName), null);
}
/**
* Configures the provided bean definition builder with a property reference
* to a bean. The bean reference is identified by the value from the
* attribute whose name is provided if that attribute is defined in the
* given element.
* <p>
* The property name will be the camel-case equivalent of the lower case
* hyphen separated attribute (e.g. the "foo-bar" attribute would match the
* "fooBar" property).
*
* @see Conventions#attributeNameToPropertyName(String)
*
* @param builder the bean definition builder to be configured
* @param element the XML element where the attribute should be defined
* @param attributeName the name of the attribute whose value will be used as a bean
* reference to populate the property
* @param defaultName the default bean reference name to use
*
* @see Conventions#attributeNameToPropertyName(String)
*/
public static void setReferenceIfAttributeDefined(BeanDefinitionBuilder builder, Element element,
String attributeName, String defaultName) {
setReferenceIfAttributeDefined(builder, element, attributeName,
Conventions.attributeNameToPropertyName(attributeName), defaultName);
}
/**
* Parses inner bean definition from an dom element.
*
* @param element the dom element
* @param parserContext the parser context
* @return bean component definition
*/
public static BeanComponentDefinition parseInnerHandlerDefinition(Element element, ParserContext parserContext) {
// parses out the inner bean definition for concrete implementation if defined
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "bean");
BeanComponentDefinition innerComponentDefinition = null;
if (childElements != null && childElements.size() == 1) {
Element beanElement = childElements.get(0);
BeanDefinitionParserDelegate delegate = parserContext.getDelegate();
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(beanElement);
bdHolder = delegate.decorateBeanDefinitionIfRequired(beanElement, bdHolder);
BeanDefinition inDef = bdHolder.getBeanDefinition();
innerComponentDefinition = new BeanComponentDefinition(inDef, bdHolder.getBeanName());
}
String ref = element.getAttribute(REF_ATTRIBUTE);
if (StringUtils.hasText(ref) && innerComponentDefinition != null) {
parserContext.getReaderContext().error(
"Ambiguous definition. Inner bean "
+ (innerComponentDefinition == null ? innerComponentDefinition : innerComponentDefinition
.getBeanDefinition().getBeanClassName()) + " declaration and \"ref\" " + ref
+ " are not allowed together on element " + createElementDescription(element) + ".",
parserContext.extractSource(element));
}
return innerComponentDefinition;
}
/**
* Provides a user friendly description of an element based on its node name
* and, if available, its "id" attribute value. This is useful for creating
* error messages from within bean definition parsers.
*
* @param element the dom element
* @return user friendly element description
*/
public static String createElementDescription(Element element) {
String elementId = "'" + element.getNodeName() + "'";
String id = element.getAttribute("id");
if (StringUtils.hasText(id)) {
elementId += " with id='" + id + "'";
}
return elementId;
}
/**
* Sets the reference property as list from a comma delimited string.
*
* @param element the element
* @param builder the builder
* @param attrName the attr name
* @param propertyName the property name
* @return true, if successful
*/
public static boolean setCSVReferenceProperty(Element element, BeanDefinitionBuilder builder, String attrName,
String propertyName) {
String attr = element.getAttribute(attrName);
if (StringUtils.hasText(attr)) {
String[] strs = StringUtils.commaDelimitedListToStringArray(attr);
ManagedList<RuntimeBeanReference> list = new ManagedList<RuntimeBeanReference>(strs.length);
for (int i = 0; i < strs.length; i++) {
list.add(new RuntimeBeanReference(strs[i].trim()));
}
builder.addPropertyValue(propertyName, list);
return true;
}
return false;
}
}