/*
* Copyright 2010 Proofpoint, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.airlift.configuration;
import com.google.common.collect.ImmutableList;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.spi.Message;
import io.airlift.testing.Assertions;
import org.testng.Assert;
import org.testng.annotations.Test;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import static io.airlift.configuration.ConfigBinder.configBinder;
public class ConfigurationFactoryTest
{
@Test
public void testAnnotatedGettersThrows()
{
Map<String, String> properties = new TreeMap<>();
properties.put("string-value", "some value");
properties.put("boolean-value", "true");
TestMonitor monitor = new TestMonitor();
try {
createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(AnnotatedGetter.class));
Assert.fail("Expected an exception in object creation due to conflicting configuration");
} catch (CreationException e) {
monitor.assertNumberOfErrors(2);
Assertions.assertContainsAllOf(e.getMessage(), "not a valid setter", "getStringValue") ;
Assertions.assertContainsAllOf(e.getMessage(), "not a valid setter", "isBooleanValue") ;
}
}
@Test
public void testAnnotatedSetters()
{
Map<String, String> properties = new TreeMap<>();
properties.put("string-value", "some value");
properties.put("boolean-value", "true");
TestMonitor monitor = new TestMonitor();
Injector injector = createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(AnnotatedSetter.class));
AnnotatedSetter annotatedSetter = injector.getInstance(AnnotatedSetter.class);
monitor.assertNumberOfErrors(0);
monitor.assertNumberOfWarnings(0);
Assert.assertNotNull(annotatedSetter);
Assert.assertEquals(annotatedSetter.getStringValue(), "some value");
Assert.assertEquals(annotatedSetter.isBooleanValue(), true);
}
@Test
public void testConfigurationDespiteLegacyConfig()
{
Map<String, String> properties = new TreeMap<>();
properties.put("string-a", "this is a");
properties.put("string-b", "this is b");
TestMonitor monitor = new TestMonitor();
Injector injector = createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(LegacyConfigPresent.class));
LegacyConfigPresent legacyConfigPresent = injector.getInstance(LegacyConfigPresent.class);
monitor.assertNumberOfErrors(0);
monitor.assertNumberOfWarnings(0);
Assert.assertNotNull(legacyConfigPresent);
Assert.assertEquals(legacyConfigPresent.getStringA(), "this is a");
Assert.assertEquals(legacyConfigPresent.getStringB(), "this is b");
}
@Test
public void testConfigurationThroughLegacyConfig()
{
Map<String, String> properties = new TreeMap<>();
properties.put("string-value", "this is a");
properties.put("string-b", "this is b");
TestMonitor monitor = new TestMonitor();
Injector injector = createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(LegacyConfigPresent.class));
LegacyConfigPresent legacyConfigPresent = injector.getInstance(LegacyConfigPresent.class);
monitor.assertNumberOfErrors(0);
monitor.assertNumberOfWarnings(1);
monitor.assertMatchingWarningRecorded("string-value", "replaced", "Use 'string-a'");
Assert.assertNotNull(legacyConfigPresent);
Assert.assertEquals(legacyConfigPresent.getStringA(), "this is a");
Assert.assertEquals(legacyConfigPresent.getStringB(), "this is b");
}
@Test
public void testConfigurationWithRedundantLegacyConfig()
{
Map<String, String> properties = new TreeMap<>();
properties.put("string-value", "this is a");
properties.put("string-a", "this is a");
properties.put("string-b", "this is b");
TestMonitor monitor = new TestMonitor();
Injector injector = createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(LegacyConfigPresent.class));
LegacyConfigPresent legacyConfigPresent = injector.getInstance(LegacyConfigPresent.class);
monitor.assertNumberOfErrors(0);
monitor.assertNumberOfWarnings(1);
monitor.assertMatchingWarningRecorded("string-value", "replaced", "Use 'string-a'");
Assert.assertNotNull(legacyConfigPresent);
Assert.assertEquals(legacyConfigPresent.getStringA(), "this is a");
Assert.assertEquals(legacyConfigPresent.getStringB(), "this is b");
}
@Test
public void testConfigurationWithConflictingLegacyConfigThrows()
{
Map<String, String> properties = new TreeMap<>();
properties.put("string-value", "this is the old value");
properties.put("string-a", "this is a");
properties.put("string-b", "this is b");
TestMonitor monitor = new TestMonitor();
try {
createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(LegacyConfigPresent.class));
Assert.fail("Expected an exception in object creation due to conflicting configuration");
} catch (CreationException e) {
monitor.assertNumberOfErrors(1);
monitor.assertNumberOfWarnings(1);
monitor.assertMatchingWarningRecorded("string-value", "replaced", "Use 'string-a'");
Assertions.assertContainsAllOf(e.getMessage(), "string-value", "conflicts with property", "string-a") ;
}
}
@Test
public void testConfigurationDespiteDeprecatedConfig()
{
Map<String, String> properties = new TreeMap<>();
properties.put("string-b", "this is b");
TestMonitor monitor = new TestMonitor();
Injector injector = createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(DeprecatedConfigPresent.class));
DeprecatedConfigPresent deprecatedConfigPresent = injector.getInstance(DeprecatedConfigPresent.class);
monitor.assertNumberOfErrors(0);
monitor.assertNumberOfWarnings(0);
Assert.assertNotNull(deprecatedConfigPresent);
Assert.assertEquals(deprecatedConfigPresent.getStringA(), "defaultA");
Assert.assertEquals(deprecatedConfigPresent.getStringB(), "this is b");
}
@Test
public void testConfigurationThroughDeprecatedConfig()
{
Map<String, String> properties = new TreeMap<>();
properties.put("string-a", "this is a");
properties.put("string-b", "this is b");
TestMonitor monitor = new TestMonitor();
Injector injector = createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(DeprecatedConfigPresent.class));
DeprecatedConfigPresent deprecatedConfigPresent = injector.getInstance(DeprecatedConfigPresent.class);
monitor.assertNumberOfErrors(0);
monitor.assertNumberOfWarnings(1);
monitor.assertMatchingWarningRecorded("string-a", "deprecated and should not be used");
Assert.assertNotNull(deprecatedConfigPresent);
Assert.assertEquals(deprecatedConfigPresent.getStringA(), "this is a");
Assert.assertEquals(deprecatedConfigPresent.getStringB(), "this is b");
}
@Test
public void testDefunctPropertyInConfigThrows()
{
Map<String, String> properties = new TreeMap<>();
properties.put("string-value", "this is a");
properties.put("defunct-value", "this shouldn't work");
TestMonitor monitor = new TestMonitor();
try {
createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(DefunctConfigPresent.class));
Assert.fail("Expected an exception in object creation due to use of defunct config");
} catch (CreationException e) {
monitor.assertNumberOfErrors(1);
monitor.assertNumberOfWarnings(0);
monitor.assertMatchingErrorRecorded("Defunct property", "'defunct-value", "cannot be configured");
}
}
@Test
public void testSuccessfulBeanValidation()
{
Map<String, String> properties = new HashMap<>();
properties.put("string-value", "has a value");
properties.put("int-value", "50");
TestMonitor monitor = new TestMonitor();
Injector injector = createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(BeanValidationClass.class));
BeanValidationClass beanValidationClass = injector.getInstance(BeanValidationClass.class);
monitor.assertNumberOfErrors(0);
monitor.assertNumberOfWarnings(0);
Assert.assertNotNull(beanValidationClass);
Assert.assertEquals(beanValidationClass.getStringValue(), "has a value");
Assert.assertEquals(beanValidationClass.getIntValue(), 50);
}
@Test
public void testFailedBeanValidation()
{
Map<String, String> properties = new HashMap<>();
// string-value left at invalid default
properties.put("int-value", "5000"); // out of range
TestMonitor monitor = new TestMonitor();
try {
createInjector(properties, monitor, binder -> configBinder(binder).bindConfig(BeanValidationClass.class));
} catch (CreationException e) {
monitor.assertNumberOfErrors(2);
monitor.assertNumberOfWarnings(0);
monitor.assertMatchingErrorRecorded("Invalid configuration property", "int-value", "must be less than or equal to 100", "BeanValidationClass");
monitor.assertMatchingErrorRecorded("Invalid configuration property", "string-value", "may not be null", "BeanValidationClass");
}
}
private static Injector createInjector(Map<String, String> properties, TestMonitor monitor, Module module)
{
ConfigurationFactory configurationFactory = new ConfigurationFactory(properties, null, monitor);
configurationFactory.registerConfigurationClasses(ImmutableList.of(module));
List<Message> messages = configurationFactory.validateRegisteredConfigurationProvider();
return Guice.createInjector(new ConfigurationModule(configurationFactory), module, new ValidationErrorModule(messages));
}
@SuppressWarnings("unused")
public static class AnnotatedGetter {
private String stringValue;
private boolean booleanValue;
@Config("string-value")
public String getStringValue()
{
return stringValue;
}
public void setStringValue(String stringValue)
{
this.stringValue = stringValue;
}
@Config("boolean-value")
public boolean isBooleanValue()
{
return booleanValue;
}
public void setBooleanValue(boolean booleanValue)
{
this.booleanValue = booleanValue;
}
}
public static class AnnotatedSetter {
private String stringValue;
private boolean booleanValue;
public String getStringValue()
{
return stringValue;
}
@Config("string-value")
public void setStringValue(String stringValue)
{
this.stringValue = stringValue;
}
public boolean isBooleanValue()
{
return booleanValue;
}
@Config("boolean-value")
public void setBooleanValue(boolean booleanValue)
{
this.booleanValue = booleanValue;
}
}
public static class LegacyConfigPresent
{
private String stringA = "defaultA";
private String stringB = "defaultB";
public String getStringA()
{
return stringA;
}
@Config("string-a")
@LegacyConfig("string-value")
public void setStringA(String stringValue)
{
this.stringA = stringValue;
}
public String getStringB()
{
return stringB;
}
@Config("string-b")
public void setStringB(String stringValue)
{
this.stringB = stringValue;
}
}
public static class DeprecatedConfigPresent
{
private String stringA = "defaultA";
private String stringB = "defaultB";
@Deprecated
public String getStringA()
{
return stringA;
}
@Deprecated
@Config("string-a")
public void setStringA(String stringValue)
{
this.stringA = stringValue;
}
public String getStringB()
{
return stringB;
}
@Config("string-b")
public void setStringB(String stringValue)
{
this.stringB = stringValue;
}
}
@SuppressWarnings("unused")
@DefunctConfig("defunct-value")
public static class DefunctConfigPresent
{
private String stringValue;
private boolean booleanValue;
public String getStringValue()
{
return stringValue;
}
@Config("string-value")
public void setStringValue(String stringValue)
{
this.stringValue = stringValue;
}
}
public static class BeanValidationClass
{
@NotNull
private String stringValue;
private int myIntValue;
public String getStringValue()
{
return stringValue;
}
@Config("string-value")
public void setStringValue(String value)
{
this.stringValue = value;
}
@Min(1)
@Max(100)
public int getIntValue()
{
return myIntValue;
}
@Config("int-value")
public void setIntValue(int value)
{
this.myIntValue = value;
}
}
}