Early solution for SGF-248 concerning bootstrapping a Spring ApplicationContext inside a GemFire Server-based JVM process when starting the GemFire Server from Gfsh.

This commit is contained in:
John Blum
2014-01-23 19:30:16 -08:00
parent 217732b7f7
commit 8e57cc05a3
8 changed files with 683 additions and 0 deletions

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2010-2013 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.data.gemfire;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.wiring.BeanConfigurerSupport;
import org.springframework.beans.factory.wiring.BeanWiringInfo;
import org.springframework.beans.factory.wiring.BeanWiringInfoResolver;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.Declarable;
/**
* The LazyWiringDeclarableSupport class is an implementation of the GemFire Declarable interface that enables support
* for wiring GemFire components with Spring bean dependencies defined in the Spring context.
* <p/>
* @author John Blum
* @see org.springframework.context.ApplicationListener
* @see org.springframework.context.event.ContextRefreshedEvent
* @see com.gemstone.gemfire.cache.Declarable
* @since 1.3.4
*/
@SuppressWarnings("unused")
public abstract class LazyWiringDeclarableSupport implements ApplicationListener<ContextRefreshedEvent>, Declarable {
// The name of the template bean defined in the Spring context for wiring this Declarable instance.
private static final String BEAN_NAME_PARAMETER = "bean-name";
// atomic reference to the parameter passed by GemFire when this Declared was constructed
// and the init method was called.
private final AtomicReference<Properties> parametersReference = new AtomicReference<Properties>();
private volatile boolean initialized = false;
/**
* Constructs an instance of the LazyWiringDeclarableSupport class register with the
* SpringContextBootstrappingInitializer to receive notification when the Spring context is created and initialized
* by GemFire to that this Declarable component can be configured and properly initialized with any required Spring
* bean dependencies.
* <p/>
* @see org.springframework.data.gemfire.support.SpringContextBootstrappingInitializer
* #register(org.springframework.context.ApplicationListener)
*/
public LazyWiringDeclarableSupport() {
SpringContextBootstrappingInitializer.register(this);
}
/**
* Asserts that this Declarable object has been properly configured and initialized by the Spring container
* after GemFire has constructed this Declarable during startup. It is recommended that this method be called
* in any GemFire CacheCallback/Declarable object operational method (e.g. CacheLoader.load(..)) before use
* in order to ensure that this Declarable was properly constructed, configured and initialized.
* <p/>
* @throws IllegalStateException if the Declarable object has not been properly configured or initialized by the
* Spring container.
* @see #isInitialized()
*/
protected void assertInitialized() {
Assert.state(isInitialized(), String.format(
"This Declarable object (%1$s) has not ben properly configured and initialized!",
getClass().getName()));
}
/**
* Performs the actual configuration and initialization of this Declarable object before use. This method
* is triggered by an ApplicationEvent indicating that the Spring context has been created and refreshed.
* <p/>
* @param beanFactory the ConfigurableListableBeanFactory used to configure and initialize this Declarable GemFire
* component.
* @param parameters Properties instance containing the parameters from GemFire's configuration file
* (e.g. cache.xml) to configure this Declarable object.
* @throws IllegalArgumentException if the bean-name parameter was specified in GemFire configuration meta-data
* but no bean with the specified name could be found in the Spring context.
* @see org.springframework.beans.factory.wiring.BeanConfigurerSupport
* @see org.springframework.beans.factory.wiring.BeanWiringInfo
* @see org.springframework.beans.factory.wiring.BeanWiringInfoResolver
*/
protected void doInit(final ConfigurableListableBeanFactory beanFactory, final Properties parameters) {
BeanConfigurerSupport beanConfigurer = new BeanConfigurerSupport();
beanConfigurer.setBeanFactory(beanFactory);
final String templateBeanName = parameters.getProperty(BEAN_NAME_PARAMETER);
if (StringUtils.hasText(templateBeanName)) {
if (beanFactory.containsBean(templateBeanName)) {
beanConfigurer.setBeanWiringInfoResolver(new BeanWiringInfoResolver() {
@Override public BeanWiringInfo resolveWiringInfo(final Object beanInstance) {
return new BeanWiringInfo(templateBeanName);
}
});
}
else {
throw new IllegalArgumentException(String.format(
"No bean with name '%1$s' was found in the Spring context '%2$s'.", templateBeanName, beanFactory));
}
}
beanConfigurer.afterPropertiesSet();
beanConfigurer.configureBean(this);
beanConfigurer.destroy();
initialized = true;
}
/**
* Initialization method called by GemFire with the configured parameters once this Declarable object has been
* constructed during GemFire startup using an &lt;initalizer&gt;.
* <p/>
* @param parameters the configured parameters passed from the GemFire configuration (e.g. cache.xml) to this
* Declarable as a Properties instance.
* @throws IllegalStateException if the Declarable object's init method has already been invoked.
* @see #doInit(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.Properties)
* @see java.util.Properties
*/
@Override
public final void init(final Properties parameters) {
Assert.state(parametersReference.compareAndSet(null, parameters), String.format(
"This Declarable (%1$s) has already been initialized.", getClass().getName()));
}
/**
* Determines whether this Declarable object has been configured and initialized (i.e. the doInit method
* has been called) by the Spring container.
* <p/>
* @return a boolean value indicating whether this Declarable object has been configured and initialized by
* the Spring container.
* @see #doInit(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.Properties)
*/
protected boolean isInitialized() {
return initialized;
}
/**
* Event handler method called when GemFire has created and initialized (refreshed) the Spring ApplicationContext
* using the SpringContextBootstrappingInitializer Declarable class.
* <p/>
* @param event the ContextRefreshedEvent published by the Spring ApplicationContext after it is successfully
* created and initialized by GemFire.
* @throws IllegalStateException if the parameters have not been passed to this Declarable (i.e. GemFire has not
* called this Declarable object's init method yet, which is probably a bug and violates the lifecycle contract
* of Declarable GemFire objects).
* @throws IllegalArgumentException if the ApplicationContext is not an instance of ConfigurableApplicationContext.
*/
@Override
public final void onApplicationEvent(final ContextRefreshedEvent event) {
Properties parameters = parametersReference.get();
Assert.state(parameters != null, String.format(
"This Declarable object's (%1$s) init method has not been invoked!", getClass().getName()));
Assert.isTrue(event.getApplicationContext() instanceof ConfigurableApplicationContext,
String.format("The Spring ApplicationContext (%1$s) must be an instance of ConfigurableApplicationContext.",
ObjectUtils.nullSafeClassName(event.getApplicationContext())));
doInit(((ConfigurableApplicationContext) event.getApplicationContext()).getBeanFactory(), parameters);
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2010-2013 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.data.gemfire.support;
import java.util.Arrays;
import java.util.Properties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.Declarable;
/**
* The SpringContextBootstrappingInitializer class is a GemFire configuration initializer used to bootstrap a Spring
* ApplicationContext inside a GemFire Server JVM-based process. This enables a GemFire Cache Server resources to be
* mostly configured with Spring Data GemFire's XML namespace. The Cache itself is the only resource that cannot be
* configured and initialized in a Spring context since the initializer is not invoked until after GemFire creates
* and initializes the Cache for use.
* <p/>
* @author John Blum
* @see org.springframework.context.ConfigurableApplicationContext
* @see org.springframework.context.support.ClassPathXmlApplicationContext
* @see com.gemstone.gemfire.cache.Declarable
* @since 1.3.4
* @link http://pubs.vmware.com/vfabric53/topic/com.vmware.vfabric.gemfire.7.0/basic_config/the_cache/setting_cache_initializer.html
*/
@SuppressWarnings("unused")
public class SpringContextBootstrappingInitializer implements Declarable, ApplicationListener<ContextRefreshedEvent> {
public static final String CONTEXT_CONFIG_LOCATIONS_PARAMETER = "contextConfigLocations";
private static ConfigurableApplicationContext applicationContext;
// TODO consider whether I should register a TaskExecutor to perform the event notifications in a separate Thread???
private static ApplicationEventMulticaster eventNotifier = new SimpleApplicationEventMulticaster();
/**
* Gets a reference to the Spring ApplicationContext constructed, configured and initialized inside the GemFire
* Server-based JVM process.
* <p/>
* @return a reference to the Spring ApplicationContext bootstrapped by GemFire.
* @see org.springframework.context.ConfigurableApplicationContext
*/
public static synchronized ConfigurableApplicationContext getApplicationContext() {
Assert.state(applicationContext != null, "The Spring ApplicationContext has not been created!");
return applicationContext;
}
/**
* Registers a Spring ApplicationListener to be notified when the Spring ApplicationContext is created by GemFire
* when instantiating and initializing declared Initializers from the GemFire native configuration file
* (e.g. cache.xml).
* <p/>
* @param listener the ApplicationListener to register for ContextStartedEvents by this
* SpringContextBootstrappingInitializer.
* @see org.springframework.context.ApplicationListener
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
* #addApplicationListener(org.springframework.context.ApplicationListener)
*/
public static void register(ApplicationListener listener) {
eventNotifier.addApplicationListener(listener);
}
/**
* Gets the the ID of the Spring ApplicationContext in a null-safe manner.
* <p/>
* @param applicationContext the Spring ApplicationContext to retrieve the ID of.
* @return the ID of the given Spring ApplicationContext or null if the ApplicationContext reference is null.
* @see org.springframework.context.ApplicationContext#getId()
*/
protected static String nullSafeGetApplicationContextId(final ApplicationContext applicationContext) {
return (applicationContext != null ? applicationContext.getId() : null);
}
/**
* Initializes a Spring ApplicationContext with the given parameters from a GemFire Initializer in GemFire native
* configuration meta-data (e.g. cache.xml).
* <p/>
* @param parameters a Properties object containing the configuration parameters and settings defined in the
* GemFire cache.xml &gt;initializer/&lt; element.
* @see java.util.Properties
*/
@Override
public void init(final Properties parameters) {
String contextConfigLocations = parameters.getProperty(CONTEXT_CONFIG_LOCATIONS_PARAMETER);
Assert.hasText(contextConfigLocations, "The contextConfigLocations parameter is required.");
String[] configLocations = contextConfigLocations.split(",");
StringUtils.trimArrayElements(configLocations);
synchronized (SpringContextBootstrappingInitializer.class) {
Assert.state(applicationContext == null, String.format(
"A Spring application context with ID (%1$s) has already been created.",
nullSafeGetApplicationContextId(applicationContext)));
applicationContext = new ClassPathXmlApplicationContext(configLocations, false);
applicationContext.addApplicationListener(this);
applicationContext.registerShutdownHook();
applicationContext.refresh();
Assert.state(applicationContext.isActive(), String.format(
"The Spring application context has failed to be properly initialized with the following config files (%1$s)!",
Arrays.toString(configLocations)));
}
}
/**
* Gets notified when the Spring ApplicationContext gets created and refreshed by GemFire. The handler method
* proceeds in notifying any other GemFire components that need to be aware that the Spring ApplicationContext
* now exists and is ready for use, such as other Declarable GemFire objects requiring auto-wiring support, etc.
* <p/>
* @param event the ContextRefreshedEvent signaling that the Spring ApplicationContext has been created
* and refreshed by GemFire.
* @see org.springframework.context.event.ContextRefreshedEvent
* @see org.springframework.context.event.ApplicationEventMulticaster
* #multicastEvent(org.springframework.context.ApplicationEvent)
*/
@Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
eventNotifier.multicastEvent(event);
}
}