All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.config.DynamicContextualProperty Maven / Gradle / Ivy

There is a newer version: 2.0.0-rc.7
Show newest version
/**
 * 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.Collection;
import java.util.List;
import java.util.Map;


import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;

/**
 * A property that has multiple possible values associated with it and determines
 * the value according to the runtime context, which can include deployment context, 
 * values of other properties or attributes of user input. 
 * 

* The value is defined as a JSON blob that * consists of multiple conditions and value associated with each condition. The following * is an example: * *

{@code
 * [
    {
       "if":  
              {"@environment":["prod"],
               "@region":["us-east-1"]
              },
       "value":5
    },
     {
        "if":
             {"@environment":["test", "dev"]},
        "value":10
     },
    {
        "value":2
    }
]
 * }
* * This blob means that *
    *
  • If "@enviornment" is "prod" and "@region" is "us-east-1", value of the property is integer 5; (Note: if you use ConfigurationManager, "@enviornment" and "@region" are automatically exported as properties from DeploymentContext) *
  • Else if "@environment" is either "test" or "dev", the value is 10; *
  • Otherwise, the default value of the property is 2 *
* In order to make this work, a Predicate is needed to determine if the current runtime context matches any of the conditions * described in the JSON blob. The predicate can be passed in as a parameter of the constructor, otherwise the default one * will interpret each key in the "dimensions" as a key of a DynamicProperty, and the list of values are the acceptable * values of the DynamicProperty. *

* For example:

* *

{@code
 * 
 *      String json = ... // string as the above JSON blob
 *      ConfigurationManager.getConfigInstance().setProperty("@environment", "test"); // no need to do this in real application as property is automatically set 
        ConfigurationManager.getConfigInstance().setProperty("contextualProp", json);
        DynamicContextualProperty prop = new DynamicContextualProperty("contextualProp", 0);
        prop.get(); // returns 10 as "@environment" == "test" matches the second condition 
 * }
* * @author awang * * @param Data type of the property, e.g., Integer, Boolean */ public class DynamicContextualProperty extends PropertyWrapper { public static class Value { private Map> dimensions; private T value; private String comment; private boolean runtimeEval = false; public Value() { } public Value(T value) { this.value = value; } @JsonProperty("if") public final Map> getDimensions() { return dimensions; } @JsonProperty("if") public final void setDimensions(Map> dimensions) { this.dimensions = dimensions; } public final T getValue() { return value; } public final void setValue(T value) { this.value = value; } public final String getComment() { return comment; } public final void setComment(String comment) { this.comment = comment; } public final boolean isRuntimeEval() { return runtimeEval; } public final void setRuntimeEval(boolean runtimeEval) { this.runtimeEval = runtimeEval; } } private static Predicate>> defaultPredicate = DefaultContextualPredicate.PROPERTY_BASED; private final Predicate>> predicate; @VisibleForTesting volatile List> values; private final ObjectMapper mapper = new ObjectMapper(); private final Class classType; @SuppressWarnings("unchecked") public DynamicContextualProperty(String propName, T defaultValue, Predicate>> predicate) { super(propName, defaultValue); classType = (Class) defaultValue.getClass(); this.predicate = predicate; propertyChangedInternal(); } @SuppressWarnings("unchecked") public DynamicContextualProperty(String propName, T defaultValue) { super(propName, defaultValue); classType = (Class) defaultValue.getClass(); this.predicate = defaultPredicate; propertyChangedInternal(); } private final void propertyChangedInternal() { if (prop.getString() != null) { try { values = mapper.readValue(prop.getString(), new TypeReference>>(){}); } catch (Throwable e) { // this could be that the property is not set up as JSON based multi-dimensional contextual property // and only has one textual value, try to parse this textual value Optional cachedValue = prop.getCachedValue(classType); if (cachedValue.isPresent()) { T value = cachedValue.get(); values = Lists.newArrayList(); values.add(new Value(value)); } else { values = null; } if (values == null) { throw new RuntimeException("Unable to parse the property value: " + prop.getString(), e); } } } else { values = null; } } @Override protected final void propertyChanged() { propertyChangedInternal(); propertyChanged(this.getValue()); } @Override public T getValue() { if (values != null) { for (Value v: values) { if (v.getDimensions() == null || v.getDimensions().isEmpty() || predicate.apply(v.getDimensions())) { return v.getValue(); } } } return defaultValue; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy