com.agileapes.couteau.context.spring.SpringContextConfigurator.java Source code

Java tutorial

Introduction

Here is the source code for com.agileapes.couteau.context.spring.SpringContextConfigurator.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2013 AgileApes, Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package com.agileapes.couteau.context.spring;

import com.agileapes.couteau.context.contract.*;
import com.agileapes.couteau.context.error.RegistryException;
import com.agileapes.couteau.reflection.util.ClassUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * <p>This class allows for configuring a Couteau context into a Spring context. The method to do this
 * is to first include this class as a bean inside a Spring application context and then fetch the
 * instance and ask the configurator to configure the context for you by calling to {@link #configure(Context)}</p>
 *
 * @see SpringConfigurableContext
 * @author Mohammad Milad Naseri (m.m.naseri@gmail.com)
 * @since 1.0 (6/29/13, 3:54 PM)
 */
public class SpringContextConfigurator implements BeanFactoryPostProcessor {

    private ConfigurableListableBeanFactory beanFactory;

    private final Set<Context<?>> contexts = new HashSet<Context<?>>();
    private final Set<Context<?>> autoConfigure = new HashSet<Context<?>>();

    public SpringContextConfigurator() {
    }

    /**
     * This constructor allows you to add contexts to be automatically configured by this configurator
     * @param contexts    the context being configured.
     */
    public SpringContextConfigurator(Collection<Context<?>> contexts) {
        autoConfigure.addAll(contexts);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
        final Collection<SpringConfigurableContext> contexts = beanFactory
                .getBeansOfType(SpringConfigurableContext.class, false, true).values();
        for (SpringConfigurableContext context : contexts) {
            autoConfigure.add(context);
        }
        if (!autoConfigure.isEmpty()) {
            for (Context<?> context : autoConfigure) {
                ContextConfigurator<Context<?>> configurator = ContextConfigurator.NULL;
                if (context instanceof SpringConfigurableContext<?>) {
                    final SpringConfigurableContext<?> configurableContext = (SpringConfigurableContext<?>) context;
                    configurator = new ContextConfigurator<Context<?>>() {
                        @Override
                        public void configure(Context<?> context, ConfigurableListableBeanFactory beanFactory) {
                            configurableContext.configure(beanFactory);
                        }
                    };
                }
                try {
                    configure(context, configurator);
                } catch (RegistryException e) {
                    throw new FatalBeanException("Failed to configure context", e);
                }
            }
        }
    }

    /**
     * This will simply do the work that {@link #configure(Context, ContextConfigurator)} does
     * without the added option to configure the context manually once it has been configured by
     * this context.
     * @param context    th context to be configured
     * @param <C>        the type of the context
     * @throws RegistryException
     */
    public <C extends Context<?>> void configure(C context) throws RegistryException {
        configure(context, ContextConfigurator.NULL);
    }

    /**
     * This method will configure the context completely. What it does goes as follows:
     * <ul>
     *     <li>First of all, it will find bean factory aware contexts, and inject the contexts
     *     into them. This is so that if you have a context that has not been already initialized
     *     through Spring, and yet you would want to give it access to the Spring context, you would
     *     be able to. You can further proceed to call {@link ConfigurableListableBeanFactory#registerSingleton(String, Object)}
     *     to register this context with Spring.</li>
     *     <li>It will add any event listeners found in the Spring context and
     *     add it to the context being configured</li>
     *     <li>Then it will pick up bean processors that are applicable to this context, and add them as
     *     bean processors</li>
     *     <li>Next, it will do the same for context processors.</li>
     *     <li>It is at <em>this</em> stage that the configurator will be given a chance to manipulate
     *     the configuration process.</li>
     *     <li>After that, if at the steps above we have picked any context processors and the context
     *     is refreshable, we will refresh the context.</li>
     *     <li>Then we will find ContextAware beans that request access to this context, and give them
     *     to this context.</li>
     *     <li>Last but not least, we will take all the beans from this context, and register them as
     *     singleton objects with Spring. This allows Spring to separately configure them and inject any
     *     dependencies into them.</li>
     * </ul>
     * @param context         the context being configured
     * @param configurator    the configuration callback
     * @param <C>             the type of the context
     * @throws RegistryException
     */
    public <C extends Context<?>> void configure(C context, ContextConfigurator<C> configurator)
            throws RegistryException {
        if (contexts.contains(context)) {
            return;
        }
        contexts.add(context);
        if (context instanceof BeanFactoryAwareContext) {
            ((BeanFactoryAwareContext) context).setBeanFactory(beanFactory);
        }
        final Map<String, EventListener> listeners = beanFactory.getBeansOfType(EventListener.class, false, true);
        for (EventListener<?> eventListener : listeners.values()) {
            context.addEventListener(eventListener);
        }
        final Map<String, BeanProcessor> processors = beanFactory.getBeansOfType(BeanProcessor.class, false, true);
        for (BeanProcessor processor : processors.values()) {
            final Class<?> processorType = ClassUtils.resolveTypeArgument(processor.getClass(),
                    BeanProcessor.class);
            if (context.getRegistryType().isAssignableFrom(processorType)) {
                //noinspection unchecked
                context.addBeanProcessor(processor);
            }
        }
        boolean needsRefresh = false;
        final Map<String, ContextProcessor> contextProcessors = beanFactory.getBeansOfType(ContextProcessor.class,
                false, true);
        for (ContextProcessor processor : contextProcessors.values()) {
            final Class<?> processorType = ClassUtils.resolveTypeArgument(processor.getClass(),
                    ContextProcessor.class);
            if (context.getRegistryType().isAssignableFrom(processorType)) {
                needsRefresh = true;
                //noinspection unchecked
                context.addContextProcessor(processor);
            }
        }
        configurator.configure(context, beanFactory);
        if (needsRefresh) {
            if (context instanceof ReconfigurableContext<?>) {
                ((ReconfigurableContext<?>) context).refresh();
            }
        }
        final Map<String, ContextAware> contextAwareBeans = beanFactory.getBeansOfType(ContextAware.class, false,
                true);
        for (ContextAware bean : contextAwareBeans.values()) {
            final Class<?> contextType = ClassUtils.resolveTypeArgument(bean.getClass(), ContextAware.class);
            if (contextType.isAssignableFrom(context.getRegistryType())) {
                //noinspection unchecked
                bean.setContext(context);
            }
        }
        for (String beanName : context.getBeanNames()) {
            beanFactory.registerSingleton(context.getClass().getCanonicalName().concat(".").concat(beanName),
                    context.get(beanName));
        }
    }

}