/*
 * Copyright 2002-2008 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.config.java.support;

import static java.lang.String.format;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;

import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.internal.factory.TypeSafeBeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.ResourceLoaderAware;


/**
 * Convenient base class for Configurations, allowing easy lookup of beans in the owning factory as
 * well as other services.
 *
 * @author  Rod Johnson
 * @author  Chris Beams
 */
@Configuration
public abstract class ConfigurationSupport implements BeanFactoryAware, ApplicationContextAware {

    private BeanFactory beanFactory;

    private AutowireCapableBeanFactory autowireCapableBeanFactory;

    private ApplicationContext applicationContext;

    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
        if (beanFactory instanceof AutowireCapableBeanFactory) {
            autowireCapableBeanFactory = (AutowireCapableBeanFactory) beanFactory;
        }
    }

    protected BeanFactory getBeanFactory() {
        return this.beanFactory;
    }

    public void setApplicationContext(ApplicationContext ac) {
        this.applicationContext = ac;
    }

    protected ApplicationContext getApplicationContext() {
        return this.applicationContext;
    }

    public Object getBean(String beanName) {
        return beanFactory.getBean(beanName);
    }

    public <T> T getBean(Class<T> type) {
        return TypeSafeBeanFactoryUtils.getBean((ListableBeanFactory) beanFactory, type);
    }

    public <T> T getBean(Class<T> type, String beanName) {
        return TypeSafeBeanFactoryUtils.getBean((ListableBeanFactory) beanFactory, type, beanName);
    }

    /**
     * Return the object created by this {@link FactoryBean} instance, first invoking any container
     * callbacks on the instance.
     *
     * @param   factoryBean FactoryBean instance
     *
     * @return  The object created by the {@link #getConfigured(Object) configured}
     *          FactoryBean instance
     *
     * @throws  FactoryBeanObjectCreationException wraps and rethrows any Exception thrown
     *          when invoking the underlying {@link FactoryBean#getObject()} method.
     *
     * @see FactoryBeanObjectCreationException
     */
    protected Object getObject(FactoryBean factoryBean) throws FactoryBeanObjectCreationException {
        try {
            return getConfigured(factoryBean).getObject();
        } catch (Exception ex) {
            throw new FactoryBeanObjectCreationException(factoryBean, ex);
        }
    }

    /**
     * Same as {@link #getObject(FactoryBean)}, but checks {@link FactoryBean#getObjectType()}
     * against user-supplied <var>requiredType</var> and then casts internally to return
     * a strongly-typed result.
     * <p/>
     *
     * Note: Will likely be removed/deprecated in the Spring 3.0 timeframe, presuming that
     * FactoryBean will be parameterized, at which point its generics metadata can be used
     * directly.
     *
     * @param <T> Expected type
     * @param factoryBean target {@link FactoryBean} instance
     * @param requiredType Class of expected type
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T getObject(FactoryBean factoryBean, Class<T> requiredType) {
        Class actualType = factoryBean.getObjectType();

        if(!requiredType.isAssignableFrom(actualType))
            throw new FactoryBeanObjectCreationException(
                    format("FactoryBean object type mismatch: expected [%s], " +
                            "but FactoryBean actually creates [%s]. Offending " +
                            "FactoryBean instance is: %s", requiredType, actualType, factoryBean));

        return (T) getObject(factoryBean);
    }

    /**
     * Invoke callbacks on the object, as though it were configured in the factory. If appropriate,
     * the object may be wrapped before being returned. For this reason, it is recommended to always
     * respect the return value when using this method.
     *
     * @param   object  object to configure
     *
     * @return  either the original object or a wrapped one after callbacks called on it.
     */
    protected <T> T getConfigured(T object) {
        if (this.autowireCapableBeanFactory == null)
            throw new UnsupportedOperationException(
                "Cannot configure object - not running in an AutowireCapableBeanFactory");

        @SuppressWarnings("unchecked") // See SPR-4955
        T configuredObject = (T) autowireCapableBeanFactory.initializeBean(object, null);

        // this block copied from ApplicationContextAwareProcessor.  See SJC-149.
        if (this.applicationContext != null) {
            if (configuredObject instanceof ResourceLoaderAware)
                ((ResourceLoaderAware) configuredObject).setResourceLoader(this.applicationContext);

            if (configuredObject instanceof ApplicationEventPublisherAware)
                ((ApplicationEventPublisherAware) configuredObject).setApplicationEventPublisher(this.applicationContext);

            if (configuredObject instanceof MessageSourceAware)
                ((MessageSourceAware) configuredObject).setMessageSource(this.applicationContext);

            if (configuredObject instanceof ApplicationContextAware)
                ((ApplicationContextAware) configuredObject).setApplicationContext(this.applicationContext);
        }

        return configuredObject;
    }
}
