org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer.java

Source

/*
 * Copyright 2010-2019 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
 *
 *       https://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.data.gemfire.support;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.geode.cache.Cache;
import org.apache.geode.cache.Declarable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.data.gemfire.util.CacheUtils;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * The {@link SpringContextBootstrappingInitializer} class is a Pivotal GemFire configuration initializer used to bootstrap
 * a Spring {@link ApplicationContext} inside a Pivotal GemFire Server JVM-based process.  This enables a Pivotal GemFire Server
 * resource to be mostly configured with SDG's configuration meta-data.  The Pivotal GemFire {@link Cache}
 * itself is the only resource that cannot be configured and initialized in a Spring context since the initializer
 * is not invoked until after Pivotal GemFire creates and initializes the Pivotal GemFire {@link Cache} for use.
 *
 * @author John Blum
 * @see org.springframework.context.ApplicationContext
 * @see org.springframework.context.ApplicationListener
 * @see org.springframework.context.ConfigurableApplicationContext
 * @see org.springframework.context.annotation.AnnotationConfigApplicationContext
 * @see org.springframework.context.event.ApplicationContextEvent
 * @see org.springframework.context.event.ApplicationEventMulticaster
 * @see org.springframework.context.support.ClassPathXmlApplicationContext
 * @see org.springframework.core.io.DefaultResourceLoader
 * @see org.apache.geode.cache.Cache
 * @see org.apache.geode.cache.Declarable
 * @link https://gemfire.docs.pivotal.io/latest/userguide/index.html#basic_config/the_cache/setting_cache_initializer.html
 * @link https://jira.springsource.org/browse/SGF-248
 * @since 1.4.0
 */
