/* vim: set ts=2 et sw=2 cindent fo=qroca: */ package com.globant.katari.core.spring; import java.io.IOException; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.core.io.Resource; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; /** Implements the katari:properties spring extension. * * This class parses the properties element from the katari namespace. The * properties file to be loaded is specified in the resource attribute in the * same way as for any other Spring resource (e.g.: * classpath:/com/katari/ui/ui.properties). * * These properties are used by the import element, also defined in the katari * namespace, for condition evaluation. Also, if the name attribute is * specified, the resulting properties object (instance of * java.util.Properties) is stored as a bean in the Spring application context * and available for later use. * * The bean's name is defined in the name attribute of the properties element. * * An import element from the katari namespace can refer to one of this * properties instance by setting the bean name in the properties-ref attribute. */ public class PropertiesParser implements BeanDefinitionParser { /** Constant for the properties element. */ private static final String PROPERTIES = "properties"; /** Constant for the name attribute. */ private static final String NAME = "name"; /** Constant for the location attribute. */ private static final String LOCATION = "location"; /** The class logger. */ private static Logger log = LoggerFactory.getLogger(PropertiesParser.class); /** The map holding all the named katari:properties. * * It is never null. */ private Map<String, Properties> propertiesMap = new LinkedHashMap<String, Properties>(); /** The list of all the katari:properties, in the reverse order found. * * It is never null. */ private List<Properties> propertiesList = new LinkedList<Properties>(); /** We should fail if an import is found after a katari:import definition. * * This attribute is set to true by the import element parsing (see * katariImportStarted. */ private boolean katariImportStarted = false; /** {@inheritDoc} * * This should never appear after a katari:import element. * * @param element the element. It must correspond to 'import'. * * @return this implementation always returns null. */ public BeanDefinition parse(final Element element, final ParserContext parserContext) { Validate.isTrue(DomUtils.nodeNameEquals(element, PROPERTIES)); log.trace("Entering parse"); String name = element.getAttribute(NAME); if (katariImportStarted) { throw new RuntimeException("katari:properties [" + name + "] found after a katari:import"); } String location = element.getAttribute(LOCATION); Resource resource = parserContext.getReaderContext().getResourceLoader() .getResource(location); // create the properties object PropertiesFactoryBean propertiesFactory = new PropertiesFactoryBean(); propertiesFactory.setLocation(resource); try { propertiesFactory.afterPropertiesSet(); log.trace("Katari properties loaded: bean name={} values [{}]", name, propertiesFactory.getObject()); addProperties(name, (Properties) propertiesFactory.getObject()); if (name != null) { // create a bean definition to get these properties available in the // application context AbstractBeanDefinition def = new GenericBeanDefinition(); def.setBeanClass(PropertiesFactoryBean.class); MutablePropertyValues values = new MutablePropertyValues(); values.addPropertyValue("location", resource); def.setPropertyValues(values); parserContext.getRegistry().registerBeanDefinition(name, def); } } catch (IOException e) { throw new RuntimeException("Error getting katari properties", e); } log.trace("Leaving parse"); return null; } /** Records the properties found. * * If the name is not null, it adds the properties instance to the map of all * the properties instances. * * @param name the properties name. If null, this properties cannot be search * by name. * * @param properties the properties. It cannot be null. */ private void addProperties(final String name, final Properties properties) { if (name != null) { propertiesMap.put(name, properties); } propertiesList.add(0, properties); } /** Gets the property value from the katari:properties element. * * @param refProperties the properties bean to be used, if null, all * available properties are used * * @param propertyName the propertyName value. It cannot be null * * @return the property value, never null. * * @TODO analyze using default values for missing properties */ public String getProperty(final String refProperties, final String propertyName) { Validate.notNull(propertyName, "The property name cannot be null."); // if a properties bean has been referenced, just look for the property in // it if (!StringUtils.isEmpty(refProperties)) { Properties properties = propertiesMap.get(refProperties); if (properties == null) { throw new RuntimeException("Properties bean [" + refProperties + "] couldn't be found"); } String propertyValue = properties.getProperty(propertyName); if (StringUtils.isEmpty(propertyValue)) { throw new RuntimeException("Couldn't get value for [" + propertyName + "] in properties bean [" + refProperties + "]"); } return propertyValue; } // no reference to a specific properties bean, use all available for (Properties properties : propertiesList) { if (properties.containsKey(propertyName)) { return properties.getProperty(propertyName); } } throw new RuntimeException("Couldn't get value for [" + propertyName + "] in any of the properties beans"); } /** Notifies this object that the parser found an import element in the * application context. */ public void katariImportStarted() { katariImportStarted = true; } }