package eu.dnetlib.springutils.condbean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import com.google.common.collect.Lists;

import eu.dnetlib.springutils.condbean.parser.CondBeanParser;
import eu.dnetlib.springutils.condbean.parser.RunccExpressionParser;

/**
 * Taken from http://robertmaldon.blogspot.com/2007/04/conditionally-defining-spring-beans.html .
 * 
 * modified to fix a problem caused by http://jira.springframework.org/browse/SPR-2955 improved expression parsing
 * 
 * @author marko
 * 
 */
public class ConditionalBeanDefinitionParser implements BeanDefinitionParser {

	/**
	 * logger.
	 */
	@SuppressWarnings("unused")
	// NOPMD by marko on 11/24/08 5:02 PM
	private static final Log log = LogFactory.getLog(ConditionalBeanDefinitionParser.class);

	/**
	 * expression parser.
	 */
	private transient RunccExpressionParser expressionParser = null;

	public void init(ResourceLoader resourceLoader) {
		if(expressionParser != null)
			return;
		
		final ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);

		ChainPropertyFinder chain = new ChainPropertyFinder();

		ResourcePropertyFinder res1 = new ResourcePropertyFinder();
		res1.setResource(resolver.getResource("classpath:/eu/dnetlib/cnr-default.properties"));

		ResourcePropertyFinder res2 = new ResourcePropertyFinder();
		res2.setResource(resolver.getResource("classpath:/eu/dnetlib/cnr-site.properties"));
		
		ResourcePropertyFinder res3 = new ResourcePropertyFinder();
		res3.setResource(resolver.getResource("classpath:/cnr-override.properties"));

		chain.setFinders(Lists.newArrayList(new SystemPropertiesFinder(), res3, res2, res1));
		
		CondBeanParser condBeanParser = new CondBeanParser();
		condBeanParser.setPropertyFinder(chain);
		
		expressionParser = new RunccExpressionParser();
		expressionParser.setCondBeanParser(condBeanParser);
	}

	/**
	 * Parse the "cond" element and check the mandatory "test" attribute. If the system property named by test is null
	 * or empty (i.e. not defined) then return null, which is the same as not defining the bean.
	 * 
	 * @param element
	 *            element to parse
	 * @param parserContext
	 *            spring parser context
	 * @return registered bean or null.
	 */
	@Override
	public BeanDefinition parse(final Element element, final ParserContext parserContext) {
		init(parserContext.getReaderContext().getResourceLoader());
		
		if (DomUtils.nodeNameEquals(element, "cond")) {
			final String test = element.getAttribute("test");
			if (expressionParser.expressionValue(test)) {
				final Element beanElement = DomUtils.getChildElementByTagName(element, "bean");
				return parseAndRegisterBean(beanElement, parserContext);
			}
		}

		return null;
	}

	/**
	 * the real job of registering the child element of this conditional bean is performed here.
	 * 
	 * @param element
	 *            element
	 * @param parserContext
	 *            spring parser context
	 * @return registered bean.
	 */
	private BeanDefinition parseAndRegisterBean(final Element element, final ParserContext parserContext) {
		if (element == null)
			throw new IllegalStateException("trying to register a null bean");

		final BeanDefinitionParserDelegate delegate = parserContext.getDelegate();
		BeanDefinitionHolder holder = delegate.parseBeanDefinitionElement(element);
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());

		// necessary because of http://jira.springframework.org/browse/SPR-2955
		holder = delegate.decorateBeanDefinitionIfRequired(element, holder);
		return holder.getBeanDefinition();
	}

}
