ReloadableClientConfig.java 16.1 KB
Newer Older
1 2 3 4 5 6 7
package com.netflix.client.config;

import com.google.common.base.Preconditions;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

E
elandau 已提交
8
import java.lang.reflect.Method;
9
import java.util.Collections;
10 11 12 13 14 15 16 17 18
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
19
import java.util.function.BiConsumer;
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * Base implementation of an IClientConfig with configuration that can be reloaded at runtime from an underlying
 * property source while optimizing access to property values.
 *
 * Properties can either be scoped to a specific client or default properties that span all clients.   By default
 * properties follow the name convention `{clientname}.{namespace}.{key}` and then fallback to `{namespace}.{key}`
 * if not found
 *
 * Internally the config tracks two maps, one for dynamic properties and one for code settable default values to use
 * when a property is not defined in the underlying property source.
 */
E
elandau 已提交
35 36
public abstract class ReloadableClientConfig implements IClientConfig {
    private static final Logger LOG = LoggerFactory.getLogger(ReloadableClientConfig.class);
37 38 39 40 41

    private static final String DEFAULT_CLIENT_NAME = "";
    private static final String DEFAULT_NAMESPACE = "ribbon";

    // Map of raw property names (without namespace or client) to values set via code
42
    private final Map<IClientConfigKey, Object> internalProperties = new HashMap<>();
43 44 45 46 47 48 49 50 51 52 53

    // Map of all seen dynamic properties.  This map will hold on properties requested with the exception of
    // those returned from getGlobalProperty().
    private final Map<IClientConfigKey, ReloadableProperty<?>> dynamicProperties = new ConcurrentHashMap<>();

    // List of actions to perform when configuration changes.  This includes both updating the Property instances
    // as well as external consumers.
    private final List<Runnable> changeActions = new CopyOnWriteArrayList<>();

    private final AtomicLong refreshCounter = new AtomicLong();

E
elandau 已提交
54 55
    private final PropertyResolver resolver;

56 57 58 59
    private String clientName;

    private String namespace = DEFAULT_NAMESPACE;

60 61
    private boolean isLoaded = false;

E
elandau 已提交
62
    protected ReloadableClientConfig(PropertyResolver resolver) {
63 64
        this.clientName = DEFAULT_CLIENT_NAME;
        this.resolver = resolver;
65 66
    }

E
elandau 已提交
67
    protected ReloadableClientConfig(PropertyResolver resolver, String clientName) {
68
        this.clientName = clientName;
E
elandau 已提交
69
        this.resolver = resolver;
70 71
    }

72 73 74 75
    protected PropertyResolver getPropertyResolver() {
        return this.resolver;
    }

76 77 78 79 80 81 82 83
    /**
     * Refresh all seen properties from the underlying property storage
     */
    public final void reload() {
        changeActions.forEach(Runnable::run);
        cachedToString = null;
    }

84 85 86 87
    /**
     * @deprecated Use {@link #loadProperties(String)}
     */
    @Deprecated
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
    public void setClientName(String clientName){
        this.clientName  = clientName;
    }

    @Override
    public final String getClientName() {
        return clientName;
    }

    @Override
    public String getNameSpace() {
        return namespace;
    }

    @Override
    public final void setNameSpace(String nameSpace) {
        this.namespace = nameSpace;
    }

    @Override
    public void loadProperties(String clientName) {
109 110
        Preconditions.checkState(isLoaded == false, "Config '{}' can only be loaded once", clientName);
        if (!isLoaded) {
111
            loadDefaultValues();
112 113 114 115
            this.isLoaded = true;
            resolver.onChange(this::reload);
        }

116 117 118 119 120
        this.clientName = clientName;
    }

    @Override
    public final Map<String, Object> getProperties() {
121 122 123 124 125 126 127 128
        final Map<String, Object> result = new HashMap<>(dynamicProperties.size());
        dynamicProperties.forEach((key, value) -> {
            Object v = value.get().orElse(null);
            if (v != null) {
                result.put(key.key(), String.valueOf(v));
            }
        });
        return result;
129 130 131 132
    }

    @Override
    public void forEach(BiConsumer<IClientConfigKey<?>, Object> consumer) {
E
elandau 已提交
133
        dynamicProperties.forEach((key, value) -> consumer.accept(key, value.get().orElse(null)));
134 135
    }

136
    private <T> ReloadableProperty<T> createProperty(final Supplier<Optional<T>> valueSupplier, final Supplier<T> defaultSupplier) {
137 138 139 140 141 142 143
        Preconditions.checkNotNull(valueSupplier, "defaultValueSupplier cannot be null");

        return new ReloadableProperty<T>() {
            private volatile Optional<T> value = Optional.empty();

            {
                refresh();
144
                changeActions.add(this::refresh);
145 146 147 148
            }

            @Override
            public void onChange(Consumer<T> consumer) {
149
                final AtomicReference<Optional<T>> previous = new AtomicReference<>(get());
150
                changeActions.add(() -> {
151
                    Optional<T> current = get();
152 153
                    if (!current.equals(Optional.ofNullable(previous.get()))) {
                        previous.set(current);
154
                        consumer.accept(current.orElseGet(defaultSupplier::get));
155 156 157 158 159
                    }
                });
            }

            @Override
160 161
            public Optional<T> get() {
                return value;
162 163 164
            }

            @Override
165 166
            public T getOrDefault() {
                return value.orElse(defaultSupplier.get());
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
            }

            @Override
            public void refresh() {
                refreshCounter.incrementAndGet();
                value = valueSupplier.get();
            }

            @Override
            public String toString() {
                return String.valueOf(get());
            }
        };
    }

    @Override
    public final <T> T get(IClientConfigKey<T> key) {
184
        return getOrCreateProperty(key).get().orElse(null);
185 186
    }

187 188
    private final <T> ReloadableProperty<T> getOrCreateProperty(IClientConfigKey<T> key) {
        return (ReloadableProperty<T>) dynamicProperties.computeIfAbsent(key, ignore -> getClientDynamicProperty(key));
189 190 191 192 193 194
    }

    @Override
    public final <T> Property<T> getGlobalProperty(IClientConfigKey<T> key) {
        LOG.debug("Get global property {} default {}", key.key(), key.defaultValue());

195
        return (Property<T>) dynamicProperties.computeIfAbsent(key, ignore -> createProperty(
E
elandau 已提交
196
                () -> resolver.get(key.key(), key.type()),
197
                key::defaultValue));
198 199 200 201 202 203
    }

    interface ReloadableProperty<T> extends Property<T> {
        void refresh();
    }

204
    private <T> ReloadableProperty<T> getClientDynamicProperty(IClientConfigKey<T> key) {
205 206 207 208
        LOG.debug("Get dynamic property key={} ns={} client={}", key.key(), getNameSpace(), clientName);

        return createProperty(
                () -> resolveFinalProperty(key),
209
                key::defaultValue);
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    }

    /**
     * Resolve a properties final value in the following order or precedence
     * - client scope
     * - default scope
     * - internally set default
     * - IClientConfigKey defaultValue
     * @param key
     * @param <T>
     * @return
     */
    private <T> Optional<T> resolveFinalProperty(IClientConfigKey<T> key) {
        Optional<T> value;
        if (!StringUtils.isEmpty(clientName)) {
E
elandau 已提交
225
            value = resolver.get(clientName + "." + getNameSpace() + "." + key.key(), key.type());
226 227 228 229 230
            if (value.isPresent()) {
                return value;
            }
        }

E
elandau 已提交
231
        value = resolver.get(getNameSpace() + "." + key.key(), key.type());
232 233 234 235
        if (value.isPresent()) {
            return value;
        }

236
        return getIfSet(key);
237 238
    }

239 240 241 242 243 244
    /**
     * Resolve a p
     * @param key
     * @param <T>
     * @return
     */
245 246
    private <T> Optional<T> resolverScopedProperty(IClientConfigKey<T> key) {
        Optional<T> value = resolver.get(clientName + "." + getNameSpace() + "." + key.key(), key.type());
247 248 249 250
        if (value.isPresent()) {
            return value;
        }

251
        return getIfSet(key);
252 253
    }

254 255
    @Override
    public <T> Optional<T> getIfSet(IClientConfigKey<T> key) {
256
        return Optional.ofNullable(internalProperties.get(key))
257
                .map(value -> {
E
elandau 已提交
258
                    final Class<T> type = key.type();
259 260 261 262
                    // Unfortunately there's some legacy code setting string values for typed keys.  Here are do our best to parse
                    // and store the typed value
                    if (!value.getClass().equals(type)) {
                        try {
E
elandau 已提交
263 264 265
                            if (type.equals(String.class)) {
                                return (T) value.toString();
                            } else if (value.getClass().equals(String.class)) {
266 267 268 269 270 271 272 273 274 275 276 277 278 279
                                final String strValue = (String) value;
                                if (Integer.class.equals(type)) {
                                    return (T) Integer.valueOf(strValue);
                                } else if (Boolean.class.equals(type)) {
                                    return (T) Boolean.valueOf(strValue);
                                } else if (Float.class.equals(type)) {
                                    return (T) Float.valueOf(strValue);
                                } else if (Long.class.equals(type)) {
                                    return (T) Long.valueOf(strValue);
                                } else if (Double.class.equals(type)) {
                                    return (T) Double.valueOf(strValue);
                                } else if (TimeUnit.class.equals(type)) {
                                    return (T) TimeUnit.valueOf(strValue);
                                } else {
E
elandau 已提交
280
                                    return PropertyResolver.resolveWithValueOf(type, strValue)
E
elandau 已提交
281
                                        .orElseThrow(() -> new IllegalArgumentException("Unsupported value type `" + type + "'"));
282 283
                                }
                            } else {
284 285
                                return PropertyResolver.resolveWithValueOf(type, value.toString())
                                        .orElseThrow(() -> new IllegalArgumentException("Incompatible value type `" + value.getClass() + "` while expecting '" + type + "`"));
286 287 288 289 290 291 292 293 294 295 296 297
                            }
                        } catch (Exception e) {
                            throw new IllegalArgumentException("Error parsing value '" + value + "' for '" + key.key() + "'", e);
                        }
                    } else {
                        return (T)value;
                    }
                });
    }

    @Override
    public final <T> Property<T> getDynamicProperty(IClientConfigKey<T> key) {
298
        return getClientDynamicProperty(key);
299 300
    }

301 302 303 304
    @Override
    public <T> Property<T> getScopedProperty(IClientConfigKey<T> key) {
        return (Property<T>) dynamicProperties.computeIfAbsent(key, ignore -> createProperty(
                () -> resolverScopedProperty(key),
305
                key::defaultValue));
306 307
    }

308 309
    @Override
    public <T> Property<T> getPrefixMappedProperty(IClientConfigKey<T> key) {
310
        return (Property<T>) dynamicProperties.computeIfAbsent(key, ignore -> createProperty(
311
                getPrefixedMapPropertySupplier(key),
312
                key::defaultValue));
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
    }

    private <T> Supplier<Optional<T>> getPrefixedMapPropertySupplier(IClientConfigKey<T> key) {
        final Method method;
        try {
            method = key.type().getDeclaredMethod("valueOf", Map.class);
        } catch (NoSuchMethodException e) {
            throw new UnsupportedOperationException("Class '" + key.type().getName() + "' must have static method valueOf(Map<String, String>)", e);
        }

        return () -> {
            final Map<String, String> values = new HashMap<>();

            resolver.forEach(getNameSpace() + "." + key.key(), values::put);

            if (!StringUtils.isEmpty(clientName)) {
                resolver.forEach(clientName + "." + getNameSpace() + "." + key.key(), values::put);
            }

            try {
                return Optional.ofNullable((T)method.invoke(null, values));
            } catch (Exception e) {
                LOG.warn("Unable to map value for '{}'", key.key(), e);
                return Optional.empty();
            }
        };
    }

341 342 343 344 345 346 347 348 349 350
    @Override
    public final <T> T get(IClientConfigKey<T> key, T defaultValue) {
        return Optional.ofNullable(get(key)).orElse(defaultValue);
    }

    @Override
    public final <T> IClientConfig set(IClientConfigKey<T> key, T value) {
        Preconditions.checkArgument(key != null, "key cannot be null");
        // Treat nulls as deletes
        if (value == null) {
351
            internalProperties.remove(key.key());
352
        } else {
353
            internalProperties.put(key, value);
354 355
        }

356 357 358
        if (isLoaded) {
            getOrCreateProperty(key).refresh();
        }
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
        cachedToString = null;

        return this;
    }

    @Override
    @Deprecated
    public void setProperty(IClientConfigKey key, Object value) {
        Preconditions.checkArgument(value != null, "Value may not be null");
        set(key, value);
    }

    @Override
    @Deprecated
    public Object getProperty(IClientConfigKey key) {
374
        return get(key);
375 376 377 378 379
    }

    @Override
    @Deprecated
    public Object getProperty(IClientConfigKey key, Object defaultVal) {
380
        return getOrCreateProperty(key).get().orElse(defaultVal);
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
    }

    @Override
    @Deprecated
    public boolean containsProperty(IClientConfigKey key) {
        return dynamicProperties.containsKey(key.key());
    }

    @Override
    @Deprecated
    public int getPropertyAsInteger(IClientConfigKey key, int defaultValue) {
        return Optional.ofNullable(getProperty(key)).map(Integer.class::cast).orElse(defaultValue);
    }

    @Override
    @Deprecated
    public String getPropertyAsString(IClientConfigKey key, String defaultValue) {
        return Optional.ofNullable(getProperty(key)).map(Object::toString).orElse(defaultValue);
    }

    @Override
    @Deprecated
    public boolean getPropertyAsBoolean(IClientConfigKey key, boolean defaultValue) {
        return Optional.ofNullable(getProperty(key)).map(Boolean.class::cast).orElse(defaultValue);
    }

    public IClientConfig applyOverride(IClientConfig override) {
        if (override == null) {
            return this;
        }

412 413 414 415 416 417 418 419 420 421
        // When overriding we only really care of picking up properties that were explicitly set in code.  This is a
        // bit of an optimization to avoid excessive memory allocation as requests are made and overrides are applied
        if (override instanceof ReloadableClientConfig) {
            ((ReloadableClientConfig)override).internalProperties.forEach((key, value) -> {
                if (value != null) {
                    setProperty(key, value);
                }
            });
        }

422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
        return this;
    }

    private volatile String cachedToString = null;

    @Override
    public String toString() {
        if (cachedToString == null) {
            String newToString = generateToString();
            cachedToString = newToString;
            return newToString;
        }
        return cachedToString;
    }

    /**
     * @return Number of individual properties refreshed.  This can be used to identify patterns of excessive updates.
     */
    public long getRefreshCount() {
        return refreshCounter.get();
    }

    private String generateToString() {
        return "ClientConfig:" + dynamicProperties.entrySet().stream()
446
                    .map(t -> {
447 448 449
                        if (t.getKey().key().endsWith("Password")) {
                            return t.getKey() + ":***";
                        }
450
                        Optional value = t.getValue().get();
451
                        Object defaultValue = t.getKey().defaultValue();
452
                        return t.getKey() + ":" + value.orElse(defaultValue);
453 454 455 456
                    })
                .collect(Collectors.joining(", "));
    }
}