Add Support for Optional Bean Properties.

This commit is contained in:
Karthick Sankarachary 2016-05-27 11:31:03 -07:00
parent 596d6c9392
commit ad3f4804f4
6 changed files with 119 additions and 3 deletions
config
build.sbt
src
main/java/com/typesafe/config
test
java/beanconfig
resources/beanconfig
scala/com/typesafe/config/impl

View File

@ -20,6 +20,8 @@ fork in run in Test := true
autoScalaLibrary := false
crossPaths := false
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.3.2"
libraryDependencies += "javax.annotation" % "javax.annotation-api" % "1.2"
libraryDependencies += "net.liftweb" %% "lift-json" % "2.5" % "test"
libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test"

View File

@ -0,0 +1,11 @@
package com.typesafe.config;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Nullable {
}

View File

@ -1,9 +1,12 @@
package com.typesafe.config.impl;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
@ -21,6 +24,7 @@ import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigMemorySize;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;
import com.typesafe.config.Nullable;
/**
* Internal implementation detail, not ABI stable, do not touch.
@ -90,7 +94,9 @@ public class ConfigBeanImpl {
if (configValue != null) {
SimpleConfig.checkValid(path, expectedType, configValue, problems);
} else {
SimpleConfig.addMissing(problems, expectedType, path, config.origin());
if (!isOptionalProperty(clazz, beanProp)) {
SimpleConfig.addMissing(problems, expectedType, path, config.origin());
}
}
}
}
@ -105,8 +111,18 @@ public class ConfigBeanImpl {
Method setter = beanProp.getWriteMethod();
Type parameterType = setter.getGenericParameterTypes()[0];
Class<?> parameterClass = setter.getParameterTypes()[0];
Object unwrapped = getValue(clazz, parameterType, parameterClass, config, originalNames.get(beanProp.getName()));
setter.invoke(bean, unwrapped);
String configPropName = originalNames.get(beanProp.getName());
if (configPropName == null) {
configPropName = beanProp.getName();
}
try {
Object unwrapped = getValue(clazz, parameterType, parameterClass, config, configPropName);
setter.invoke(bean, unwrapped);
} catch (ConfigException.Missing e) {
if (!isOptionalProperty(clazz, beanProp)) {
throw e;
}
}
}
return bean;
} catch (InstantiationException e) {
@ -118,6 +134,11 @@ public class ConfigBeanImpl {
}
}
private static boolean isOptionalProperty(Class clazz, PropertyDescriptor beanProp) {
Field field = FieldUtils.getField(clazz, beanProp.getName(), true);
return (field.getAnnotationsByType(Nullable.class).length > 0);
}
// we could magically make this work in many cases by doing
// getAnyRef() (or getValue().unwrapped()), but anytime we
// rely on that, we aren't doing the type conversions Config

View File

@ -0,0 +1,68 @@
package beanconfig;
import com.typesafe.config.Nullable;
public class ObjectsConfig {
public static class ValueObject {
@Nullable
private String nullableValue;
private String nonNullableValue;
public String getNonNullableValue() {
return nonNullableValue;
}
public void setNonNullableValue(String nonNullableValue) {
this.nonNullableValue = nonNullableValue;
}
public String getNullableValue() {
return nullableValue;
}
public void setNullableValue(String nullableValue) {
this.nullableValue = nullableValue;
}
}
private ValueObject valueObject;
public ValueObject getValueObject() {
return valueObject;
}
public void setValueObject(ValueObject valueObject) {
this.valueObject = valueObject;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ObjectsConfig)) {
return false;
}
ObjectsConfig that = (ObjectsConfig) o;
return !(getValueObject() != null ? !getValueObject().equals(that.getValueObject()) : that.getValueObject() != null);
}
@Override
public int hashCode() {
return getValueObject() != null ? getValueObject().hashCode() : 0;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("ObjectsConfig{");
sb.append("innerType=").append(valueObject);
sb.append('}');
return sb.toString();
}
}

View File

@ -92,5 +92,10 @@
"configValue" : "hello world",
"list" : [1,2,3],
"unwrappedMap" : ${validation}
},
"objects" : {
"valueObject": {
"nonNullableValue": "nonNullValue"
}
}
}

View File

@ -162,6 +162,15 @@ class ConfigBeanFactoryTest extends TestUtils {
assertEquals(42, beanConfig.getUnwrappedMap.get("should-be-boolean"))
}
@Test
def testOptionalProperties() {
val beanConfig: ObjectsConfig = ConfigBeanFactory.create(loadConfig().getConfig("objects"), classOf[ObjectsConfig])
assertNotNull(beanConfig)
assertNotNull(beanConfig.getValueObject)
assertNull(beanConfig.getValueObject.getNullableValue)
assertEquals("nonNullValue", beanConfig.getValueObject.getNonNullableValue)
}
@Test
def testNotABeanField() {
val e = intercept[ConfigException.BadBean] {