diff --git a/archaius2-core/src/main/java/com/netflix/archaius/ConfigProxyFactory.java b/archaius2-core/src/main/java/com/netflix/archaius/ConfigProxyFactory.java index 8e669232..652afb00 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/ConfigProxyFactory.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/ConfigProxyFactory.java @@ -5,6 +5,7 @@ import com.netflix.archaius.api.Property; import com.netflix.archaius.api.PropertyFactory; import com.netflix.archaius.api.PropertyRepository; +import com.netflix.archaius.api.TypeConverter; import com.netflix.archaius.api.annotations.Configuration; import com.netflix.archaius.api.annotations.DefaultValue; import com.netflix.archaius.api.annotations.PropertyName; @@ -296,9 +297,11 @@ private MethodInvokerHolder buildInvokerForMethod(Class type, String pref // This object encapsulates the way to get the value for the current property. final PropertyValueGetter propertyValueGetter; - if (!knownCollections.containsKey(returnType) && returnType.isInterface()) { - // Our return type is an interface but not a known collection. We treat it as a nested Config proxy - // interface and create a proxy with it, with the current property name as the initial prefix for nesting. + if (!knownCollections.containsKey(returnType) + && returnType.isInterface() + && !(decoder instanceof TypeConverter.Registry && ((TypeConverter.Registry) decoder).get(m.getGenericReturnType()).isPresent())) { + // Our return type is an interface but not a known collection and is also not a type our decoder can handle. + // We treat it as a nested Config proxy interface and create a proxy with it, with the current property name as the initial prefix for nesting. propertyValueGetter = createInterfaceProperty(propName, newProxy(returnType, propName, immutable)); } else if (m.getParameterCount() > 0) { diff --git a/archaius2-core/src/main/java/com/netflix/archaius/DefaultDecoder.java b/archaius2-core/src/main/java/com/netflix/archaius/DefaultDecoder.java index f4bbb4b0..7d020e9a 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/DefaultDecoder.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/DefaultDecoder.java @@ -60,7 +60,12 @@ public T decode(Type type, String encoded) { if (encoded == null) { return null; } - return (T) getOrCreateConverter(type).convert(encoded); + @SuppressWarnings("unchecked") + TypeConverter converter = (TypeConverter) getOrCreateConverter(type); + if (converter == null) { + throw new RuntimeException("No converter found for type '" + type + "'"); + } + return converter.convert(encoded); } catch (Exception e) { throw new ParseException("Error decoding type `" + type.getTypeName() + "`", e); } @@ -76,7 +81,7 @@ private TypeConverter getOrCreateConverter(Type type) { if (converter == null) { converter = resolve(type); if (converter == null) { - throw new RuntimeException("No converter found for type '" + type + "'"); + return null; } TypeConverter existing = cache.putIfAbsent(type, converter); if (existing != null) { diff --git a/archaius2-core/src/test/java/com/netflix/archaius/DefaultDecoderTest.java b/archaius2-core/src/test/java/com/netflix/archaius/DefaultDecoderTest.java index 7e4144a6..9c3cfe67 100644 --- a/archaius2-core/src/test/java/com/netflix/archaius/DefaultDecoderTest.java +++ b/archaius2-core/src/test/java/com/netflix/archaius/DefaultDecoderTest.java @@ -89,4 +89,12 @@ public void testJavaMiscellaneous() throws DecoderException { Assert.assertEquals(URI.create("https://netflix.com"), decoder.decode(URI.class, "https://netflix.com")); Assert.assertEquals(Locale.ENGLISH, decoder.decode(Locale.class, "en")); } + + @Test + public void testTypeConverterRegistry() { + Assert.assertTrue(DefaultDecoder.INSTANCE.get(Instant.class).isPresent()); + + class Foo {} + Assert.assertFalse(DefaultDecoder.INSTANCE.get(Foo.class).isPresent()); + } } diff --git a/archaius2-core/src/test/java/com/netflix/archaius/ProxyFactoryTest.java b/archaius2-core/src/test/java/com/netflix/archaius/ProxyFactoryTest.java index 05c833d4..92069989 100644 --- a/archaius2-core/src/test/java/com/netflix/archaius/ProxyFactoryTest.java +++ b/archaius2-core/src/test/java/com/netflix/archaius/ProxyFactoryTest.java @@ -4,18 +4,23 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.nullValue; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.Nullable; +import com.netflix.archaius.api.Decoder; +import com.netflix.archaius.api.TypeConverter; +import com.netflix.archaius.config.MapConfig; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -574,4 +579,49 @@ public void testLogExcessiveUse() { } factory.newProxy(WithArguments.class, "somePrefix"); // This one should not log, because it's a new prefix. } + + interface ConfigWithNestedInterface { + int intValue(); + + CustomObject customValue(); + + interface CustomObject { + String value(); + } + } + + @Test + public void testNestedInterfaceWithCustomDecoder() { + TypeConverter customObjectTypeConverter = value -> value::toUpperCase; + + final class CustomDecoder implements Decoder, TypeConverter.Registry { + @Override + public T decode(Class type, String encoded) { + if (type.equals(ConfigWithNestedInterface.CustomObject.class)) { + @SuppressWarnings("unchecked") + T converted = (T) customObjectTypeConverter.convert(encoded); + return converted; + } + return DefaultDecoder.INSTANCE.decode(type, encoded); + } + + @Override + public Optional> get(Type type) { + if (type.equals(ConfigWithNestedInterface.CustomObject.class)) { + return Optional.of(customObjectTypeConverter); + } + return DefaultDecoder.INSTANCE.get(type); + } + } + Config config = MapConfig.builder() + .put("intValue", "5") + .put("customValue", "blah") + .build(); + config.setDecoder(new CustomDecoder()); + ConfigProxyFactory proxyFactory = new ConfigProxyFactory(config, config.getDecoder(), DefaultPropertyFactory.from(config)); + + ConfigWithNestedInterface proxy = proxyFactory.newProxy(ConfigWithNestedInterface.class); + Assert.assertEquals(5, proxy.intValue()); + Assert.assertEquals("BLAH", proxy.customValue().value()); + } }