com.netflix.config.ChainedDynamicProperty Maven / Gradle / Ivy
/**
* Copyright 2014 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.config;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The class that provides the chaining functionality of dynamic properties.
* The idea is that the head property holds the current up-to-date value of the property and the "next" property
* in the chain serves as the default if the head property value does not exist or is not acceptable.
* Concrete implementation is available for IntProperty, StringProperty, BooleanProperty and FloatProperty.
*
* For example
*
*
* {@code
DynamicStringProperty pString = DynamicPropertyFactory.getInstance().getStringProperty("defaultString", "default-default");
ChainedDynamicProperty.StringProperty fString = new ChainedDynamicProperty.StringProperty("overrideString", pString);
assertTrue("default-default".equals(fString.get()));
ConfigurationManager.getConfigInstance().setProperty("defaultString", "default");
assertTrue("default".equals(fString.get()));
ConfigurationManager.getConfigInstance().setProperty("overrideString", "override");
assertTrue("override".equals(fString.get()));
ConfigurationManager.getConfigInstance().clearProperty("overrideString");
assertTrue("default".equals(fString.get()));
ConfigurationManager.getConfigInstance().clearProperty("defaultString");
assertTrue("default-default".equals(fString.get()));
* }
*
*
* @see IntProperty
* @see StringProperty
* @see BooleanProperty
* @see FloatProperty
*/
public class ChainedDynamicProperty {
private static final Logger logger = LoggerFactory.getLogger(ChainedDynamicProperty.class);
public static abstract class ChainLink implements Property {
private final AtomicReference> pReference;
private final ChainLink next;
private final List callbacks;
private final T defaultValue;
/**
* @return Boolean
*/
public abstract boolean isValueAcceptable();
/**
* Get the property referenced by this ChainLink
* @return the referenced property
*/
protected abstract Property getReferencedProperty();
/**
* Used for end node
*/
public ChainLink(T defaultValue) {
next = null;
pReference = new AtomicReference>(this);
callbacks = new ArrayList();
this.defaultValue = defaultValue;
}
/**
* @param nextProperty
*/
public ChainLink(ChainLink nextProperty) {
next = nextProperty;
pReference = new AtomicReference>(next);
callbacks = new ArrayList();
this.defaultValue = nextProperty.getDefaultValue();
}
protected void checkAndFlip() {
// in case this is the end node
if(next == null) {
pReference.set(this);
return;
}
if (this.isValueAcceptable()) {
logger.info("Flipping property: " + getName() + " to use it's current value:" + getValue());
pReference.set(this);
} else {
logger.info("Flipping property: " + getName() + " to use NEXT property: " + 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();
}
}
@Override
public T getValue() {
return getReferencedProperty().getValue();
}
@Override
public T getDefaultValue() {
return defaultValue;
}
@Override
public String getName() {
return getReferencedProperty().getName();
}
@Override
public long getChangedTimestamp() {
if (pReference.get() == this) {
return getReferencedProperty().getChangedTimestamp();
} else {
return pReference.get().getChangedTimestamp();
}
}
/**
* @param r
*/
@Override
public void addCallback(Runnable r) {
callbacks.add(r);
}
/**
* Remove callback from callbacks list
*/
@Override
public void removeAllCallbacks() {
callbacks.clear();
}
/**
* @return String
*/
@Override
public String toString() {
return getName() + " = " + get();
}
}
public static class StringProperty extends ChainLink {
private final DynamicStringProperty sProp;
private Runnable callback;
public StringProperty(DynamicStringProperty sProperty) {
super(sProperty.getDefaultValue());
sProp = sProperty;
}
public StringProperty(String name, DynamicStringProperty sProperty) {
this(name, new StringProperty(sProperty));
}
public StringProperty(String name, StringProperty next) {
super(next); // setup next pointer
sProp = DynamicPropertyFactory.getInstance().getStringProperty(name, null);
callback = new Runnable() {
@Override
public void run() {
logger.info("Property changed: '" + getName() + " = " + getValue() + "'");
checkAndFlip();
}
};
sProp.addCallback(callback);
checkAndFlip();
}
@Override
public boolean isValueAcceptable() {
return (sProp.get() != null);
}
@Override
protected Property getReferencedProperty() {
return sProp;
}
@Override
public void removeAllCallbacks() {
super.removeAllCallbacks();
sProp.prop.removeCallback(callback);
}
}
public static class IntProperty extends ChainLink {
private final DynamicIntProperty sProp;
private Runnable callback;
public IntProperty(DynamicIntProperty sProperty) {
super(sProperty.getDefaultValue());
sProp = sProperty;
}
public IntProperty(String name, DynamicIntProperty sProperty) {
this(name, new IntProperty(sProperty));
}
public IntProperty(String name, IntProperty next) {
super(next); // setup next pointer
sProp = DynamicPropertyFactory.getInstance().getIntProperty(name, Integer.MIN_VALUE);
callback = new Runnable() {
@Override
public void run() {
logger.info("Property changed: '" + getName() + " = " + getValue() + "'");
checkAndFlip();
}
};
sProp.addCallback(callback);
checkAndFlip();
}
@Override
public boolean isValueAcceptable() {
return (sProp.get() != Integer.MIN_VALUE);
}
@Override
protected Property getReferencedProperty() {
return sProp;
}
@Override
public void removeAllCallbacks() {
super.removeAllCallbacks();
sProp.prop.removeCallback(callback);
}
}
public static class LongProperty extends ChainLink {
private final DynamicLongProperty sProp;
private Runnable callback;
public LongProperty(DynamicLongProperty sProperty) {
super(sProperty.getDefaultValue());
sProp = sProperty;
}
public LongProperty(String name, DynamicLongProperty sProperty) {
this(name, new LongProperty(sProperty));
}
public LongProperty(String name, LongProperty next) {
super(next); // setup next pointer
sProp = DynamicPropertyFactory.getInstance().getLongProperty(name, Long.MIN_VALUE);
callback = new Runnable() {
@Override
public void run() {
logger.info("Property changed: '" + getName() + " = " + getValue() + "'");
checkAndFlip();
}
};
sProp.addCallback(callback);
checkAndFlip();
}
@Override
public boolean isValueAcceptable() {
return (sProp.get() != Long.MIN_VALUE);
}
@Override
protected Property getReferencedProperty() {
return sProp;
}
@Override
public void removeAllCallbacks() {
super.removeAllCallbacks();
sProp.prop.removeCallback(callback);
}
}
public static class FloatProperty extends ChainLink {
private final DynamicFloatProperty sProp;
private Runnable callback;
public FloatProperty(DynamicFloatProperty sProperty) {
super(sProperty.getDefaultValue());
sProp = sProperty;
}
public FloatProperty(String name, DynamicFloatProperty sProperty) {
this(name, new FloatProperty(sProperty));
}
public FloatProperty(String name, FloatProperty next) {
super(next); // setup next pointer
sProp = DynamicPropertyFactory.getInstance().getFloatProperty(name, Float.MIN_VALUE);
callback = new Runnable() {
@Override
public void run() {
logger.info("Property changed: '" + getName() + " = " + getValue() + "'");
checkAndFlip();
}
};
sProp.addCallback(callback);
checkAndFlip();
}
@Override
public boolean isValueAcceptable() {
return Math.abs(sProp.get() - Float.MIN_VALUE) > 0.000001f ;
}
@Override
protected Property getReferencedProperty() {
return sProp;
}
@Override
public void removeAllCallbacks() {
super.removeAllCallbacks();
sProp.prop.removeCallback(callback);
}
}
public static class BooleanProperty extends ChainLink {
private final DynamicBooleanPropertyThatSupportsNull sProp;
private Runnable callback;
public BooleanProperty(DynamicBooleanPropertyThatSupportsNull sProperty) {
super(sProperty.getDefaultValue());
sProp = sProperty;
}
public BooleanProperty(String name, DynamicBooleanPropertyThatSupportsNull sProperty) {
this(name, new BooleanProperty(sProperty));
}
public BooleanProperty(String name, BooleanProperty next) {
super(next); // setup next pointer
sProp = new DynamicBooleanPropertyThatSupportsNull(name, null);
callback = new Runnable() {
@Override
public void run() {
logger.info("Property changed: '" + getName() + " = " + getValue() + "'");
checkAndFlip();
}
};
sProp.addCallback(callback);
checkAndFlip();
}
@Override
public boolean isValueAcceptable() {
return (sProp.getValue() != null);
}
@Override
protected Property getReferencedProperty() {
return sProp;
}
@Override
public void removeAllCallbacks() {
super.removeAllCallbacks();
sProp.prop.removeCallback(callback);
}
}
public static class DynamicBooleanPropertyThatSupportsNull extends PropertyWrapper {
public DynamicBooleanPropertyThatSupportsNull(String propName, Boolean defaultValue) {
super(propName, defaultValue);
}
/**
* Get the current value from the underlying DynamicProperty
*/
public Boolean get() {
return prop.getBoolean(defaultValue);
}
@Override
public Boolean getValue() {
return get();
}
}
}