Skip to content

Commit add6cf1

Browse files
Throw ParseException when a configuration setting can't be parsed as the type requested.
Homogenize the handling of parsing errors. Before this change, some code paths in the default Property implementation would silently swallow parsing errors and either return a defaultValue or null (even for non-nullable methods!) This leads to errors that are hard to debug. Throwing a ParseException is a behaviour change that will lead to simpler debugging, at the expense of surfacing latent bugs in user code. For example, if bad values were being set in config files, the code will now fail instead of silently returning a different value than expected.
1 parent 3191c48 commit add6cf1

File tree

8 files changed

+432
-493
lines changed

8 files changed

+432
-493
lines changed

archaius2-api/src/main/java/com/netflix/archaius/api/PropertyFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
@Deprecated
2727
public interface PropertyFactory extends PropertyRepository {
2828
/**
29-
* Create a property for the property name.
30-
* @deprecated Use {@link PropertyRepository#get(String, Type)} instead.
29+
* Create a {@link PropertyContainer} for the given name.
30+
* @deprecated Use {@link PropertyRepository#get(String, Type)} or {@link PropertyRepository#get(String, Class)} instead.
3131
*/
3232
@Deprecated
3333
PropertyContainer getProperty(String propName);

archaius2-archaius1-bridge/src/test/java/com/netflix/archaius/bridge/DynamicPropertyTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package com.netflix.archaius.bridge;
22

3+
import com.netflix.archaius.api.PropertyRepository;
34
import org.apache.commons.configuration.AbstractConfiguration;
45

56
import com.google.inject.Guice;
67
import com.google.inject.Injector;
78
import com.google.inject.Key;
89
import com.netflix.archaius.api.Config;
910
import com.netflix.archaius.api.Property;
10-
import com.netflix.archaius.api.PropertyFactory;
1111
import com.netflix.archaius.api.config.SettableConfig;
1212
import com.netflix.archaius.api.inject.RuntimeLayer;
1313
import com.netflix.archaius.guice.ArchaiusModule;
@@ -44,7 +44,7 @@ public void after() {
4444
@Test
4545
public void settingOnArchaius2UpdateArchaius1(TestInfo testInfo) {
4646
String methodName = testInfo.getTestMethod().map(Method::getName).orElse("unknown");
47-
Property<String> a2prop = injector.getInstance(PropertyFactory.class).getProperty(methodName).asString("default");
47+
Property<String> a2prop = injector.getInstance(PropertyRepository.class).get(methodName, String.class).orElse("default");
4848
DynamicStringProperty a1prop = DynamicPropertyFactory.getInstance().getStringProperty(methodName, "default");
4949

5050
assertEquals("default", a1prop.get());
@@ -63,7 +63,7 @@ public void testNonStringDynamicProperty() {
6363
config.accept(new PrintStreamVisitor());
6464
ConfigurationManager.getConfigInstance().setProperty("foo", 123);
6565

66-
Property<Integer> prop2 = injector.getInstance(PropertyFactory.class).getProperty("foo").asInteger(1);
66+
Property<Integer> prop2 = injector.getInstance(PropertyRepository.class).get("foo", Integer.class).orElse(1);
6767

6868
DynamicIntProperty prop = DynamicPropertyFactory.getInstance().getIntProperty("foo", 2);
6969

archaius2-core/src/main/java/com/netflix/archaius/DefaultPropertyFactory.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.netflix.archaius.api.PropertyContainer;
77
import com.netflix.archaius.api.PropertyFactory;
88
import com.netflix.archaius.api.PropertyListener;
9+
import com.netflix.archaius.exceptions.ParseException;
910
import org.slf4j.Logger;
1011
import org.slf4j.LoggerFactory;
1112

@@ -62,6 +63,7 @@ public DefaultPropertyFactory(Config config) {
6263
this.config.addListener(this);
6364
}
6465

66+
/** @deprecated Use {@link #get(String, Type)} or {@link #get(String, Class)} instead. */
6567
@Override
6668
@Deprecated
6769
@SuppressWarnings("deprecation")
@@ -124,20 +126,24 @@ public <T> Property<T> asType(Class<T> type, T defaultValue) {
124126

125127
@Override
126128
public <T> Property<T> asType(Function<String, T> mapper, String defaultValue) {
127-
T typedDefaultValue = mapper.apply(defaultValue);
129+
T typedDefaultValue = applyOrThrow(mapper, defaultValue);
128130
return getFromSupplier(propName, null, () -> {
129131
String value = config.getString(propName, null);
130132
if (value != null) {
131-
try {
132-
return mapper.apply(value);
133-
} catch (Exception e) {
134-
LOG.error("Invalid value '{}' for property '{}'. Will return the default instead.", propName, value);
135-
}
133+
return applyOrThrow(mapper, value);
136134
}
137135

138136
return typedDefaultValue;
139137
});
140138
}
139+
140+
private <T> T applyOrThrow(Function<String, T> mapper, String value) {
141+
try {
142+
return mapper.apply(value);
143+
} catch (RuntimeException e) {
144+
throw new ParseException("Invalid value '" + value + "' for property '" + propName + "'.", e);
145+
}
146+
}
141147
};
142148
}
143149

@@ -208,23 +214,28 @@ public PropertyImpl(KeyAndType<T> keyAndType, Supplier<T> supplier) {
208214

209215
@Override
210216
public T get() {
211-
int cacheVersion = cache.getStamp();
217+
int[] cacheVersion = new int[1];
218+
T currentValue = cache.get(cacheVersion);
212219
int latestVersion = masterVersion.get();
213-
214-
if (cacheVersion != latestVersion) {
215-
T currentValue = cache.getReference();
216-
T newValue = null;
217-
try {
218-
newValue = supplier.get();
219-
} catch (Exception e) {
220-
LOG.error("Unable to get current version of property '{}'", keyAndType.key, e);
221-
}
222-
223-
if (cache.compareAndSet(currentValue, newValue, cacheVersion, latestVersion)) {
224-
// Possible race condition here but not important enough to warrant locking
220+
221+
if (cacheVersion[0] == latestVersion) {
222+
return currentValue;
223+
}
224+
225+
try {
226+
T newValue = supplier.get();
227+
228+
if (cache.compareAndSet(currentValue, newValue, cacheVersion[0], latestVersion)) {
229+
// newValue could be stale here already, if the cache was updated *again* between the CAS and this line
230+
// We don't care enough about this edge case to fix it.
225231
return newValue;
226232
}
233+
234+
} catch (RuntimeException e) {
235+
LOG.error("Unable to get current version of property '{}'", keyAndType.key, e);
236+
throw e; // Rethrow the exception, our caller should know that the property is not available
227237
}
238+
228239
return cache.getReference();
229240
}
230241

archaius2-core/src/test/java/com/netflix/archaius/ConfigManagerTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import java.util.concurrent.atomic.AtomicReference;
2121

2222
import com.netflix.archaius.api.Property;
23-
import com.netflix.archaius.api.PropertyFactory;
23+
import com.netflix.archaius.api.PropertyRepository;
2424
import com.netflix.archaius.config.DefaultCompositeConfig;
2525

2626
import com.netflix.archaius.config.MapConfig;
@@ -45,9 +45,9 @@ public void testBasicReplacement() throws ConfigException {
4545
.build()));
4646

4747

48-
PropertyFactory factory = DefaultPropertyFactory.from(config);
48+
PropertyRepository factory = DefaultPropertyFactory.from(config);
4949

50-
Property<String> prop = factory.getProperty("abc").asString("defaultValue");
50+
Property<String> prop = factory.get("abc", String.class).orElse("defaultValue");
5151

5252
// Use an AtomicReference to capture the property value change
5353
AtomicReference<String> capturedValue = new AtomicReference<>("");

archaius2-core/src/test/java/com/netflix/archaius/DefaultPropertyContainerTest.java

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)