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

com.netflix.archaius.property.DefaultPropertyContainer Maven / Gradle / Ivy

There is a newer version: 2.8.2
Show newest version
/**
 * Copyright 2015 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.archaius.property;

import com.netflix.archaius.api.Config;
import com.netflix.archaius.api.Property;
import com.netflix.archaius.api.PropertyContainer;
import com.netflix.archaius.api.PropertyListener;
import com.netflix.archaius.property.ListenerManager.ListenerUpdater;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.function.Function;

/**
 * Implementation of PropertyContainer which reuses the same object for each
 * type.  This implementation assumes that each fast property is mostly accessed
 * as the same type but allows for additional types to be deserialized.  
 * Instead of incurring the overhead for caching in a hash map, the objects are 
 * stored in a CopyOnWriteArrayList and items are retrieved via a linear scan.
 * 
 * Once created a PropertyContainer property cannot be removed.  However, listeners may be
 * added and removed. 
 */
@Deprecated
public class DefaultPropertyContainer implements PropertyContainer {
    private final Logger LOG = LoggerFactory.getLogger(DefaultPropertyContainer.class);
    
    enum Type {
        INTEGER     (int.class,     Integer.class),
        BYTE        (byte.class,    Byte.class),
        SHORT       (short.class,   Short.class),
        DOUBLE      (double.class,  Double.class),
        FLOAT       (float.class,   Float.class),
        BOOLEAN     (boolean.class, Boolean.class),
        LONG        (long.class,    Long.class),
        STRING      (String.class),
        BIG_DECIMAL (BigDecimal.class),
        BIG_INTEGER (BigInteger.class),
        CUSTOM      ();     // Must be last
        
        private final Class[] types;
        
        Type(Class ... type) {
            types = type;
        }
        
        static Type fromClass(Class clazz) {
            for (Type type : values()) {
                for (Class cls : type.types) {
                    if (cls.equals(clazz)) {
                        return type;
                    }
                }
            }
            return CUSTOM;
        }
    }

    /**
     * The property name
     */
    private final String key;
    
    /**
     * Config from which property values are resolved
     */
    private final Config config;
    
    /**
     * Cache for each type attached to this property.  
     */
    private final CopyOnWriteArrayList> cache = new CopyOnWriteArrayList>();
    
    /**
     * Listeners are tracked globally as an optimization so it is not necessary to iterate through all 
     * property containers when the listeners need to be invoked since the expectation is to have far
     * less listeners than property containers.
     */
    private final ListenerManager listeners;
    
    /**
     * Reference to the externally managed master version used as the dirty flag
     */
    private final AtomicInteger masterVersion;
    
    private volatile long lastUpdateTimeInMillis = 0;
    
    public DefaultPropertyContainer(String key, Config config, AtomicInteger version, ListenerManager listeners) {
        this.key = key;
        this.config = config;
        this.listeners = listeners;
        this.masterVersion = version;
    }

    abstract class CachedProperty implements Property {

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((defaultValue == null) ? 0 : defaultValue.hashCode());
            result = prime * result + type;
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            CachedProperty other = (CachedProperty) obj;
            if (defaultValue == null) {
                if (other.defaultValue != null)
                    return false;
            } else if (!defaultValue.equals(other.defaultValue))
                return false;
            if (type != other.type)
                return false;
            return true;
        }
        private final AtomicStampedReference cache = new AtomicStampedReference<>(null, -1);
        private final int type;
        private final T defaultValue;
        
        CachedProperty(Type type, T defaultValue) {
            this.type = type.ordinal();
            this.defaultValue = defaultValue;
        }
        
        public void addListener(final PropertyListener listener) {
            listeners.add(listener, new ListenerUpdater() {
                private AtomicReference last = new AtomicReference(null);
                
                @Override
                public void update() {
                    final T prev = last.get();
                    final T value;
                    
                    try {
                        value = get();
                    }
                    catch (Exception e) {
                        listener.onParseError(e);
                        return;
                    }

                    if (prev != value) {
                        if (last.compareAndSet(prev, value)) {
                            listener.onChange(value);
                        }
                    }
                }
            });
        }
        
        @Override
        public String getKey() {
            return DefaultPropertyContainer.this.key;
        }

        public void removeListener(PropertyListener listener) {
            listeners.remove(listener);
        }
        
        /**
         * Fetch the latest version of the property.  If not up to date then resolve to the latest
         * value, inline.
         * 
         * TODO: Make resolving property value an offline task
         * 
         * @return
         */
        public T get() {
            int cacheVersion = cache.getStamp();
            int latestVersion  = masterVersion.get();
            
            if (cacheVersion != latestVersion) {
                T currentValue = cache.getReference();
                T newValue = null;
                try {
                    newValue = resolveCurrent();
                } catch (Exception e) {
                    LOG.warn("Unable to get current version of property '{}'", key, e);
                }
                
                if (cache.compareAndSet(currentValue, newValue, cacheVersion, latestVersion)) {
                    // Slight race condition here but not important enough to warrent locking
                    lastUpdateTimeInMillis = System.currentTimeMillis();
                    return firstNonNull(newValue, defaultValue);
                }
            }
            return firstNonNull(cache.getReference(), defaultValue);
        }
        
        public long getLastUpdateTime(TimeUnit units) {
            return units.convert(lastUpdateTimeInMillis, TimeUnit.MILLISECONDS);
        }

        private T firstNonNull(T first, T second) {
            return first == null ? second : first;
        }
        /**
         * Resolve to the most recent value
         * @return
         * @throws Exception
         */
        protected abstract T resolveCurrent() throws Exception;

        private DefaultPropertyContainer getOuterType() {
            return DefaultPropertyContainer.this;
        }
    }

    /**
     * Add a new property to the end of the array list but first check
     * to see if it already exists.
     * @param newProperty
     * @return
     */
    @SuppressWarnings("unchecked")
    private  CachedProperty add(final CachedProperty newProperty) {
        // TODO(nikos): This while() looks like it's redundant
        // since we are only calling add() after a get().
        while (!cache.add(newProperty)) {
            for (CachedProperty property : cache) {
                if (property.equals(newProperty)) {
                    return (CachedProperty) property;
                }
            }
        }
        
        return newProperty;
    }
    
    @Override
    public Property asString(final String defaultValue) {
        return add(new CachedProperty(Type.STRING, defaultValue) {
            @Override
            protected String resolveCurrent() throws Exception {
                return config.getString(key, null);
            }
        });
    }

    @Override
    public Property asInteger(final Integer defaultValue) {
        return add(new CachedProperty(Type.INTEGER, defaultValue) {
            @Override
            protected Integer resolveCurrent() throws Exception {
                return config.getInteger(key, null);
            }
        });
    }

    @Override
    public Property asLong(final Long defaultValue) {
        return add(new CachedProperty(Type.LONG, defaultValue) {
            @Override
            protected Long resolveCurrent() throws Exception {
                return config.getLong(key, null);
            }
        });
    }

    @Override
    public Property asDouble(final Double defaultValue) {
        return add(new CachedProperty(Type.DOUBLE, defaultValue) {
            @Override
            protected Double resolveCurrent() throws Exception {
                return config.getDouble(key, null);
            }
        });
    }

    @Override
    public Property asFloat(final Float defaultValue) {
        return add(new CachedProperty(Type.FLOAT, defaultValue) {
            @Override
            protected Float resolveCurrent() throws Exception {
                return config.getFloat(key, null);
            }
        });
    }

    @Override
    public Property asShort(final Short defaultValue) {
        return add(new CachedProperty(Type.SHORT, defaultValue) {
            @Override
            protected Short resolveCurrent() throws Exception {
                return config.getShort(key, null);
            }
        });
    }

    @Override
    public Property asByte(final Byte defaultValue) {
        return add(new CachedProperty(Type.BYTE, defaultValue) {
            @Override
            protected Byte resolveCurrent() throws Exception {
                return config.getByte(key, defaultValue);
            }
        });
    }

    @Override
    public Property asBigDecimal(final BigDecimal defaultValue) {
        return add(new CachedProperty(Type.BIG_DECIMAL, defaultValue) {
            @Override
            protected BigDecimal resolveCurrent() throws Exception {
                return config.getBigDecimal(key, defaultValue);
            }
        });
    }
    
    @Override
    public Property asBoolean(final Boolean defaultValue) {
        return add(new CachedProperty(Type.BOOLEAN, defaultValue) {
            @Override
            protected Boolean resolveCurrent() throws Exception {
                return config.getBoolean(key, defaultValue);
            }
        });
    }

    @Override
    public Property asBigInteger(final BigInteger defaultValue) {
        return add(new CachedProperty(Type.BIG_INTEGER, defaultValue) {
            @Override
            protected BigInteger resolveCurrent() throws Exception {
                return config.getBigInteger(key, defaultValue);
            }
        });
    }
    
    /**
     * No caching for custom types.
     */
    @SuppressWarnings("unchecked")
    @Override
    public  Property asType(final Class type, final T defaultValue) {
        switch (Type.fromClass(type)) {
        case INTEGER:
            return (Property) asInteger((Integer)defaultValue);
        case BYTE:
            return (Property) asByte((Byte)defaultValue);
        case SHORT:
            return (Property) asShort((Short)defaultValue);
        case DOUBLE:
            return (Property) asDouble((Double)defaultValue);
        case FLOAT:
            return (Property) asFloat((Float)defaultValue);
        case BOOLEAN:
            return (Property) asBoolean((Boolean)defaultValue);
        case STRING:
            return (Property) asString((String)defaultValue);
        case LONG:
            return (Property) asLong((Long)defaultValue);
        case BIG_DECIMAL:
            return (Property) asBigDecimal((BigDecimal)defaultValue);
        case BIG_INTEGER:
            return (Property) asBigInteger((BigInteger)defaultValue);
        default: {
                CachedProperty prop = add(new CachedProperty(Type.CUSTOM, defaultValue) {
                    @Override
                    protected T resolveCurrent() throws Exception {
                        return config.get(type, key, defaultValue);
                    }
                });
                return prop;
            }
        }
    }

    @Override
    public  Property asType(Function type, String defaultValue) {
        return add(new CachedProperty(Type.CUSTOM, null) {
            @Override
            protected T resolveCurrent() throws Exception {
                return type.apply(config.getString(key, defaultValue));
            }
        });
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy