Skip to content

Commit 15cfb49

Browse files
authored
Merge pull request #427 from elandau/bugfix/reduce_property_resolution
config: Fix property resolution when config not loaded
2 parents 782fb40 + 7c3e716 commit 15cfb49

File tree

8 files changed

+87
-147
lines changed

8 files changed

+87
-147
lines changed

ribbon-archaius/src/main/java/com/netflix/client/config/ArchaiusPropertyResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public <T> Optional<T> get(String key, Class<T> type) {
6464
if (type.equals(String.class)) {
6565
return (T)value;
6666
} else {
67-
return PropertyResolver.resolveWithValueOf(type, value)
67+
return PropertyUtils.resolveWithValueOf(type, value)
6868
.orElseThrow(() -> new IllegalArgumentException("Unable to convert value to desired type " + type));
6969
}
7070
});

ribbon-core/src/main/java/com/netflix/client/config/AbstractDefaultClientConfigImpl.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,6 @@ protected AbstractDefaultClientConfigImpl(PropertyResolver resolver) {
154154
super(resolver);
155155
}
156156

157-
protected AbstractDefaultClientConfigImpl(PropertyResolver resolver, String clientName) {
158-
super(resolver, clientName);
159-
}
160-
161157
public void setVipAddressResolver(VipAddressResolver resolver) {
162158
this.vipResolver = resolver;
163159
}

ribbon-core/src/main/java/com/netflix/client/config/IClientConfig.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,6 @@ default <T> Optional<T> getIfSet(IClientConfigKey<T> key) {
147147
*/
148148
<T> Property<T> getDynamicProperty(IClientConfigKey<T> key);
149149

150-
/**
151-
* @return Get a property that is only scoped to this client.
152-
*/
153-
default <T> Property<T> getScopedProperty(IClientConfigKey<T> key) {
154-
throw new UnsupportedOperationException();
155-
}
156-
157150
/**
158151
* @return Return a dynamically updated property that is a mapping of all properties prefixed by the key name to an
159152
* object with static method valueOf(Map{@literal <}String, String{@literal >})
Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
package com.netflix.client.config;
22

3-
import org.slf4j.Logger;
4-
import org.slf4j.LoggerFactory;
5-
6-
import java.lang.reflect.Method;
7-
import java.util.Map;
83
import java.util.Optional;
9-
import java.util.concurrent.ConcurrentHashMap;
104
import java.util.function.BiConsumer;
115

126
/**
137
* Internal abstraction to decouple the property source from Ribbon's internal configuration.
148
*/
159
public interface PropertyResolver {
16-
Logger LOG = LoggerFactory.getLogger(PropertyResolver.class);
17-
1810
/**
1911
* @return Get the value of a property or Optional.empty() if not set
2012
*/
@@ -30,29 +22,4 @@ public interface PropertyResolver {
3022
* @param action
3123
*/
3224
void onChange(Runnable action);
33-
34-
/**
35-
* Returns the internal property to the desiredn type
36-
*/
37-
Map<Class<?>, Optional<Method>> valueOfMethods = new ConcurrentHashMap<>();
38-
39-
static <T> Optional<T> resolveWithValueOf(Class<T> type, String value) {
40-
return valueOfMethods.computeIfAbsent(type, ignore -> {
41-
try {
42-
return Optional.of(type.getDeclaredMethod("valueOf", String.class));
43-
} catch (NoSuchMethodException e) {
44-
return Optional.empty();
45-
} catch (Exception e) {
46-
LOG.warn("Unable to determine if type " + type + " has a valueOf() static method", e);
47-
return Optional.empty();
48-
}
49-
}).map(method -> {
50-
try {
51-
return (T)method.invoke(null, value);
52-
} catch (Exception e) {
53-
throw new RuntimeException(e);
54-
}
55-
});
56-
}
57-
5825
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.netflix.client.config;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.lang.reflect.Method;
7+
import java.util.Map;
8+
import java.util.Optional;
9+
import java.util.concurrent.ConcurrentHashMap;
10+
11+
public class PropertyUtils {
12+
private static Logger LOG = LoggerFactory.getLogger(PropertyUtils.class);
13+
14+
private PropertyUtils() {}
15+
16+
/**
17+
* Returns the internal property to the desiredn type
18+
*/
19+
private static Map<Class<?>, Optional<Method>> valueOfMethods = new ConcurrentHashMap<>();
20+
21+
public static <T> Optional<T> resolveWithValueOf(Class<T> type, String value) {
22+
return valueOfMethods.computeIfAbsent(type, ignore -> {
23+
try {
24+
return Optional.of(type.getDeclaredMethod("valueOf", String.class));
25+
} catch (NoSuchMethodException e) {
26+
return Optional.empty();
27+
} catch (Exception e) {
28+
LOG.warn("Unable to determine if type " + type + " has a valueOf() static method", e);
29+
return Optional.empty();
30+
}
31+
}).map(method -> {
32+
try {
33+
return (T)method.invoke(null, value);
34+
} catch (Exception e) {
35+
throw new RuntimeException(e);
36+
}
37+
});
38+
}
39+
40+
}

ribbon-core/src/main/java/com/netflix/client/config/ReloadableClientConfig.java

Lines changed: 43 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import org.slf4j.LoggerFactory;
77

88
import java.lang.reflect.Method;
9-
import java.util.Collections;
109
import java.util.HashMap;
1110
import java.util.List;
1211
import java.util.Map;
@@ -40,7 +39,7 @@ public abstract class ReloadableClientConfig implements IClientConfig {
4039

4140
// Map of raw property names (without namespace or client name) to values. All values are non-null and properly
4241
// typed to match the key type
43-
private final Map<IClientConfigKey, Optional<Object>> internalProperties = new ConcurrentHashMap<>();
42+
private final Map<IClientConfigKey, Optional<?>> internalProperties = new ConcurrentHashMap<>();
4443

4544
private final Map<IClientConfigKey, ReloadableProperty<?>> dynamicProperties = new ConcurrentHashMap<>();
4645

@@ -52,31 +51,13 @@ public abstract class ReloadableClientConfig implements IClientConfig {
5251

5352
private final PropertyResolver resolver;
5453

55-
private String clientName;
54+
private String clientName = DEFAULT_CLIENT_NAME;
5655

5756
private String namespace = DEFAULT_NAMESPACE;
5857

5958
private boolean isLoaded = false;
6059

61-
/**
62-
* @deprecated Use {@link #ReloadableClientConfig(PropertyResolver, String, String)}
63-
*/
64-
@Deprecated
6560
protected ReloadableClientConfig(PropertyResolver resolver) {
66-
this(resolver, DEFAULT_CLIENT_NAME);
67-
}
68-
69-
/**
70-
* @deprecated Use {@link #ReloadableClientConfig(PropertyResolver, String, String)}
71-
*/
72-
@Deprecated
73-
protected ReloadableClientConfig(PropertyResolver resolver, String clientName) {
74-
this(resolver, DEFAULT_NAMESPACE, DEFAULT_CLIENT_NAME);
75-
}
76-
77-
protected ReloadableClientConfig(PropertyResolver resolver, String namespace, String clientName) {
78-
this.namespace = namespace;
79-
this.clientName = clientName;
8061
this.resolver = resolver;
8162
}
8263

@@ -119,10 +100,14 @@ public final void setNameSpace(String nameSpace) {
119100
@Override
120101
public void loadProperties(String clientName) {
121102
Preconditions.checkState(!isLoaded, "Config '{}' can only be loaded once", clientName);
103+
104+
LOG.info("Loading config for `{}`", clientName);
122105
this.clientName = clientName;
123106
this.isLoaded = true;
124107
loadDefaultValues();
125108
resolver.onChange(this::reload);
109+
110+
internalProperties.forEach((key, value) -> LOG.info("{} : {} -> {}", clientName, key, value.orElse(null)));
126111
}
127112

128113
/**
@@ -146,27 +131,29 @@ public void forEach(BiConsumer<IClientConfigKey<?>, Object> consumer) {
146131
}
147132

148133
/**
149-
* Create an action that will refresh the cached value for key. Uses the current value as a reference and will
134+
* Register an action that will refresh the cached value for key. Uses the current value as a reference and will
150135
* update from the dynamic property source to either delete or set a new value.
151136
*
152137
* @param key - Property key without client name or namespace
153-
* @param valueSupplier - Strategy for generating the value. Could potentially ready multiple property names, such
154-
* as default and client specific
155138
*/
156-
private <T> Runnable createAutoRefreshAction(final IClientConfigKey<T> key, final Supplier<Optional<T>> valueSupplier) {
157-
final AtomicReference<Optional<T>> current = new AtomicReference<>(valueSupplier.get());
158-
return () -> {
159-
final Optional<T> next = valueSupplier.get();
160-
if (!next.equals(current.get())) {
161-
LOG.debug("New value {}: {} -> {}", key.key(), current.get(), next);
162-
current.set(next);
163-
if (next.isPresent()) {
164-
internalProperties.put(key, (Optional<Object>) next);
165-
} else {
166-
internalProperties.put(key, Optional.empty());
167-
}
139+
private <T> void autoRefreshFromPropertyResolver(final IClientConfigKey<T> key) {
140+
changeActions.computeIfAbsent(key, ignore -> {
141+
final Supplier<Optional<T>> valueSupplier = () -> resolveFromPropertyResolver(key);
142+
final Optional<T> current = valueSupplier.get();
143+
if (current.isPresent()) {
144+
internalProperties.put(key, current);
168145
}
169-
};
146+
147+
final AtomicReference<Optional<T>> previous = new AtomicReference<>(current);
148+
return () -> {
149+
final Optional<T> next = valueSupplier.get();
150+
if (!next.equals(previous.get())) {
151+
LOG.info("New value {}: {} -> {}", key.key(), previous.get(), next);
152+
previous.set(next);
153+
internalProperties.put(key, next);
154+
}
155+
};
156+
});
170157
}
171158

172159
interface ReloadableProperty<T> extends Property<T> {
@@ -221,8 +208,12 @@ public String toString() {
221208
public final <T> T get(IClientConfigKey<T> key) {
222209
Optional<T> value = (Optional<T>)internalProperties.get(key);
223210
if (value == null) {
224-
set(key, null);
225-
value = (Optional<T>)internalProperties.get(key);
211+
if (!isLoaded) {
212+
return null;
213+
} else {
214+
set(key, null);
215+
value = (Optional<T>) internalProperties.get(key);
216+
}
226217
}
227218

228219
return value.orElse(null);
@@ -242,21 +233,13 @@ public final <T> Property<T> getGlobalProperty(IClientConfigKey<T> key) {
242233
public final <T> Property<T> getDynamicProperty(IClientConfigKey<T> key) {
243234
LOG.debug("Get dynamic property key={} ns={} client={}", key.key(), getNameSpace(), clientName);
244235

245-
get(key);
246-
247-
return getOrCreateProperty(
248-
key,
249-
() -> (Optional<T>)internalProperties.get(key),
250-
key::defaultValue);
251-
}
252-
253-
@Override
254-
public <T> Property<T> getScopedProperty(IClientConfigKey<T> key) {
255-
LOG.debug("Get dynamic property key={} ns={} client={}", key.key(), getNameSpace(), clientName);
236+
if (isLoaded) {
237+
autoRefreshFromPropertyResolver(key);
238+
}
256239

257240
return getOrCreateProperty(
258241
key,
259-
() -> resolverScopedProperty(key),
242+
() -> (Optional<T>)internalProperties.getOrDefault(key, Optional.empty()),
260243
key::defaultValue);
261244
}
262245

@@ -280,7 +263,7 @@ public <T> Property<T> getPrefixMappedProperty(IClientConfigKey<T> key) {
280263
* @param <T>
281264
* @return
282265
*/
283-
private <T> Optional<T> resolveFinalProperty(IClientConfigKey<T> key) {
266+
private <T> Optional<T> resolveFromPropertyResolver(IClientConfigKey<T> key) {
284267
Optional<T> value;
285268
if (!StringUtils.isEmpty(clientName)) {
286269
value = resolver.get(clientName + "." + getNameSpace() + "." + key.key(), key.type());
@@ -292,28 +275,9 @@ private <T> Optional<T> resolveFinalProperty(IClientConfigKey<T> key) {
292275
return resolver.get(getNameSpace() + "." + key.key(), key.type());
293276
}
294277

295-
/**
296-
* Resolve a p
297-
* @param key
298-
* @param <T>
299-
* @return
300-
*/
301-
private <T> Optional<T> resolverScopedProperty(IClientConfigKey<T> key) {
302-
Optional<T> value = resolver.get(clientName + "." + getNameSpace() + "." + key.key(), key.type());
303-
if (value.isPresent()) {
304-
return value;
305-
}
306-
307-
return getIfSet(key);
308-
}
309-
310278
@Override
311279
public <T> Optional<T> getIfSet(IClientConfigKey<T> key) {
312-
Optional<T> value = (Optional<T>)internalProperties.get(key);
313-
if (value == null) {
314-
return Optional.empty();
315-
}
316-
return value;
280+
return (Optional<T>)internalProperties.getOrDefault(key, Optional.empty());
317281
}
318282

319283
private <T> T resolveValueToType(IClientConfigKey<T> key, Object value) {
@@ -343,11 +307,11 @@ private <T> T resolveValueToType(IClientConfigKey<T> key, Object value) {
343307
} else if (TimeUnit.class.equals(type)) {
344308
return (T) TimeUnit.valueOf(strValue);
345309
} else {
346-
return PropertyResolver.resolveWithValueOf(type, strValue)
310+
return PropertyUtils.resolveWithValueOf(type, strValue)
347311
.orElseThrow(() -> new IllegalArgumentException("Unsupported value type `" + type + "'"));
348312
}
349313
} else {
350-
return PropertyResolver.resolveWithValueOf(type, value.toString())
314+
return PropertyUtils.resolveWithValueOf(type, value.toString())
351315
.orElseThrow(() -> new IllegalArgumentException("Incompatible value type `" + value.getClass() + "` while expecting '" + type + "`"));
352316
}
353317
} catch (Exception e) {
@@ -390,23 +354,15 @@ public final <T> T get(IClientConfigKey<T> key, T defaultValue) {
390354
}
391355

392356
@Override
393-
public final <T> IClientConfig set(IClientConfigKey<T> key, T value) {
357+
public <T> IClientConfig set(IClientConfigKey<T> key, T value) {
394358
Preconditions.checkArgument(key != null, "key cannot be null");
395359

396-
// Make sure the value is property typed.
397360
value = resolveValueToType(key, value);
398-
399-
// Check if there's a dynamic config override available
400-
value = resolveFinalProperty(key).orElse(value);
401-
402-
// Cache the new value
403-
internalProperties.put(key, Optional.ofNullable(value));
404-
405-
// If this is the first time a property is seen and a client name has been specified then make sure
406-
// we have the necessary refresh to pick up new values from dynamic config
407361
if (isLoaded) {
408-
changeActions.computeIfAbsent(key, ignore -> createAutoRefreshAction(key, () -> resolveFinalProperty(key)))
409-
.run();
362+
internalProperties.put(key, Optional.ofNullable(resolveFromPropertyResolver(key).orElse(value)));
363+
autoRefreshFromPropertyResolver(key);
364+
} else {
365+
internalProperties.put(key, Optional.ofNullable(value));
410366
}
411367
cachedToString = null;
412368

ribbon-core/src/test/java/com/netflix/client/config/ClientConfigTest.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -219,18 +219,6 @@ public void testValueOf() {
219219
Assert.assertEquals("value2", prop.getOrDefault().getValue());
220220
}
221221

222-
@Test
223-
public void testScopedProperty() {
224-
ConfigurationManager.getConfigInstance().setProperty("ribbon.foo.ScopePropertyTimeout", "2000");
225-
ConfigurationManager.getConfigInstance().setProperty("testScopedProperty.ribbon.foo.ScopePropertyTimeout", "1000");
226-
227-
DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl();
228-
clientConfig.loadProperties("testScopedProperty");
229-
230-
Property<Integer> prop = clientConfig.getScopedProperty(new CommonClientConfigKey<Integer>("foo.ScopePropertyTimeout", 0) {});
231-
Assert.assertEquals(1000, prop.get().get().intValue());
232-
}
233-
234222
@Test
235223
public void testDynamicConfig() {
236224
ConfigurationManager.getConfigInstance().setProperty("testValueOf.ribbon.CustomValueOf", "value");

ribbon-core/src/test/java/com/netflix/client/config/DefaultClientConfigImplTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ protected NewConfigKey(String configKey) {
2727

2828
@Test
2929
public void testTypedValue() {
30-
ConfigurationManager.getConfigInstance().setProperty("myclient.ribbon." + CommonClientConfigKey.ConnectTimeout, "1000");
30+
ConfigurationManager.getConfigInstance().setProperty("myclient.ribbon." + CommonClientConfigKey.ConnectTimeout, "1500");
3131
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
3232
config.loadProperties("myclient");
33-
assertEquals(1000, config.get(CommonClientConfigKey.ConnectTimeout).intValue());
33+
assertEquals(1500, config.get(CommonClientConfigKey.ConnectTimeout).intValue());
3434
config.set(CommonClientConfigKey.ConnectTimeout, 2000);
3535
// The archaius property should override code override
36-
assertEquals(1000, config.get(CommonClientConfigKey.ConnectTimeout).intValue());
36+
assertEquals(1500, config.get(CommonClientConfigKey.ConnectTimeout).intValue());
3737
}
3838

3939
@Test

0 commit comments

Comments
 (0)