/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.myfaces.orchestra.annotation.spring;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.orchestra.annotation.AnnotationInfo;
import org.apache.myfaces.orchestra.annotation.AnnotationInfoManager;
import org.apache.myfaces.orchestra.conversation.annotations.ConversationName;
import org.apache.myfaces.orchestra.conversation.spring.BeanDefinitionConversationNameAttrDecorator;
import org.apache.myfaces.orchestra.conversation.spring._SpringUtils;
import org.apache.myfaces.shared_orchestra.util.ClassUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Ordered;

/**
 * Parse all configured spring beans and extract Orchestra annotations out of
 * them.
 * <p>
 * Just declaring an instance of this type as a Spring Singleton will cause the
 * postProcessBeanFactory to be called passing in info about all the bean
 * declarations in the spring context, allowing Orchestra annotations on any
 * directly declared class to be discovered and processed.
 * <p>
 * Every class referenced from a bean declaration is then passed to the
 * AnnotationInfoManager instance that has been injected into this object.
 */
public class AnnotationsInfoInitializer implements BeanFactoryPostProcessor, Ordered
{
    private Log log = LogFactory.getLog(AnnotationsInfoInitializer.class);

    private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered

    private AnnotationInfoManager annotationInfoManager;

    /**
     * Implement the Spring Ordered interface.
     */
    public int getOrder()
    {
        return order;
    }

    public void setOrder(int order)
    {
        this.order = order;
    }

    /**
     * Inject the object that actually inspects each Class for Orchestra annotations.
     */
    public void setAnnotationInfoManager(AnnotationInfoManager annotationInfoManager)
    {
        this.annotationInfoManager = annotationInfoManager;
    }

    /**
     * For each bean in the beanFactory, load the appropriate Class object and
     * pass it to the annotationInfoManager object for inspection.
     */
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException
    {
        String[] beanNames = beanFactory.getBeanDefinitionNames();
        for (String beanName : beanNames)
        {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            String className = beanDefinition.getBeanClassName();
            if (className != null)
            {
                Class<?> beanClass = null;
                try
                {
                    beanClass = ClassUtils.classForName(className);
                }
                catch (ClassNotFoundException e)
                {
                    log.debug(e.getLocalizedMessage(), e);
                }

                if (beanClass != null)
                {
                    // XXX: Hack to deal with aop:scope-proxy (scopedTarget.) beans
                    if (!_SpringUtils.isAlternateBeanName(beanName))
                    {
                        // we are not on a scopedTarget ... check if there is one
                        String alternateBeanName = _SpringUtils.getAlternateBeanName(beanName);
                        if (beanFactory.containsBeanDefinition(alternateBeanName))
                        {
                            // ... yes, we are just interested in the alternate one.
                            continue;
                        }
                    }
                    String realBeanName = _SpringUtils.getRealBeanName(beanName);

                    // check whether the bean is an orchestra-annotated bean,
                    // and if so cache its annotation info for later use.
                    annotationInfoManager.processBeanAnnotations(realBeanName, beanClass);

                    // Now deal with any annotation data that must be processed at startup.
                    AnnotationInfo info = annotationInfoManager
                            .getAnnotationInfoByBeanName(realBeanName);
                    if (info != null)
                    {
                        processStartupAnnotations(beanDefinition, info);
                    }
                }
            }
        }
    }

    /**
     * Handle any annotations on a bean which should be processed on startup.
     * <p>
     * One such annotation is the
     * 
     * @ConversationName annotation, which should modify the beanDefinition
     *                   object, as it is equivalent to putting an
     *                   orchestra:conversationName attribute in the spring bean
     *                   declaration.
     */
    private void processStartupAnnotations(BeanDefinition beanDefinition, AnnotationInfo info)
    {
        ConversationName conversationName = info.getConversationName();
        if (conversationName != null)
        {
            String convNameFromAnno = conversationName.value();
            if (convNameFromAnno != null && convNameFromAnno.length() > 0)
            {
                String convNameFromDef = getConversationName(beanDefinition);
                if (convNameFromDef == null)
                {
                    setConversationName(beanDefinition, convNameFromAnno);
                }
            }
        }
    }

    /**
     * Get the orchestra conversationName attribute (if any) from the spring
     * bean definition.
     */
    // TODO: Move this method into orchestra-core, then call it from here, from
    // AbstractSpringOrchestraScope and BeanDefinition*Decorator. This of course
    // creates a dependency from this code onto that modified orchestra-core
    // version.
    private static String getConversationName(BeanDefinition def)
    {
        return (String) def
                .getAttribute(BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE);
    }

    // See getConversationName
    private static void setConversationName(BeanDefinition def, String convName)
    {
        def.setAttribute(BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE,
                convName);
    }
}