@SuppressWarnings("unused")
public class SpringContextBootstrappingInitializer
        implements Declarable, ApplicationListener<ApplicationContextEvent> {

    public static final String BASE_PACKAGES_PARAMETER = "basePackages";
    public static final String CONTEXT_CONFIG_LOCATIONS_PARAMETER = "contextConfigLocations";

    protected static final String CHARS_TO_DELETE = " \n\t";
    protected static final String COMMA_DELIMITER = ",";

    private static final ApplicationEventMulticaster applicationEventNotifier = new SimpleApplicationEventMulticaster();

    private static final AtomicReference<ClassLoader> beanClassLoaderReference = new AtomicReference<>(null);

    static volatile ConfigurableApplicationContext applicationContext;

    static volatile ContextRefreshedEvent contextRefreshedEvent;

    private static final List<Class<?>> registeredAnnotatedClasses = new CopyOnWriteArrayList<>();

    protected final Logger logger = initLogger();

    /**
     * Gets a reference to the Spring ApplicationContext constructed, configured and initialized inside the GemFire
     * Server-based JVM process.
     *
     * @return a reference to the Spring ApplicationContext bootstrapped by GemFire.
     * @see org.springframework.context.ConfigurableApplicationContext
     */
    public static synchronized ConfigurableApplicationContext getApplicationContext() {

        Assert.state(applicationContext != null,
                "A Spring ApplicationContext was not configured and initialized properly");

        return applicationContext;
    }

    /**
     * Sets the ClassLoader used by the Spring ApplicationContext, created by this Pivotal GemFire Initializer, when creating
     * bean definition classes.
     *
     * @param beanClassLoader the ClassLoader used by the Spring ApplicationContext to load bean definition classes.
     * @throws java.lang.IllegalStateException if the Spring ApplicationContext has already been created
     * and initialized.
     * @see java.lang.ClassLoader
     */
    public static void setBeanClassLoader(ClassLoader beanClassLoader) {

        if (isApplicationContextInitializable()) {
            beanClassLoaderReference.set(beanClassLoader);
        } else {
            throw new IllegalStateException("A Spring ApplicationContext has already been initialized");
        }
    }

    /**
     * Notifies any Spring ApplicationListeners of a current and existing ContextRefreshedEvent if the
     * ApplicationContext had been previously created, initialized and refreshed before any ApplicationListeners
     * interested in ContextRefreshedEvents were registered so that application components (such as the
     * Pivotal GemFire CacheLoaders extending LazyWiringDeclarableSupport objects) registered late, requiring configuration
     * (auto-wiring), also get notified and wired accordingly.
     *
     * @param listener a Spring ApplicationListener requiring notification of any ContextRefreshedEvents after the
     * ApplicationContext has already been created, initialized and/or refreshed.
     * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
     * @see org.springframework.context.event.ContextRefreshedEvent
     */
    protected static void notifyOnExistingContextRefreshedEvent(
            ApplicationListener<ContextRefreshedEvent> listener) {

        synchronized (applicationEventNotifier) {
            if (contextRefreshedEvent != null) {
                listener.onApplicationEvent(contextRefreshedEvent);
            }
        }
    }

    /**
     * Registers a Spring ApplicationListener to be notified when the Spring ApplicationContext is created by GemFire
     * when instantiating and initializing Declarables declared inside the &lt;initializer&gt; block inside Pivotal GemFire's
     * cache.xml file.
     *
     * @param <T> the Class type of the Spring ApplicationListener.
     * @param listener the ApplicationListener to register for ContextRefreshedEvents multi-casted by this
     * SpringContextBootstrappingInitializer.
     * @return the reference to the ApplicationListener for method call chaining purposes.
     * @see #notifyOnExistingContextRefreshedEvent(org.springframework.context.ApplicationListener)
     * @see #unregister(org.springframework.context.ApplicationListener)
     * @see org.springframework.context.ApplicationListener
     * @see org.springframework.context.event.ContextRefreshedEvent
     * @see org.springframework.context.event.SimpleApplicationEventMulticaster
     *    #addApplicationListener(org.springframework.context.ApplicationListener)
     */
    public static <T extends ApplicationListener<ContextRefreshedEvent>> T register(T listener) {

        synchronized (applicationEventNotifier) {
            applicationEventNotifier.addApplicationListener(listener);
            notifyOnExistingContextRefreshedEvent(listener);
        }

        return listener;
    }

    /**
     * Registers the specified Spring annotated POJO class, which will be used to configure and initialize
     * the Spring ApplicationContext.
     *
     * @param annotatedClass the Spring annotated (@Configuration) POJO class to register.
     * @return a boolean value indicating whether the Spring annotated POJO class was successfully registered.
     * @see #unregister(Class)
     */
    public static boolean register(Class<?> annotatedClass) {

        Assert.notNull(annotatedClass, "The Spring annotated class to register must not be null");

        return registeredAnnotatedClasses.add(annotatedClass);
    }

    /**
     * Un-registers the Spring ApplicationListener from this SpringContextBootstrappingInitializer in order to stop
     * receiving ApplicationEvents on Spring context refreshes.
     *
     * @param <T> the Class type of the Spring ApplicationListener.
     * @param listener the ApplicationListener to unregister from receiving ContextRefreshedEvents by this
     * SpringContextBootstrappingInitializer.
     * @return the reference to the ApplicationListener for method call chaining purposes.
     * @see #register(org.springframework.context.ApplicationListener)
     * @see org.springframework.context.ApplicationListener
     * @see org.springframework.context.event.ContextRefreshedEvent
     * @see org.springframework.context.event.SimpleApplicationEventMulticaster
     *    #removeApplicationListener(org.springframework.context.ApplicationListener)
     */
    public static <T extends ApplicationListener<ContextRefreshedEvent>> T unregister(T listener) {

        synchronized (applicationEventNotifier) {
            applicationEventNotifier.removeApplicationListener(listener);
        }

        return listener;
    }

    /**
     * Un-registers the specified Spring annotated POJO class used to configure and initialize
     * the Spring ApplicationContext.
     *
     * @param annotatedClass the Spring annotated (@Configuration) POJO class to unregister.
     * @return a boolean value indicating whether the Spring annotated POJO class was successfully un-registered.
     * @see #register(Class)
     */
    public static boolean unregister(Class<?> annotatedClass) {
        return registeredAnnotatedClasses.remove(annotatedClass);
    }

    /**
     * Initialization method for the logger used to log important messages from this initializer.
     *
     * @return a Apache Commons Log used to log messages from this initializer
     * @see org.apache.commons.logging.LogFactory#getLog(Class)
     * @see org.apache.commons.logging.Log
     */
    protected Logger initLogger() {
        return LoggerFactory.getLogger(getClass());
    }

    private boolean isConfigurable(Collection<Class<?>> annotatedClasses, String[] basePackages,
            String[] contextConfigLocations) {

        return !(CollectionUtils.isEmpty(annotatedClasses) && ObjectUtils.isEmpty(basePackages)
                && ObjectUtils.isEmpty(contextConfigLocations));
    }

    /**
     * Creates (constructs and configures) an instance of the ConfigurableApplicationContext based on either the
     * specified base packages containing @Configuration, @Component or JSR 330 annotated classes to scan, or the
     * specified locations of context configuration meta-data files.  The created ConfigurableApplicationContext
     * is not automatically "refreshed" and therefore must be "refreshed" by the caller manually.
     *
     * When basePackages are specified, an instance of AnnotationConfigApplicationContext is constructed and a scan
     * is performed; otherwise an instance of the ClassPathXmlApplicationContext is initialized with the
     * configLocations.  This method prefers the ClassPathXmlApplicationContext to the
     * AnnotationConfigApplicationContext when both basePackages and configLocations are specified.
     *
     * @param basePackages the base packages to scan for application @Components and @Configuration classes.
     * @param configLocations a String array indicating the locations of the context configuration meta-data files
     * used to configure the ClassPathXmlApplicationContext instance.
     * @return an instance of ConfigurableApplicationContext configured and initialized with either configLocations
     * or the basePackages when configLocations is unspecified.  Note, the "refresh" method must be called manually
     * before using the context.
     * @throws IllegalArgumentException if both the basePackages and configLocation parameter arguments
     * are null or empty.
     * @see #createApplicationContext(String[])
     * @see org.springframework.context.annotation.AnnotationConfigApplicationContext
     * @see org.springframework.context.annotation.AnnotationConfigApplicationContext#scan(String...)
     * @see org.springframework.context.support.ClassPathXmlApplicationContext
     */
    protected ConfigurableApplicationContext createApplicationContext(String[] basePackages,
            String[] configLocations) {

        String message = "'AnnotatedClasses', 'basePackages' or 'configLocations' must be specified"
                + " in order to construct and configure an instance of the ConfigurableApplicationContext";

        Assert.isTrue(isConfigurable(registeredAnnotatedClasses, basePackages, configLocations), message);

        Class<?>[] annotatedClasses = registeredAnnotatedClasses.toArray(new Class<?>[0]);

        ConfigurableApplicationContext applicationContext = createApplicationContext(configLocations);

        return scanBasePackages(registerAnnotatedClasses(applicationContext, annotatedClasses), basePackages);
    }

    ConfigurableApplicationContext createApplicationContext(String[] configLocations) {

        return ObjectUtils.isEmpty(configLocations) ? new AnnotationConfigApplicationContext()
                : new ClassPathXmlApplicationContext(configLocations, false);
    }

    /**
     * Initializes the given ApplicationContext by registering this SpringContextBootstrappingInitializer as an
     * ApplicationListener and registering a runtime shutdown hook.
     *
     * @param applicationContext the ConfigurableApplicationContext to initialize.
     * @return the initialized ApplicationContext.
     * @see org.springframework.context.ConfigurableApplicationContext
     * @see org.springframework.context.ConfigurableApplicationContext#addApplicationListener(org.springframework.context.ApplicationListener)
     * @see org.springframework.context.ConfigurableApplicationContext#registerShutdownHook()
     * @throws java.lang.IllegalArgumentException if the ApplicationContext reference is null!
     */
    protected ConfigurableApplicationContext initApplicationContext(
            ConfigurableApplicationContext applicationContext) {

        Assert.notNull(applicationContext, "ConfigurableApplicationContext must not be null");

        applicationContext.addApplicationListener(this);
        applicationContext.registerShutdownHook();

        return setClassLoader(applicationContext);
    }

    /**
     * Refreshes the given ApplicationContext making the context active.
     *
     * @param applicationContext the ConfigurableApplicationContext to refresh.
     * @return the refreshed ApplicationContext.
     * @see org.springframework.context.ConfigurableApplicationContext
     * @see org.springframework.context.ConfigurableApplicationContext#refresh()
     * @throws java.lang.IllegalArgumentException if the ApplicationContext reference is null!
     */
    protected ConfigurableApplicationContext refreshApplicationContext(
            ConfigurableApplicationContext applicationContext) {

        Assert.notNull(applicationContext, "ConfigurableApplicationContext must not be null");

        applicationContext.refresh();

        return applicationContext;
    }

    /**
     * Registers the given Spring annotated (@Configuration) POJO classes with the specified
     * AnnotationConfigApplicationContext.
     *
     * @param applicationContext the AnnotationConfigApplicationContext used to register the Spring annotated,
     * POJO classes.
     * @param annotatedClasses a Class array of Spring annotated (@Configuration) classes used to configure
     * and initialize the Spring AnnotationConfigApplicationContext.
     * @return the given AnnotationConfigApplicationContext.
     * @see org.springframework.context.annotation.AnnotationConfigApplicationContext#register(Class[])
     */
    ConfigurableApplicationContext registerAnnotatedClasses(ConfigurableApplicationContext applicationContext,
            Class<?>[] annotatedClasses) {

        return applicationContext instanceof AnnotationConfigApplicationContext
                && !ObjectUtils.isEmpty(annotatedClasses) ? doRegister(applicationContext, annotatedClasses)
                        : applicationContext;
    }

    ConfigurableApplicationContext doRegister(ConfigurableApplicationContext applicationContext,
            Class<?>[] annotatedClasses) {

        ((AnnotationConfigApplicationContext) applicationContext).register(annotatedClasses);

        return applicationContext;
    }

    /**
     * Configures classpath component scanning using the specified base packages on the specified
     * AnnotationConfigApplicationContext.
     *
     * @param applicationContext the AnnotationConfigApplicationContext to setup with classpath component scanning
     * using the specified base packages.
     * @param basePackages an array of Strings indicating the base packages to use in the classpath component scan.
     * @return the given AnnotationConfigApplicationContext.
     * @see org.springframework.context.annotation.AnnotationConfigApplicationContext#scan(String...)
     */
    ConfigurableApplicationContext scanBasePackages(ConfigurableApplicationContext applicationContext,
            String[] basePackages) {

        return applicationContext instanceof AnnotationConfigApplicationContext
                && !ObjectUtils.isEmpty(basePackages) ? doScan(applicationContext, basePackages)
                        : applicationContext;
    }

    ConfigurableApplicationContext doScan(ConfigurableApplicationContext applicationContext,
            String[] basePackages) {

        ((AnnotationConfigApplicationContext) applicationContext).scan(basePackages);

        return applicationContext;
    }

    /**
     * Sets the ClassLoader used to load bean definition classes on the Spring ApplicationContext.
     *
     * @param applicationContext the Spring ApplicationContext in which to configure the ClassLoader.
     * @return the given Spring ApplicationContext.
     * @see org.springframework.core.io.DefaultResourceLoader#setClassLoader(ClassLoader)
     * @see java.lang.ClassLoader
     */
    ConfigurableApplicationContext setClassLoader(ConfigurableApplicationContext applicationContext) {

        ClassLoader beanClassLoader = beanClassLoaderReference.get();

        if (applicationContext instanceof DefaultResourceLoader && beanClassLoader != null) {
            ((DefaultResourceLoader) applicationContext).setClassLoader(beanClassLoader);
        }

        return applicationContext;
    }

    @Override
    @SuppressWarnings("deprecation")
    public void init(Properties parameters) {
        init(CacheUtils.getCache(), parameters);
    }

    /**
     * Initializes a Spring {@link ApplicationContext} with the given parameters specified with an Apache Geode
     * or Pivotal GemFire &lt;initializer&gt; block in {@literal cache.xml}.
     *
     * @param parameters {@link Properties} object containing the configuration parameters and settings defined in the
     * Apache Geode/Pivotal GemFire {@literal cache.xml} &lt;initializer&gt; block for the declared
     * {@link SpringContextBootstrappingInitializer} Apache Geode/Pivotal GemFire {@link Declarable} object.
     * @param cache reference to the peer {@link Cache}.
     * @throws org.springframework.context.ApplicationContextException if the Spring {@link ApplicationContext}
     * could not be successfully constructed, configured and initialized.
     * @see #createApplicationContext(String[], String[])
     * @see #initApplicationContext(org.springframework.context.ConfigurableApplicationContext)
     * @see #refreshApplicationContext(org.springframework.context.ConfigurableApplicationContext)
     * @see java.util.Properties
     */
    public void init(Cache cache, Properties parameters) {

        try {
            synchronized (SpringContextBootstrappingInitializer.class) {
                if (isApplicationContextInitializable()) {

                    String basePackages = parameters.getProperty(BASE_PACKAGES_PARAMETER);
                    String contextConfigLocations = parameters.getProperty(CONTEXT_CONFIG_LOCATIONS_PARAMETER);

                    String[] basePackagesArray = StringUtils.delimitedListToStringArray(
                            StringUtils.trimWhitespace(basePackages), COMMA_DELIMITER, CHARS_TO_DELETE);

                    String[] contextConfigLocationsArray = StringUtils.delimitedListToStringArray(
                            StringUtils.trimWhitespace(contextConfigLocations), COMMA_DELIMITER, CHARS_TO_DELETE);

                    ConfigurableApplicationContext localApplicationContext = refreshApplicationContext(
                            initApplicationContext(
                                    createApplicationContext(basePackagesArray, contextConfigLocationsArray)));

                    Assert.state(localApplicationContext.isRunning(), String.format(
                            "The Spring ApplicationContext (%1$s) failed to be properly initialized with the context config files (%2$s) or base packages (%3$s)!",
                            nullSafeGetApplicationContextId(localApplicationContext),
                            Arrays.toString(contextConfigLocationsArray), Arrays.toString(basePackagesArray)));

                    applicationContext = localApplicationContext;
                }
            }
        } catch (Throwable cause) {
            String message = "Failed to bootstrap the Spring ApplicationContext";
            logger.error(message, cause);
            throw new ApplicationContextException(message, cause);
        }
    }

    private static boolean isApplicationContextInitializable() {
        return applicationContext == null || !applicationContext.isActive();
    }

    /**
     * Null-safe operation used to get the ID of the Spring ApplicationContext.
     *
     * @param applicationContext the Spring ApplicationContext from which to get the ID.
     * @return the ID of the given Spring ApplicationContext or null if the ApplicationContext reference is null.
     * @see org.springframework.context.ApplicationContext#getId()
     */
    String nullSafeGetApplicationContextId(ApplicationContext applicationContext) {

        return applicationContext != null ? applicationContext.getId() : null;
    }

    /**
     * Gets notified when the Spring ApplicationContext gets created and refreshed by GemFire, once the
     * &lt;initializer&gt; block is processed and the SpringContextBootstrappingInitializer Declarable component
     * is initialized.  This handler method proceeds in notifying any other Pivotal GemFire components that need to be aware
     * that the Spring ApplicationContext now exists and is ready for use, such as other Declarable Pivotal GemFire objects
     * requiring auto-wiring support, etc.
     *
     * In addition, this method handles the ContextClosedEvent by removing the ApplicationContext reference.
     *
     * @param event the ApplicationContextEvent signaling that the Spring ApplicationContext has been created
     * and refreshed by GemFire, or closed when the JVM process exits.
     * @see org.springframework.context.event.ContextClosedEvent
     * @see org.springframework.context.event.ContextRefreshedEvent
     * @see org.springframework.context.event.ApplicationEventMulticaster
     *  #multicastEvent(org.springframework.context.ApplicationEvent)
     */
    @Override
    public void onApplicationEvent(ApplicationContextEvent event) {

        if (event instanceof ContextRefreshedEvent) {
            synchronized (applicationEventNotifier) {
                contextRefreshedEvent = (ContextRefreshedEvent) event;
                applicationEventNotifier.multicastEvent(event);
            }
        } else if (event instanceof ContextClosedEvent) {
            synchronized (applicationEventNotifier) {
                contextRefreshedEvent = null;
            }
        }
    }
}