com.avanza.astrix.spring.AstrixFrameworkBean.java Source code

Java tutorial

Introduction

Here is the source code for com.avanza.astrix.spring.AstrixFrameworkBean.java

Source

/*
 * 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 java.util.Objects;

import javax.annotation.PreDestroy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.AstrixBeanKey;
import com.avanza.astrix.beans.core.AstrixSettings;
import com.avanza.astrix.beans.publish.ApiProvider;
import com.avanza.astrix.beans.service.ObjectSerializerDefinition;
import com.avanza.astrix.beans.service.ServiceDefinition;
import com.avanza.astrix.config.DynamicConfig;
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.ServiceAdministrator;
import com.avanza.astrix.serviceunit.ServiceAdministratorImpl;
import com.avanza.astrix.serviceunit.ServiceAdministratorVersioningConfigurer;
import com.avanza.astrix.serviceunit.ExportedServiceBeanDefinition;
import com.avanza.astrix.serviceunit.ServiceExporter;
import com.avanza.astrix.serviceunit.ServiceRegistryExporter;

/**
 * 
 * @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 static final Logger log = LoggerFactory.getLogger(AstrixFrameworkBean.class);
    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 serviceExporterStarted = false;
    private ApplicationContext applicationContext;
    private final AstrixConfigurer configurer = new AstrixConfigurer();

    public AstrixFrameworkBean() {
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        astrixContext = createAsterixContext(getDynamicConfig(applicationContext));
        astrixContext.getInstance(AstrixSpringContext.class).setApplicationContext(applicationContext);
        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);
        beanFactory.addBeanPostProcessor(astrixContext.getInstance(AstrixBeanPostProcessor.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 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 provided, then the service exporting part of the framework
     * will be loaded with all required components to provide the services defined in
     * the api's provided by the given applicatinDescriptor.
     * 
     * @param serviceDescriptor
     */
    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 createAsterixContext(DynamicConfig optionalConfig) {
        configurer.setSettings(this.settings);
        if (optionalConfig != null) {
            configurer.setConfig(optionalConfig);
        }
        if (this.subsystem != null) {
            configurer.setSubsystem(this.subsystem);
        }
        AstrixApplicationContext astrixContext = (AstrixApplicationContext) configurer.configure();
        return astrixContext;
    }

    @Override
    public void onApplicationEvent(ApplicationContextEvent event) {
        if (event instanceof ContextRefreshedEvent && !serviceExporterStarted) {
            // Application initialization complete. Export astrix-services.
            exportAllProvidedServices();
            serviceExporterStarted = 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 void exportAllProvidedServices() {
        if (applicationDescriptor == null) {
            return; // current application exports no services
        }
        String applicationInstanceId = setupApplicationInstanceId();
        ServiceExporter serviceExporter = astrixContext.getInstance(ServiceExporter.class);

        serviceExporter.addServiceProvider(astrixContext.getInstance(ServiceAdministratorImpl.class));
        // TODO 
        ObjectSerializerDefinition serializer = ObjectSerializerDefinition.versionedService(1,
                ServiceAdministratorVersioningConfigurer.class);
        ServiceDefinition<ServiceAdministrator> serviceDefinition = new ServiceDefinition<>(
                ApiProvider.create("FrameworkServices"),
                AstrixBeanKey.create(ServiceAdministrator.class, applicationInstanceId), serializer, true);
        ExportedServiceBeanDefinition serviceAdminDefintion = new ExportedServiceBeanDefinition(
                AstrixBeanKey.create(ServiceAdministrator.class, applicationInstanceId), serviceDefinition, true, // isVersioned  
                true, // alwaysActive
                AstrixSettings.SERVICE_ADMINISTRATOR_COMPONENT.getFrom(this.astrixContext.getConfig()).get());
        serviceExporter.exportService(serviceAdminDefintion);

        serviceExporter.setServiceDescriptor(applicationDescriptor); // TODO This is a hack. Avoid setting serviceDescriptor explicitly here
        serviceExporter.exportProvidedServices();
        astrixContext.getInstance(ServiceRegistryExporter.class).startPublishServices();
    }

    private String setupApplicationInstanceId() {
        String applicationInstanceId = AstrixSettings.APPLICATION_INSTANCE_ID
                .getFrom(this.astrixContext.getConfig()).get();
        if (applicationInstanceId == null) {
            applicationInstanceId = this.applicationDescriptor.toString();
            configurer.set(AstrixSettings.APPLICATION_INSTANCE_ID, this.applicationDescriptor.toString());
            log.info(
                    "No applicationInstanceId set, using name of ApplicationDescriptor as applicationInstanceId: {}",
                    applicationInstanceId);
            Objects.requireNonNull(
                    AstrixSettings.APPLICATION_INSTANCE_ID.getFrom(this.astrixContext.getConfig()).get());
        } else {
            log.info("Current applicationInstanceId={}", applicationInstanceId);
        }
        return applicationInstanceId;
    }

    public void setConsumedAstrixBeans(Class<?>... consumedAstrixBeans) {
        setConsumedAstrixBeans(Arrays.asList(consumedAstrixBeans));
    }

    @Override
    public int getOrder() {
        // Run this class as late as possible
        return Ordered.LOWEST_PRECEDENCE;
    }

}