/** * Copyright 2016 Netflix, Inc. * * 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.netflix.hystrix.strategy.properties; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netflix.hystrix.strategy.HystrixPlugins; /** * Chained property allowing a chain of defaults properties which is uses the properties plugin. * <p> * Instead of just a single dynamic property with a default this allows a sequence of properties that fallback to the farthest down the chain with a value. * * TODO This should be replaced by a version in the Archaius library once available. * * @ExcludeFromJavadoc */ public abstract class HystrixPropertiesChainedProperty { private static final Logger logger = LoggerFactory.getLogger(HystrixPropertiesChainedProperty.class); /** * @ExcludeFromJavadoc */ private static abstract class ChainLink<T> { private final AtomicReference<ChainLink<T>> pReference; private final ChainLink<T> next; private final List<Runnable> callbacks; /** * @return String */ public abstract String getName(); /** * @return T */ protected abstract T getValue(); /** * @return Boolean */ public abstract boolean isValueAcceptable(); /** * No arg constructor - used for end node */ public ChainLink() { next = null; pReference = new AtomicReference<ChainLink<T>>(this); callbacks = new ArrayList<Runnable>(); } /** * @param nextProperty next property in the chain */ public ChainLink(ChainLink<T> nextProperty) { next = nextProperty; pReference = new AtomicReference<ChainLink<T>>(next); callbacks = new ArrayList<Runnable>(); } protected void checkAndFlip() { // in case this is the end node if (next == null) { pReference.set(this); return; } if (this.isValueAcceptable()) { logger.debug("Flipping property: {} to use its current value: {}", getName(), getValue()); pReference.set(this); } else { logger.debug("Flipping property: {} to use NEXT property: {}", getName(), next); pReference.set(next); } for (Runnable r : callbacks) { r.run(); } } /** * @return T */ public T get() { if (pReference.get() == this) { return this.getValue(); } else { return pReference.get().get(); } } /** * @param r callback to execut */ public void addCallback(Runnable r) { callbacks.add(r); } /** * @return String */ public String toString() { return getName() + " = " + get(); } } public static abstract class ChainBuilder<T> { private ChainBuilder() { super(); } private List<HystrixDynamicProperty<T>> properties = new ArrayList<HystrixDynamicProperty<T>>(); public ChainBuilder<T> add(HystrixDynamicProperty<T> property) { properties.add(property); return this; } public ChainBuilder<T> add(String name, T defaultValue) { properties.add(getDynamicProperty(name, defaultValue, getType())); return this; } public HystrixDynamicProperty<T> build() { if (properties.size() < 1) throw new IllegalArgumentException(); if (properties.size() == 1) return properties.get(0); List<HystrixDynamicProperty<T>> reversed = new ArrayList<HystrixDynamicProperty<T>>(properties); Collections.reverse(reversed); ChainProperty<T> current = null; for (HystrixDynamicProperty<T> p : reversed) { if (current == null) { current = new ChainProperty<T>(p); } else { current = new ChainProperty<T>(p, current); } } return new ChainHystrixProperty<T>(current); } protected abstract Class<T> getType(); } private static <T> ChainBuilder<T> forType(final Class<T> type) { return new ChainBuilder<T>() { @Override protected Class<T> getType() { return type; } }; } public static ChainBuilder<String> forString() { return forType(String.class); } public static ChainBuilder<Integer> forInteger() { return forType(Integer.class); } public static ChainBuilder<Boolean> forBoolean() { return forType(Boolean.class); } public static ChainBuilder<Long> forLong() { return forType(Long.class); } private static class ChainHystrixProperty<T> implements HystrixDynamicProperty<T> { private final ChainProperty<T> property; public ChainHystrixProperty(ChainProperty<T> property) { super(); this.property = property; } @Override public String getName() { return property.getName(); } @Override public T get() { return property.get(); } @Override public void addCallback(Runnable callback) { property.addCallback(callback); } } private static class ChainProperty<T> extends ChainLink<T> { private final HystrixDynamicProperty<T> sProp; public ChainProperty(HystrixDynamicProperty<T> sProperty) { super(); sProp = sProperty; } public ChainProperty(HystrixDynamicProperty<T> sProperty, ChainProperty<T> next) { super(next); // setup next pointer sProp = sProperty; sProp.addCallback(new Runnable() { @Override public void run() { logger.debug("Property changed: '{} = {}'", getName(), getValue()); checkAndFlip(); } }); checkAndFlip(); } @Override public boolean isValueAcceptable() { return (sProp.get() != null); } @Override protected T getValue() { return sProp.get(); } @Override public String getName() { return sProp.getName(); } } private static <T> HystrixDynamicProperty<T> getDynamicProperty(String propName, T defaultValue, Class<T> type) { HystrixDynamicProperties properties = HystrixPlugins.getInstance().getDynamicProperties(); HystrixDynamicProperty<T> p = HystrixDynamicProperties.Util.getProperty(properties, propName, defaultValue, type); return p; } }