/* http://robertmaldon.blogspot.com/2007/04/ conditionally-defining-spring-beans.html */ /* vim: set ts=2 et sw=2 cindent fo=qroca: */ package com.globant.katari.core.spring; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ResourceEntityResolver; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.ResourceLoader; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; /** Implements the conditional import spring extension. * * This class parses the import element from the katari namespace. That element * works like spring import tag, but specific for katari modules, and the import * can be skipped if certain conditions are met. * * To decide if the module is going to be imported, this class uses * property-name and property-value attributes: the module is imported only if * the value of the property named [property-name] is [property-value]. * * The resource to be imported is always a katari module: a file named * module.xml. The parser searches in the 'resource' path in the classpath. * * This parser looks for property values in property files loaded by the * 'properties' element (also in the katari namespace). If the import specifies * a properties-ref attribute, the property value is looked for in the property * element with a matching name. Otherwise, it is looked for in all the declared * properties. If the same property is found in more that one property file, * then it considers the last one. * * All properties elements must go before the first import element. You must * specify at least one properties element. */ public class ConditionalImportParser implements BeanDefinitionParser { /** Constant for the katari:import element. */ private static final String IMPORT = "import"; /** Constant for the resource attribute. */ private static final String MODULE = "module"; /** Constant for the ref-properties attribute. */ private static final String PROPERTIES_REF = "properties-ref"; /** Constant for the property-value attribute. */ private static final String PROPERTY_VALUE = "property-value"; /** Constant for the property-name attribute. */ private static final String PROPERTY_NAME = "property-name"; /** Constant for the module name. */ private static final String KATARI_MODULE_NAME = "module.xml"; /** The prefix to add to the module name, so that spring loads it from the * classpath. */ private static final String CLASSPATH_PREFIX = "classpath:"; /** Interpretes the katari:properties element, used here to check that no * katari:properties come after any katari:import. * * This is never null. */ private PropertiesParser propertiesParser; /** Constructor. * * @param thePropertiesParser this is used to check that no katari:properties * come after any katari:import. It is never null. */ public ConditionalImportParser(final PropertiesParser thePropertiesParser) { super(); propertiesParser = thePropertiesParser; } /** Parses an import element. * * It must have a module attribute, and may have optional properties-ref, * property-name and property-value attributes. * * @return always returns null. * * {@inheritDoc} */ public BeanDefinition parse(final Element element, final ParserContext parserContext) { if (DomUtils.nodeNameEquals(element, IMPORT)) { // notify the properties parser that import has begun and no more // <katari:properties> should be allowed propertiesParser.katariImportStarted(); if (evalCondition(element)) { return parseAndRegisterBean(element, parserContext); } } return null; } /** * Evaluates the condition specified in the import element. * * If the condition evals to true, the module (specified by the resource * attribute) should be loaded * * @param condition the condition element. It may have a properties-ref, * property-name and property-value optional attributes. * * @return true if condition does not have an attribute named * [attribute-name] or the attribute named [attribute-name] is * [attribute-value]. */ private boolean evalCondition(final Element condition) { String refProperties = condition.getAttribute(PROPERTIES_REF); String propertyName = condition.getAttribute(PROPERTY_NAME); String propertyValue = condition.getAttribute(PROPERTY_VALUE); if (!condition.hasAttribute(PROPERTY_NAME)) { // property-name not specified, evaluates to true. return true; } String actualPropertyValue = propertiesParser.getProperty(refProperties, propertyName); if ((!StringUtils.isEmpty(actualPropertyValue)) && (actualPropertyValue.equals(propertyValue))) { return true; } return false; } /** * Parses the and register bean. * * @param element * the element * @param parserContext * the parser context * * @return the bean definition */ private BeanDefinition parseAndRegisterBean(final Element element, final ParserContext parserContext) { XmlBeanDefinitionReader beanReader; beanReader = new XmlBeanDefinitionReader(parserContext.getRegistry()); // Configure the bean definition reader with this context's // resource loading environment. ResourceLoader resourceLoader; resourceLoader = parserContext.getReaderContext().getResourceLoader(); beanReader.setResourceLoader(resourceLoader); beanReader.setEntityResolver(new ResourceEntityResolver(resourceLoader)); String location = element.getAttribute(MODULE); location = location.replaceAll("\\.", "/"); location = CLASSPATH_PREFIX.concat(location).concat("/").concat( KATARI_MODULE_NAME); beanReader.loadBeanDefinitions(location); return null; } }