com.github.enadim.spring.cloud.ribbon.support.ContextPropagationConfig Maven / Gradle / Ivy
/*
* Copyright (c) 2017 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 com.github.enadim.spring.cloud.ribbon.support;
import com.github.enadim.spring.cloud.ribbon.api.RibbonRuleContext;
import com.github.enadim.spring.cloud.ribbon.propagator.concurrent.AsyncListenableTaskExecutorPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.concurrent.AsyncTaskExecutorPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.concurrent.ExecutorPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.concurrent.ExecutorServicePropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.concurrent.ScheduledExecutorServicePropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.concurrent.SchedulingTaskExecutorPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.concurrent.TaskSchedulerPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.concurrent.ThreadPoolTaskExecutorPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.concurrent.ThreadPoolTaskSchedulerPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.feign.FeignHttpHeadersPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.hystrix.HystrixPropagationStrategy;
import com.github.enadim.spring.cloud.ribbon.propagator.jms.ConnectionFactoryPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.servlet.HttpRequestHeadersPropagator;
import com.github.enadim.spring.cloud.ribbon.propagator.stomp.StompSessionPropagator;
import com.netflix.hystrix.Hystrix;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import feign.Feign;
import feign.RequestInterceptor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.messaging.simp.stomp.StompSession;
import org.springframework.scheduling.SchedulingTaskExecutor;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.PostConstruct;
import javax.jms.ConnectionFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
/**
* Enables {@link RibbonRuleContext} propagation.
* Contents
*
* - Configures {@link FeignHttpHeadersPropagator} that enabled propagation to feign resource calls.
*
- Configures {@link HttpRequestHeadersPropagator} that enables propagation from incoming http requests.
*
- Configures Hystrix to enable propagation on async commands through.
*
- Defines {@link ZuulHandlerMapping} post processor to enable the propagation for routes.
*
- Defines {@link ExecutorService} post processor to enable the propagation to async tasks.
*
- Defines {@link ConnectionFactory} post processor which enables the propagation from/to jms properties.
*
- Defines {@link StompSession} post processor which enables the propagation from/to the stomp headers.
*
* The propagation keys should be defined on the property 'ribbon.extensions.propagation.keys' default is an empty list {}
*
* @author Nadim Benabdenbi
* @see EnableRibbonContextPropagation
*/
@Slf4j
public class ContextPropagationConfig {
@Configuration
@ConditionalOnClass(Feign.class)
@ConditionalOnProperty(value = "ribbon.extensions.propagation.feign.enabled", matchIfMissing = true)
@ConditionalOnExpression(value = "${ribbon.extensions.propagation.enabled:true}")
public static class FeignPropagationConfig {
/**
* @param properties the propagation properties
* @return the feign http headers interceptor
* @see FeignHttpHeadersPropagator
*/
@Bean
public RequestInterceptor feignHeaderPropagator(PropagationProperties properties) {
log.info("Propagation enabled for feign clients on keys={}.", properties.getKeysAsSet());
return new FeignHttpHeadersPropagator(properties.getKeysAsSet());
}
}
@Configuration
@ConditionalOnWebApplication
@ConditionalOnProperty(value = "ribbon.extensions.propagation.http.enabled", matchIfMissing = true)
@ConditionalOnExpression(value = "${ribbon.extensions.propagation.enabled:true}")
public static class WebApplicationPropagationConfig extends WebMvcConfigurerAdapter {
@Autowired
protected PropagationProperties properties;
/**
* Adds http request interceptor copying headers from the request to the context
*
* @param registry the interceptor registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HttpRequestHeadersPropagator(properties.getKeysAsSet())).addPathPatterns(
"/**");
log.debug("Propagation enabled for http request on keys={}.", properties.getKeysAsSet());
}
}
/**
* Enables {@link RibbonRuleContext} propagation with {@link Hystrix}.
* ² *
Registers Hystrix concurrent strategy wrapping the callable see: Histrix Wiki.
*
* @see HystrixPropagationStrategy
*/
@Configuration
@ConditionalOnClass(Hystrix.class)
@ConditionalOnProperty(value = "ribbon.extensions.propagation.hystrix.enabled", matchIfMissing = true)
@ConditionalOnExpression(value = "${ribbon.extensions.propagation.enabled:true}")
//HystrixSecurityAutoConfiguration does not comply with a clean registration process over a static holder.
@AutoConfigureAfter(HystrixSecurityAutoConfiguration.class)
@Slf4j
public static class HystrixRibbonContextPropagationConfig {
@PostConstruct
public void postContruct() {
init();
}
/**
* registers the {@link HystrixPropagationStrategy}
*/
public static void init() {
// keeps references of existing Hystrix plugins.
HystrixConcurrencyStrategy existingConcurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy();
HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook();
// reset the Hystrix plugin
HystrixPlugins.reset();
// configure the plugin
HystrixPlugins.getInstance().registerConcurrencyStrategy(new HystrixPropagationStrategy(existingConcurrencyStrategy));
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
log.info("Propagation enabled for Hystrix.");
}
}
/**
* Zuul support
*/
@Configuration
@ConditionalOnClass(ZuulHandlerMapping.class)
@ConditionalOnProperty(value = "ribbon.extensions.propagation.zuul.enabled", matchIfMissing = true)
@ConditionalOnExpression(value = "${ribbon.extensions.propagation.enabled:true}")
public static class ZuulHandlerBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
@Autowired
protected PropagationProperties properties;
/**
* {@inheritDoc}
*/
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) {
if (bean instanceof ZuulHandlerMapping) {
ZuulHandlerMapping zuulHandlerMapping = (ZuulHandlerMapping) bean;
zuulHandlerMapping.setInterceptors(new HttpRequestHeadersPropagator(properties.getKeysAsSet()));
log.debug("Propagation enabled for zuul handler[{}] on keys={}.", beanName, properties.getKeysAsSet());
}
return super.postProcessAfterInstantiation(bean, beanName);
}
}
/**
* {@link ExecutorService} support
*/
@Configuration
@ConditionalOnProperty(value = "ribbon.extensions.propagation.executor.enabled", matchIfMissing = true)
@ConditionalOnExpression(value = "${ribbon.extensions.propagation.enabled:true}")
public static class ExecutorPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
/**
* {@inheritDoc}
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof Executor || bean instanceof TaskScheduler) {
// spring
if (bean instanceof AsyncListenableTaskExecutor && bean instanceof SchedulingTaskExecutor && bean instanceof TaskScheduler) {
log.debug("Propagation enabled for ~ThreadPoolTaskScheduler [{}]:[{}].", beanName, bean.getClass().getName());
return new ThreadPoolTaskSchedulerPropagator((AsyncListenableTaskExecutor) bean, (SchedulingTaskExecutor) bean, (TaskScheduler) bean);
} else if (bean instanceof AsyncListenableTaskExecutor && bean instanceof SchedulingTaskExecutor) {
log.debug("Propagation enabled for ~ThreadPoolTaskExecutor [{}]:[{}].", beanName, bean.getClass().getName());
return new ThreadPoolTaskExecutorPropagator((AsyncListenableTaskExecutor) bean, (SchedulingTaskExecutor) bean);
} else if (bean instanceof TaskScheduler) {
log.debug("Propagation enabled for TaskScheduler [{}]:[{}].", beanName, bean.getClass().getName());
return new TaskSchedulerPropagator((TaskScheduler) bean);
} else if (bean instanceof SchedulingTaskExecutor) {
log.debug("Propagation enabled for SchedulingTaskExecutor [{}]:[{}].", beanName, bean.getClass().getName());
return new SchedulingTaskExecutorPropagator((SchedulingTaskExecutor) bean);
} else if (bean instanceof AsyncListenableTaskExecutor) {
log.debug("Propagation enabled for AsyncListenableTaskExecutor [{}]:[{}].", beanName, bean.getClass().getName());
return new AsyncListenableTaskExecutorPropagator((AsyncListenableTaskExecutor) bean);
} else if (bean instanceof AsyncTaskExecutor) {
log.debug("Propagation enabled for AsyncTaskExecutor [{}]:[{}].", beanName, bean.getClass().getName());
return new AsyncTaskExecutorPropagator((AsyncTaskExecutor) bean);
}
// java
else if (bean instanceof ScheduledExecutorService) {
log.debug("Propagation enabled for ScheduledExecutorService [{}]:[{}].", beanName, bean.getClass().getName());
return new ScheduledExecutorServicePropagator((ScheduledExecutorService) bean);
} else if (bean instanceof ExecutorService) {
log.debug("Propagation enabled for ExecutorService [{}]:[{}].", beanName, bean.getClass().getName());
return new ExecutorServicePropagator((ExecutorService) bean);
} else {
log.debug("Propagation enabled for Executor [{}]:[{}].", bean, bean.getClass().getName());
return new ExecutorPropagator((Executor) bean);
}
}
return bean;
}
}
/**
* Stomp {@link StompSession} support.
*/
@Configuration
@ConditionalOnClass(StompSession.class)
@ConditionalOnProperty(value = "ribbon.extensions.propagation.stomp.enabled", matchIfMissing = true)
@ConditionalOnExpression(value = "${ribbon.extensions.propagation.enabled:true}")
public static class StompPropagationPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
@Autowired
protected PropagationProperties properties;
/**
* {@inheritDoc}
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof StompSession && !(bean instanceof StompSessionPropagator)) {
log.debug("Propagation enabled for stomp session [{}].", bean);
return new StompSessionPropagator((StompSession) bean, properties.getKeysAsSet());
} else {
return bean;
}
}
}
/**
* Jms propagation support.
*/
@Configuration
@ConditionalOnClass(ConnectionFactoryPropagator.class)
@ConditionalOnProperty(value = "ribbon.extensions.propagation.jms.enabled", matchIfMissing = true)
@ConditionalOnExpression(value = "${ribbon.extensions.propagation.enabled:true}")
public static class ConnectionFactoryPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
@Autowired
protected PropagationProperties properties;
/**
* {@inheritDoc}
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof ConnectionFactory && !(bean instanceof ConnectionFactoryPropagator)) {
log.debug("Propagation enabled for jms connection factory [{}].", bean);
return new ConnectionFactoryPropagator((ConnectionFactory) bean, properties.getKeysAsSet());
} else {
return bean;
}
}
}
@ConfigurationProperties(prefix = "ribbon.extensions.propagation")
@Component
@Getter
public static class PropagationProperties {
/**
* the keys to propagate
*/
private List keys = new ArrayList<>();
/**
* Convenient keys getter (set conversion not supported for now).
*
* @return the keys to propagate as a {@link Set}
*/
public Set getKeysAsSet() {
return new HashSet<>(getKeys());
}
}
}