/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.camel.component.hystrix.processor; import java.util.HashMap; import java.util.Map; import java.util.Optional; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.HystrixCommandProperties; import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.HystrixThreadPoolProperties; import org.apache.camel.CamelContext; import org.apache.camel.Processor; import org.apache.camel.impl.TypedProcessorFactory; import org.apache.camel.model.HystrixConfigurationDefinition; import org.apache.camel.model.HystrixDefinition; import org.apache.camel.spi.RouteContext; import org.apache.camel.util.IntrospectionSupport; import org.apache.camel.util.function.Suppliers; import static org.apache.camel.util.CamelContextHelper.lookup; import static org.apache.camel.util.CamelContextHelper.mandatoryLookup; /** * To integrate camel-hystrix with the Camel routes using the Hystrix EIP. */ public class HystrixProcessorFactory extends TypedProcessorFactory<HystrixDefinition> { public HystrixProcessorFactory() { super(HystrixDefinition.class); } @Override public Processor doCreateProcessor(RouteContext routeContext, HystrixDefinition definition) throws Exception { // create the regular and fallback processors Processor processor = definition.createChildProcessor(routeContext, true); Processor fallback = null; if (definition.getOnFallback() != null) { fallback = definition.getOnFallback().createProcessor(routeContext); } final HystrixConfigurationDefinition config = buildHystrixConfiguration(routeContext.getCamelContext(), definition); final String id = definition.idOrCreate(routeContext.getCamelContext().getNodeIdFactory()); // group and thread pool keys to use they can be configured on configRef and config, so look there first, and if none then use default String groupKey = config.getGroupKey(); String threadPoolKey = config.getThreadPoolKey(); if (groupKey == null) { groupKey = HystrixConfigurationDefinition.DEFAULT_GROUP_KEY; } if (threadPoolKey == null) { // by default use the thread pool from the group threadPoolKey = groupKey; } // use the node id as the command key HystrixCommandKey hcCommandKey = HystrixCommandKey.Factory.asKey(id); HystrixCommandKey hcFallbackCommandKey = HystrixCommandKey.Factory.asKey(id + "-fallback"); // use the configured group key HystrixCommandGroupKey hcGroupKey = HystrixCommandGroupKey.Factory.asKey(groupKey); HystrixThreadPoolKey tpKey = HystrixThreadPoolKey.Factory.asKey(threadPoolKey); // create setter using the default options HystrixCommand.Setter setter = HystrixCommand.Setter.withGroupKey(hcGroupKey) .andCommandKey(hcCommandKey) .andThreadPoolKey(tpKey); HystrixCommandProperties.Setter commandSetter = HystrixCommandProperties.Setter(); setter.andCommandPropertiesDefaults(commandSetter); HystrixThreadPoolProperties.Setter threadPoolSetter = HystrixThreadPoolProperties.Setter(); setter.andThreadPoolPropertiesDefaults(threadPoolSetter); configureHystrix(commandSetter, threadPoolSetter, config); // create setter for fallback via network HystrixCommand.Setter fallbackSetter = null; boolean fallbackViaNetwork = definition.getOnFallback() != null && definition.getOnFallback().isFallbackViaNetwork(); if (fallbackViaNetwork) { // use a different thread pool that is for fallback (should never use the same thread pool as the regular command) HystrixThreadPoolKey tpFallbackKey = HystrixThreadPoolKey.Factory.asKey(threadPoolKey + "-fallback"); fallbackSetter = HystrixCommand.Setter.withGroupKey(hcGroupKey) .andCommandKey(hcFallbackCommandKey) .andThreadPoolKey(tpFallbackKey); HystrixCommandProperties.Setter commandFallbackSetter = HystrixCommandProperties.Setter(); fallbackSetter.andCommandPropertiesDefaults(commandFallbackSetter); HystrixThreadPoolProperties.Setter fallbackThreadPoolSetter = HystrixThreadPoolProperties.Setter(); fallbackSetter.andThreadPoolPropertiesDefaults(fallbackThreadPoolSetter); // at first configure any shared options configureHystrix(commandFallbackSetter, fallbackThreadPoolSetter, config); } return new HystrixProcessor(hcGroupKey, hcCommandKey, hcFallbackCommandKey, setter, fallbackSetter, processor, fallback, fallbackViaNetwork); } private void configureHystrix(HystrixCommandProperties.Setter command, HystrixThreadPoolProperties.Setter threadPool, HystrixConfigurationDefinition config) { // command if (config.getCircuitBreakerEnabled() != null) { command.withCircuitBreakerEnabled(config.getCircuitBreakerEnabled()); } if (config.getCircuitBreakerErrorThresholdPercentage() != null) { command.withCircuitBreakerErrorThresholdPercentage(config.getCircuitBreakerErrorThresholdPercentage()); } if (config.getCircuitBreakerForceClosed() != null) { command.withCircuitBreakerForceClosed(config.getCircuitBreakerForceClosed()); } if (config.getCircuitBreakerForceOpen() != null) { command.withCircuitBreakerForceOpen(config.getCircuitBreakerForceOpen()); } if (config.getCircuitBreakerRequestVolumeThreshold() != null) { command.withCircuitBreakerRequestVolumeThreshold(config.getCircuitBreakerRequestVolumeThreshold()); } if (config.getCircuitBreakerSleepWindowInMilliseconds() != null) { command.withCircuitBreakerSleepWindowInMilliseconds(config.getCircuitBreakerSleepWindowInMilliseconds()); } if (config.getExecutionIsolationSemaphoreMaxConcurrentRequests() != null) { command.withExecutionIsolationSemaphoreMaxConcurrentRequests(config.getExecutionIsolationSemaphoreMaxConcurrentRequests()); } if (config.getExecutionIsolationStrategy() != null) { command.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.valueOf(config.getExecutionIsolationStrategy())); } if (config.getExecutionIsolationThreadInterruptOnTimeout() != null) { command.withExecutionIsolationThreadInterruptOnTimeout(config.getExecutionIsolationThreadInterruptOnTimeout()); } if (config.getExecutionTimeoutInMilliseconds() != null) { command.withExecutionTimeoutInMilliseconds(config.getExecutionTimeoutInMilliseconds()); } if (config.getExecutionTimeoutEnabled() != null) { command.withExecutionTimeoutEnabled(config.getExecutionTimeoutEnabled()); } if (config.getFallbackIsolationSemaphoreMaxConcurrentRequests() != null) { command.withFallbackIsolationSemaphoreMaxConcurrentRequests(config.getFallbackIsolationSemaphoreMaxConcurrentRequests()); } if (config.getFallbackEnabled() != null) { command.withFallbackEnabled(config.getFallbackEnabled()); } if (config.getMetricsHealthSnapshotIntervalInMilliseconds() != null) { command.withMetricsHealthSnapshotIntervalInMilliseconds(config.getMetricsHealthSnapshotIntervalInMilliseconds()); } if (config.getMetricsRollingPercentileBucketSize() != null) { command.withMetricsRollingPercentileBucketSize(config.getMetricsRollingPercentileBucketSize()); } if (config.getMetricsRollingPercentileEnabled() != null) { command.withMetricsRollingPercentileEnabled(config.getMetricsRollingPercentileEnabled()); } if (config.getMetricsRollingPercentileWindowInMilliseconds() != null) { command.withMetricsRollingPercentileWindowInMilliseconds(config.getMetricsRollingPercentileWindowInMilliseconds()); } if (config.getMetricsRollingPercentileWindowBuckets() != null) { command.withMetricsRollingPercentileWindowBuckets(config.getMetricsRollingPercentileWindowBuckets()); } if (config.getMetricsRollingStatisticalWindowInMilliseconds() != null) { command.withMetricsRollingStatisticalWindowInMilliseconds(config.getMetricsRollingStatisticalWindowInMilliseconds()); } if (config.getMetricsRollingStatisticalWindowBuckets() != null) { command.withMetricsRollingStatisticalWindowBuckets(config.getMetricsRollingStatisticalWindowBuckets()); } if (config.getRequestLogEnabled() != null) { command.withRequestLogEnabled(config.getRequestLogEnabled()); } if (config.getCorePoolSize() != null) { threadPool.withCoreSize(config.getCorePoolSize()); } if (config.getMaximumSize() != null) { threadPool.withMaximumSize(config.getMaximumSize()); } if (config.getKeepAliveTime() != null) { threadPool.withKeepAliveTimeMinutes(config.getKeepAliveTime()); } if (config.getMaxQueueSize() != null) { threadPool.withMaxQueueSize(config.getMaxQueueSize()); } if (config.getQueueSizeRejectionThreshold() != null) { threadPool.withQueueSizeRejectionThreshold(config.getQueueSizeRejectionThreshold()); } if (config.getThreadPoolRollingNumberStatisticalWindowInMilliseconds() != null) { threadPool.withMetricsRollingStatisticalWindowInMilliseconds(config.getThreadPoolRollingNumberStatisticalWindowInMilliseconds()); } if (config.getThreadPoolRollingNumberStatisticalWindowBuckets() != null) { threadPool.withMetricsRollingStatisticalWindowBuckets(config.getThreadPoolRollingNumberStatisticalWindowBuckets()); } if (config.getAllowMaximumSizeToDivergeFromCoreSize() != null) { threadPool.withAllowMaximumSizeToDivergeFromCoreSize(config.getAllowMaximumSizeToDivergeFromCoreSize()); } } // ******************************* // Helpers // ******************************* HystrixConfigurationDefinition buildHystrixConfiguration(CamelContext camelContext, HystrixDefinition definition) throws Exception { Map<String, Object> properties = new HashMap<>(); // Extract properties from default configuration, the one configured on // camel context takes the precedence over those in the registry loadProperties(properties, Suppliers.firstNotNull( () -> camelContext.getHystrixConfiguration(null), () -> lookup(camelContext, HystrixConstants.DEFAULT_HYSTRIX_CONFIGURATION_ID, HystrixConfigurationDefinition.class)) ); // Extract properties from referenced configuration, the one configured // on camel context takes the precedence over those in the registry if (definition.getHystrixConfigurationRef() != null) { final String ref = definition.getHystrixConfigurationRef(); loadProperties(properties, Suppliers.firstNotNull( () -> camelContext.getHystrixConfiguration(ref), () -> mandatoryLookup(camelContext, ref, HystrixConfigurationDefinition.class)) ); } // Extract properties from local configuration loadProperties(properties, Optional.ofNullable(definition.getHystrixConfiguration())); // Extract properties from definition IntrospectionSupport.getProperties(definition, properties, null, false); HystrixConfigurationDefinition config = new HystrixConfigurationDefinition(); // Apply properties to a new configuration IntrospectionSupport.setProperties(camelContext, camelContext.getTypeConverter(), config, properties); return config; } private void loadProperties(Map<String, Object> properties, Optional<?> optional) { optional.ifPresent(bean -> IntrospectionSupport.getProperties(bean, properties, null, false)); } }