/*
* 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.context;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.stream.Stream;
import com.avanza.astrix.beans.api.ApiProviderBeanPublisherModule;
import com.avanza.astrix.beans.config.AstrixConfig;
import com.avanza.astrix.beans.config.AstrixConfigModule;
import com.avanza.astrix.beans.configdiscovery.ConfigDiscoveryModule;
import com.avanza.astrix.beans.core.AstrixBeanKey;
import com.avanza.astrix.beans.core.AstrixBeanSettings.BeanSetting;
import com.avanza.astrix.beans.core.AstrixBeanSettings.BooleanBeanSetting;
import com.avanza.astrix.beans.core.AstrixBeanSettings.IntBeanSetting;
import com.avanza.astrix.beans.core.AstrixBeanSettings.LongBeanSetting;
import com.avanza.astrix.beans.core.AstrixBeansCoreModule;
import com.avanza.astrix.beans.core.AstrixConfigAware;
import com.avanza.astrix.beans.core.AstrixSettings;
import com.avanza.astrix.beans.factory.BeanFactoryModule;
import com.avanza.astrix.beans.factory.StandardFactoryBean;
import com.avanza.astrix.beans.ft.*;
import com.avanza.astrix.beans.publish.*;
import com.avanza.astrix.beans.registry.AstrixServiceRegistryLibraryProvider;
import com.avanza.astrix.beans.registry.AstrixServiceRegistryServiceProvider;
import com.avanza.astrix.beans.registry.ServiceRegistryDiscoveryModule;
import com.avanza.astrix.beans.service.DirectComponentModule;
import com.avanza.astrix.beans.service.ServiceModule;
import com.avanza.astrix.config.*;
import com.avanza.astrix.context.mbeans.AstrixMBeanModule;
import com.avanza.astrix.context.mbeans.MBeanServerFacade;
import com.avanza.astrix.context.mbeans.PlatformMBeanServer;
import com.avanza.astrix.context.metrics.DefaultMetricSpi;
import com.avanza.astrix.context.metrics.MetricsModule;
import com.avanza.astrix.context.metrics.MetricsSpi;
import com.avanza.astrix.modules.*;
import com.avanza.astrix.provider.core.AstrixApiProvider;
import com.avanza.astrix.provider.core.AstrixExcludedByProfile;
import com.avanza.astrix.provider.core.AstrixIncludedByProfile;
import com.avanza.astrix.serviceunit.AstrixApplicationDescriptor;
import com.avanza.astrix.serviceunit.ServiceUnitModule;
import com.avanza.astrix.serviceunit.SystemServiceApiProvider;
import com.avanza.astrix.versioning.core.ObjectSerializerModule;
import com.avanza.astrix.versioning.jackson2.Jackson2SerializerModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Used to configure and create an {@link AstrixContext}. <p>
*
* @author Elias Lindholm (elilin)
*
*/
public class AstrixConfigurer {
private static final String CLASSPATH_OVERRIDE_SETTINGS = "META-INF/astrix/settings.properties";
private static final Logger log = LoggerFactory.getLogger(AstrixConfigurer.class);
private ApiProviders astrixApiProviders;
private final Collection<StandardFactoryBean<?>> standaloneFactories = new LinkedList<>();
private final List<Module> customModules = new ArrayList<>();
private final Map<Class<?>, StrategyProvider<?>> strategyProviderByType = new HashMap<>();
private final MapConfigSource settings = new MapConfigSource();
private DynamicConfig customConfig = null;
private final DynamicConfig wellKnownConfigSources = DynamicConfig.create(new SystemPropertiesConfigSource(), settings, PropertiesConfigSource.optionalClasspathPropertiesFile(CLASSPATH_OVERRIDE_SETTINGS));
private final Set<String> activeProfiles = new HashSet<>();
private AstrixApplicationDescriptor applicationDescriptor;
public AstrixConfigurer() {
}
public void setApplicationDescriptor(AstrixApplicationDescriptor applicationDescriptor) {
this.applicationDescriptor = applicationDescriptor;
}
/**
* Creates an AstrixContext instance using the current configuration. <p>
*
* @return
*/
public AstrixContext configure() {
DynamicConfig config = createDynamicConfig();
ModulesConfigurer modulesConfigurer = new ModulesConfigurer();
modulesConfigurer.registerDefault(StrategyProvider.create(HystrixCommandNamingStrategy.class, DefaultHystrixCommandNamingStrategy.class));
modulesConfigurer.registerDefault(StrategyProvider.create(BeanFaultToleranceFactorySpi.class, NoFaultTolerance.class));
modulesConfigurer.registerDefault(StrategyProvider.create(MetricsSpi.class, DefaultMetricSpi.class));
modulesConfigurer.registerDefault(StrategyProvider.create(MBeanServerFacade.class, PlatformMBeanServer.class, context -> context.importType(AstrixConfig.class)));
for (Module plugin : customModules) {
modulesConfigurer.register(plugin);
}
loadAstrixContextPlugins(modulesConfigurer);
for (StrategyProvider<?> strategyProvider : this.strategyProviderByType.values()) {
modulesConfigurer.register(strategyProvider);
}
modulesConfigurer.register(new AstrixConfigModule(config, this.settings));
modulesConfigurer.register(new DirectComponentModule());
modulesConfigurer.register(new AstrixBeansCoreModule());
modulesConfigurer.register(new MetricsModule());
modulesConfigurer.register(new AstrixMBeanModule());
modulesConfigurer.register(new ServiceRegistryDiscoveryModule());
modulesConfigurer.register(new ConfigDiscoveryModule());
modulesConfigurer.register(new BeansPublishModule());
modulesConfigurer.register(new ServiceModule());
modulesConfigurer.register(new ObjectSerializerModule());
modulesConfigurer.register(new Jackson2SerializerModule());
modulesConfigurer.register(new ApiProviderBeanPublisherModule());
modulesConfigurer.register(new FaultToleranceModule());
modulesConfigurer.register(new BeanFactoryModule());
if (this.applicationDescriptor != null) {
// Init server parts
setupApplicationInstanceId(config);
modulesConfigurer.register(new ServiceUnitModule(this.applicationDescriptor));
}
modulesConfigurer.registerBeanPostProcessor(new AstrixAwareInjector(config));
Modules modules = modulesConfigurer.configure();
final AstrixContextImpl context = new AstrixContextImpl(modules, this.applicationDescriptor);
Stream<ApiProviderClass> systemApis =
Stream.of(AstrixServiceRegistryServiceProvider.class, AstrixServiceRegistryLibraryProvider.class, SystemServiceApiProvider.class)
.map(ApiProviderClass::create);
Stream.concat(systemApis, getApiProviders(modules, config))
.filter(this::isActive)
.distinct()
.forEach(context::register);
// TODO: Merge with FilteredApiProviders and create module
for (StandardFactoryBean<?> beanFactory : standaloneFactories) {
log.debug("Registering standalone factory: bean={}", beanFactory.getBeanKey());
context.registerBeanFactory(beanFactory);
}
return context;
}
private void setupApplicationInstanceId(DynamicConfig config) {
String applicationInstanceId = AstrixSettings.APPLICATION_INSTANCE_ID.getFrom(config).get();
if (applicationInstanceId == null) {
applicationInstanceId = this.applicationDescriptor.toString();
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(config).get());
} else {
log.info("Current applicationInstanceId={}", applicationInstanceId);
}
}
private void loadAstrixContextPlugins(final ModulesConfigurer modulesConfigurer) {
Iterator<AstrixContextPlugin> contextPlugins = ServiceLoader.load(AstrixContextPlugin.class).iterator();
while (contextPlugins.hasNext()) {
AstrixContextPlugin contextPlugin = contextPlugins.next();
log.debug("Registering AstrixContextPlugin: astrixContextPlugin={}", contextPlugin.getClass().getName());
contextPlugin.registerStrategies(new AstrixStrategiesConfig() {
@Override
public <T> void registerDefaultStrategy(Class<T> strategyType, Class<? extends T> strategyProvider) {
modulesConfigurer.registerDefault(StrategyProvider.create(strategyType, strategyProvider));
}
@Override
public <T> void registerStrategy(Class<T> strategyType,
Class<? extends T> strategyImpl) {
modulesConfigurer.register(StrategyProvider.create(strategyType, strategyImpl));
}
@Override
public <T> void registerStrategy(Class<T> strategyType,
Class<? extends T> strategyImpl,
StrategyContextPreparer contextPreparer) {
modulesConfigurer.register(StrategyProvider.create(strategyType, strategyImpl, contextPreparer));
}
});
modulesConfigurer.register(contextPlugin);
}
}
private DynamicConfig createDynamicConfig() {
if (customConfig != null) {
return DynamicConfig.merged(customConfig, wellKnownConfigSources);
}
String dynamicConfigFactoryClass = AstrixSettings.DYNAMIC_CONFIG_FACTORY.getFrom(wellKnownConfigSources).get();
if (dynamicConfigFactoryClass != null) {
AstrixDynamicConfigFactory dynamicConfigFactory = initFactory(dynamicConfigFactoryClass);
DynamicConfig config = dynamicConfigFactory.create();
return DynamicConfig.merged(config, wellKnownConfigSources);
}
return wellKnownConfigSources;
}
private AstrixDynamicConfigFactory initFactory(String dynamicConfigFactoryClass) {
try {
return (AstrixDynamicConfigFactory) Class.forName(dynamicConfigFactoryClass).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new RuntimeException("Failed to init AstrixDynamicConfigFactoryClass: " + dynamicConfigFactoryClass, e);
}
}
/*
* Allows api's published using astrix (using @AstrixApiProvider) to have the
* DynamicConfig instance associated with the current AstrixContext injected
*/
private final class AstrixAwareInjector implements ModuleInstancePostProcessor {
private final DynamicConfig config;
public AstrixAwareInjector(DynamicConfig config) {
this.config = config;
}
@Override
public void postProcess(Object bean) {
if (bean instanceof AstrixConfigAware) {
AstrixConfigAware.class.cast(bean).setConfig(config); // TODO: config
}
}
}
private boolean isActive(ApiProviderClass providerClass) {
if (providerClass.isAnnotationPresent(AstrixIncludedByProfile.class)) {
AstrixIncludedByProfile activatedBy = providerClass.getAnnotation(AstrixIncludedByProfile.class);
if (!this.activeProfiles.contains(activatedBy.value())) {
log.debug("Rejecting provider, required profile not active. profile={} provider={}", activatedBy.value(), providerClass.getProviderClassName());
return false;
}
}
if (providerClass.isAnnotationPresent(AstrixExcludedByProfile.class)) {
AstrixExcludedByProfile deactivatedBy = providerClass.getAnnotation(AstrixExcludedByProfile.class);
if (this.activeProfiles.contains(deactivatedBy.value())) {
log.debug("Rejecting provider, excluded by active profile. profile={} provider={}", deactivatedBy.value(), providerClass.getProviderClassName());
return false;
}
}
log.debug("Found provider: provider={}", providerClass.getProviderClassName());
return true;
}
private Stream<ApiProviderClass> getApiProviders(Modules modules, DynamicConfig config) {
if (this.astrixApiProviders != null) {
return astrixApiProviders.getAll();
}
String basePackage = AstrixSettings.API_PROVIDER_SCANNER_BASE_PACKAGE.getFrom(config).get();
if (!basePackage.trim().isEmpty()) {
return new AstrixApiProviderClassScanner(getAllApiProviderAnnotationsTypes(modules), "com.avanza.astrix", basePackage.split(",")).getAll(); // Always scan com.avanza.astrix package
}
return new AstrixApiProviderClassScanner(getAllApiProviderAnnotationsTypes(modules), "com.avanza.astrix").getAll();
}
private List<Class<? extends Annotation>> getAllApiProviderAnnotationsTypes(Modules modules) {
List<Class<? extends Annotation>> result = new ArrayList<>();
for (BeanPublisherPlugin plugin : modules.getInstance(ApiProviderPlugins.class).getAll()) {
result.add(plugin.getProviderAnnotationType());
}
return result;
}
/**
* Sets the base-package used when scanning for {@link AstrixApiProvider}'s.<p>
*
* @param basePackage
* @return
*/
public AstrixConfigurer setBasePackage(String basePackage) {
this.settings.set(AstrixSettings.API_PROVIDER_SCANNER_BASE_PACKAGE, basePackage);
return this;
}
public AstrixConfigurer enableFaultTolerance(boolean enableFaultTolerance) {
this.settings.set(AstrixSettings.ENABLE_FAULT_TOLERANCE, enableFaultTolerance);
return this;
}
// package private. Used for internal testing only
void setAstrixApiProviders(ApiProviders astrixApiProviders) {
this.astrixApiProviders = astrixApiProviders;
}
// package private. Used for internal testing only
<T> AstrixConfigurer registerPlugin(final Class<T> type, final T provider) {
customModules.add(new Module() {
@Override
public void prepare(ModuleContext pluginContext) {
pluginContext.bind(type, provider);
pluginContext.export(type);
}
@Override
public String name() {
return "plugin-" + type.getName() + "[" + provider.getClass().getName() + "]";
}
});
return this;
}
// package private. Used for internal testing only
<T> void registerStrategy(final Class<T> strategyInterface, final T strategyInstance) {
this.strategyProviderByType.put(strategyInterface, StrategyProvider.create(strategyInterface, strategyInstance));
}
public AstrixConfigurer set(String settingName, long value) {
this.settings.set(settingName, Long.toString(value));
return this;
}
public AstrixConfigurer set(String settingName, boolean value) {
this.settings.set(settingName, Boolean.toString(value));
return this;
}
public AstrixConfigurer set(String settingName, String value) {
this.settings.set(settingName, value);
return this;
}
public final <T> AstrixConfigurer set(Setting<T> setting, T value) {
this.settings.set(setting, value);
return this;
}
public final <T> AstrixConfigurer set(LongSetting setting, long value) {
this.settings.set(setting, value);
return this;
}
public AstrixConfigurer setSettings(Map<String, String> settings) {
for (Map.Entry<String, String> setting : settings.entrySet()) {
this.settings.set(setting.getKey(), setting.getValue());
}
return this;
}
/**
* Sets the custom configuration sources that should be used by the AstrixContext.
*
* When set, then the given DynamicConfig instance will take precedence over all well-known configuration
* sources, see list below. When this property is set, Astrix will NOT look for a {@link AstrixDynamicConfigFactory}
* to create the custom configuration. If NOT set, then astrix will query all well-known configuration sources
* for a AstrixDynamicConfigFactory. It one is found, than that factory will be used to create a DynamicConfig instance
* for the custom configuration sources, otherwise no custom configuration sources will be used by the
* created AstrixContext (that is, only well-known configuration sources will be used).
*
* <h6>List of well-known configuration sources</h6>
* <ol>
* <li>System Properties</li>
* <li>Programmatic configuration set on this instance</li>
* <li>META-INF/astrix/settings.properties</li>
* <li>default values</li>
* </ol>
*
* @param config custom DynamicConfig to use
* @return
*/
public AstrixConfigurer setConfig(DynamicConfig config) {
this.customConfig = config;
return this;
}
/**
* Optional property that identifies what subsystem the current context belongs to. Its only
* allowed to invoke non-versioned services within the same subsystem. Attempting
* to invoke a non-versioned service in another subsystem will throw an IllegalSubsystemException. <p>
*
* @param string
* @return
*/
public AstrixConfigurer setSubsystem(String subsystem) {
this.settings.set(AstrixSettings.SUBSYSTEM_NAME, subsystem);
return this;
}
void addFactoryBean(StandardFactoryBean<?> factoryBean) {
this.standaloneFactories.add(factoryBean);
}
void removeSetting(String name) {
this.settings.set(name, null);
}
/**
* Activates a given Astrix profile.
*
* Astrix profiles are used to include/exclude {@link AstrixApiProvider}'s at runtime by annotating them
* with {@link AstrixIncludedByProfile} and/or {@link AstrixExcludedByProfile}, typically to
* replace a given {@link AstrixApiProvider} in testing scenarios.<p>
*
*
* @param profile
* @return
*/
public AstrixConfigurer activateProfile(String profile) {
this.activeProfiles.add(profile);
return this;
}
public void set(BooleanBeanSetting beanSetting, AstrixBeanKey<?> beanKey, boolean value) {
set(beanSetting.nameFor(beanKey), value);
}
public void set(IntBeanSetting beanSetting, AstrixBeanKey<?> beanKey, int value) {
set(beanSetting.nameFor(beanKey), value);
}
public void set(LongBeanSetting beanSetting, AstrixBeanKey<?> beanKey, long value) {
set(beanSetting.nameFor(beanKey), value);
}
void registerModule(Module module) {
this.customModules.add(module);
}
public <T> void set(BeanSetting<T> setting, AstrixBeanKey<?> beanKey, T value) {
set(setting.nameFor(beanKey), asString(value));
}
private String asString(Object value) {
if (value == null) {
return null;
}
return value.toString();
}
}