/*
* Copyright 2014 Avanza Bank AB
*
* 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 com.avanza.astrix.spring;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PreDestroy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStoppedEvent;
import org.springframework.core.Ordered;
import com.avanza.astrix.beans.core.AstrixBeanSettings.BeanSetting;
import com.avanza.astrix.beans.core.AstrixBeanKey;
import com.avanza.astrix.beans.core.AstrixSettings;
import com.avanza.astrix.config.DynamicConfig;
import com.avanza.astrix.config.Setting;
import com.avanza.astrix.context.Astrix;
import com.avanza.astrix.context.AstrixApplicationContext;
import com.avanza.astrix.context.AstrixConfigurer;
import com.avanza.astrix.context.AstrixContext;
import com.avanza.astrix.serviceunit.AstrixApplicationDescriptor;
import com.avanza.astrix.serviceunit.ServiceExporter;
/**
*
* @author Elias Lindholm (elilin)
*/
public class AstrixFrameworkBean implements BeanFactoryPostProcessor, ApplicationContextAware, ApplicationListener<ApplicationContextEvent>, Ordered {
/*
* A NOTE ON THE IMPLEMENTATION NOTE: This comment is outdated...
*
* IMPLEMENTATION NOTE - Astrix startup
*
* The startup procedure goes as follows:
*
* 1. BeanFactoryPostProcessor (BFPP)
* - The BFPP will register an instance for each consumedAstrixBean in the BeanFactory
* - The BFPP will also register an instance of AstrixSpingContext to act as bridge
* between spring and astrix.
*
* 2. BeanPostProcessor (BPP)
* - The BPP will investigate each spring-bean in the current application and search
* for @AstrixServiceExport annotated classes and register those with the ServiceRegistryExporterClient
*
* 3. ApplicationListener<ContexRefreshedEvent>
* - After each spring-bean have bean created and fully initialized this class will receive a call-back
* and start exporting services using the ServiceRegistryExporterClient.
*
*
* Note that this class (AstrixFrameworkBean) is the only class in the framework that will recieve spring-lifecylce events.
*
*/
private List<Class<?>> consumedAstrixBeans = new ArrayList<>();
private String subsystem;
private Map<String, String> settings = new HashMap<>();
private AstrixApplicationDescriptor applicationDescriptor;
private AstrixApplicationContext astrixContext;
private volatile boolean servicePublisherStarted = false;
private ApplicationContext applicationContext;
private final AstrixConfigurer configurer = new AstrixConfigurer();
public AstrixFrameworkBean() {
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
astrixContext = createAstrixContext(getDynamicConfig(applicationContext));
astrixContext.getInstance(AstrixSpringContext.class).setApplicationContext(applicationContext);
astrixContext.getInstance(AstrixSpringContext.class).setAstrixContext(astrixContext);
for (Class<?> consumedAstrixBean : this.consumedAstrixBeans) {
beanFactory.registerSingleton(consumedAstrixBean.getName(), astrixContext.getBean(consumedAstrixBean));
}
beanFactory.registerSingleton(AstrixSpringContext.class.getName(), astrixContext.getInstance(AstrixSpringContext.class));
beanFactory.registerSingleton(AstrixContext.class.getName(), astrixContext);
if (isServer()) {
beanFactory.addBeanPostProcessor(new AstrixBeanPostProcessor(astrixContext.getInstance(ServiceExporter.class)));
}
}
/**
* All consumedAstrixBeans will be created (see {@link Astrix#getBean(Class)} and registered in the spring ApplicationContext at
* startup. All consumedAstrixBeans will be available as autowiring candidates for other beans in the current spring ApplicationContext.<p>
*
* Its also possible to wire consumedAstrixBeans by reference. Each consumed Astrix-bean will be registered under the fully qualified
* class name of the given Astrix bean.<p>
*
* <pre>
* Example:
*
* setConsumedAstrixBeans(asList(se.avanza.customer.CustomerService.class));
*
* Creates and registers an Astrix bean of type CustomerService in the current ApplicationContext. The name
* of the CustomerService bean in the spring ApplicationContext will be "se.avanza.customer.CustomerService", which
* can be used in a ApplicationContext-xml file to explicitly wire an instance of CustomerService into another
* spring bean.
* </pre>
*
* @param consumedAstrixBeans
*/
public void setConsumedAstrixBeans(List<Class<? extends Object>> consumedAstrixBeans) {
this.consumedAstrixBeans = consumedAstrixBeans;
}
public void setSettings(Map<String, String> settings) {
this.settings.putAll(settings);
}
public <T> void set(Setting<T> setting, T value) {
this.configurer.set(setting, value);
}
public <T> void set(BeanSetting<T> setting, AstrixBeanKey<?> beanKey, T value) {
this.configurer.set(setting, beanKey, value);
}
public Map<String, String> getSettings() {
return settings;
}
@PreDestroy
public void destroy() {
this.astrixContext.destroy();
}
/**
* If a service descriptor is provided, then the service exporting part of the framework
* will be loaded with all required components for the given serviceDescriptor.
*
* @param serviceDescriptor
* @deprecated - replaced by {@link AstrixFrameworkBean#setApplicationDescriptor(Class)}
*/
@Deprecated
public void setServiceDescriptor(Class<?> serviceDescriptorHolder) {
this.applicationDescriptor = AstrixApplicationDescriptor.create(serviceDescriptorHolder);
}
/**
* If an application descriptor is set, then the service exporting part of the framework
* will be loaded with all required components to provide the services defined in
* the api's referred to by the given applicatinDescriptor.
*
* @param applicationDescriptorHolder
*/
public void setApplicationDescriptor(Class<?> applicationDescriptorHolder) {
this.applicationDescriptor = AstrixApplicationDescriptor.create(applicationDescriptorHolder);
}
/**
* All services consumed by the current application. Each type will be created and available
* for autowiring in the current applicationContext.
*
* Implementation note: This is only application defined usages of Astrix beans. Any Astrix-beans
* used internally by the service-framework will not be included in this set.
*
* @return
*/
public List<Class<?>> getConsumedAstrixBeans() {
return consumedAstrixBeans;
}
public void setSubsystem(String subsystem) {
this.subsystem = subsystem;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private DynamicConfig getDynamicConfig(ApplicationContext applicationContext) {
Collection<DynamicConfig> dynamicConfigs = applicationContext.getBeansOfType(DynamicConfig.class).values();
if (dynamicConfigs.isEmpty()) {
return null;
}
if (dynamicConfigs.size() == 1) {
return dynamicConfigs.iterator().next();
}
throw new IllegalArgumentException("Multiple DynamicConfig instances found in ApplicationContext");
}
private AstrixApplicationContext createAstrixContext(DynamicConfig optionalConfig) {
configurer.setSettings(this.settings);
if (optionalConfig != null) {
configurer.setConfig(optionalConfig);
}
if (this.subsystem != null) {
configurer.setSubsystem(this.subsystem);
}
if (this.applicationDescriptor != null) {
configurer.setApplicationDescriptor(applicationDescriptor);
}
return (AstrixApplicationContext) configurer.configure();
}
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent && !servicePublisherStarted) {
// Application initialization complete. Export astrix-services.
if (isServer()) {
this.astrixContext.startServicePublisher();
}
servicePublisherStarted = true;
} else if (event instanceof ContextClosedEvent || event instanceof ContextStoppedEvent) {
/*
* What's the difference between the "stopped" and "closed" event? In our embedded
* integration tests we only receive ContextClosedEvent
*/
destroyAstrixContext();
}
}
private void destroyAstrixContext() {
this.astrixContext.destroy();
}
private boolean isServer() {
return applicationDescriptor != null;
}
public void setConsumedAstrixBeans(Class<?>... consumedAstrixBeans) {
setConsumedAstrixBeans(Arrays.asList(consumedAstrixBeans));
}
@Override
public int getOrder() {
// Run this class as late as possible
return Ordered.LOWEST_PRECEDENCE;
}
}