/**
* Copyright 2015 StreamSets Inc.
*
* Licensed under the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.streamsets.datacollector.definition;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.streamsets.datacollector.config.ConfigDefinition;
import com.streamsets.datacollector.el.ElConstantDefinition;
import com.streamsets.datacollector.el.ElFunctionDefinition;
import com.streamsets.pipeline.api.ConfigDef;
import com.streamsets.pipeline.api.ConfigDefBean;
import com.streamsets.pipeline.api.Dependency;
import com.streamsets.pipeline.api.ElConstant;
import com.streamsets.pipeline.api.ElFunction;
import com.streamsets.pipeline.api.ElParam;
import com.streamsets.pipeline.api.FieldSelectorModel;
import com.streamsets.pipeline.api.PredicateModel;
import com.streamsets.pipeline.api.impl.ErrorMessage;
import org.junit.Assert;
import org.junit.Test;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TestConfigDefinitionExtractor {
public static class Empty {
}
public static class ELs {
@ElFunction(prefix = "p", name = "f", description = "ff")
public static String f(@ElParam("x") int x) {
return null;
}
@ElConstant(name = "C", description = "CC")
public static final String C = "c";
}
public static class Ok1 {
@ConfigDef(
label = "L",
description = "D",
type = ConfigDef.Type.NUMBER,
defaultValue = "10",
required = true,
group = "G",
displayPosition = 1,
lines = 2,
min = 3,
max = 4,
evaluation = ConfigDef.Evaluation.EXPLICIT,
mode = ConfigDef.Mode.JAVA,
elDefs = ELs.class
)
public int config;
}
public static class Ok2 {
@ConfigDef(
label = "L",
description = "D",
type = ConfigDef.Type.BOOLEAN,
required = true,
group = "G",
displayPosition = 1,
lines = 2,
evaluation = ConfigDef.Evaluation.EXPLICIT,
mode = ConfigDef.Mode.JAVA,
elDefs = ELs.class
)
public String config;
}
public static class Ok3 {
@ConfigDef(
label = "L",
description = "D",
type = ConfigDef.Type.MODEL,
required = true,
elDefs = ELs.class
)
@PredicateModel
public List<String> predicates;
}
public static class Ok4 {
@ConfigDef(
label = "L",
description = "D",
type = ConfigDef.Type.MODEL,
required = true,
elDefs = ELs.class
)
@FieldSelectorModel
public String selector;
}
@Test
public void testStringConfigOk() {
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(Ok1.class, Collections.<String>emptyList(),
"x");
Assert.assertEquals(1, configs.size());
ConfigDefinition config = configs.get(0);
Assert.assertEquals("config", config.getName());
Assert.assertEquals("config", config.getFieldName());
Assert.assertEquals(ConfigDef.Type.NUMBER, config.getType());
Assert.assertEquals("L", config.getLabel());
Assert.assertEquals("D", config.getDescription());
Assert.assertEquals(10, config.getDefaultValue());
Assert.assertEquals(true, config.isRequired());
Assert.assertEquals("G", config.getGroup());
Assert.assertEquals(1, config.getDisplayPosition());
Assert.assertEquals(2, config.getLines());
Assert.assertEquals(3, config.getMin());
Assert.assertEquals(4, config.getMax());
Assert.assertEquals(ConfigDef.Evaluation.EXPLICIT, config.getEvaluation());
Assert.assertEquals("text/x-java", config.getMode());
Assert.assertTrue(containsF(config.getElFunctionDefinitions(), "p:f"));
Assert.assertTrue(containsC(config.getElConstantDefinitions(), "C"));
Assert.assertNull(config.getModel());
Assert.assertEquals("", config.getDependsOn());
Assert.assertNull(config.getTriggeredByValues());
}
@Test
public void testTypeNoELConfigOk() {
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(Ok2.class, Collections.<String>emptyList(),
"x");
Assert.assertEquals(1, configs.size());
ConfigDefinition config = configs.get(0);
Assert.assertEquals(ConfigDef.Type.BOOLEAN, config.getType());
Assert.assertNull(config.getDefaultValue());
Assert.assertTrue(config.getElFunctionDefinitions().isEmpty());
Assert.assertTrue(config.getElConstantDefinitions().isEmpty());
}
@Test
public void testModelNoELConfigOk() {
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(Ok4.class, Collections.<String>emptyList(),
"x");
Assert.assertEquals(1, configs.size());
ConfigDefinition config = configs.get(0);
Assert.assertEquals(ConfigDef.Type.MODEL, config.getType());
Assert.assertTrue(config.getElFunctionDefinitions().isEmpty());
Assert.assertTrue(config.getElConstantDefinitions().isEmpty());
}
@Test
public void testModelELConfigOk() {
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(Ok3.class, Collections.<String>emptyList(),
"x");
Assert.assertEquals(1, configs.size());
ConfigDefinition config = configs.get(0);
Assert.assertEquals(ConfigDef.Type.MODEL, config.getType());
Assert.assertFalse(config.getElFunctionDefinitions().isEmpty());
Assert.assertFalse(config.getElConstantDefinitions().isEmpty());
}
private boolean containsF(List<ElFunctionDefinition> defs, String name) {
for (ElFunctionDefinition def : defs) {
if (def.getName().equals(name)) {
return true;
}
}
return false;
}
private boolean containsC(List<ElConstantDefinition> defs, String name) {
for (ElConstantDefinition def : defs) {
if (def.getName().equals(name)) {
return true;
}
}
return false;
}
public static class Model {
@ConfigDef(
label = "L",
type = ConfigDef.Type.MODEL,
required = true
)
@FieldSelectorModel
public String config;
}
@Test
public void testModel() {
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(Model.class, Collections.<String>emptyList(),
"x");
Assert.assertEquals(1, configs.size());
ConfigDefinition config = configs.get(0);
Assert.assertEquals(ConfigDef.Type.MODEL, config.getType());
Assert.assertNotNull(config.getModel());
}
public static class Fail2 {
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = false
)
public static String config;
}
@Test(expected = IllegalArgumentException.class)
public void testStringConfigFail2() {
ConfigDefinitionExtractor.get().extract(Fail2.class, Collections.<String>emptyList(), "x");
}
public static class Fail3 {
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = false,
min = 0
)
public static String config;
}
@Test(expected = IllegalArgumentException.class)
public void testStringConfigFail3() {
ConfigDefinitionExtractor.get().extract(Fail3.class, Collections.<String>emptyList(), "x");
}
public static class DependsOn {
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependsOn = "b",
triggeredByValue = "B"
)
public String a;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true
)
public String b;
}
@Test
public void testDependsOn() {
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(DependsOn.class,
Collections.<String>emptyList(),"x");
Assert.assertEquals(2, configs.size());
ConfigDefinition cd1 = configs.get(0);
ConfigDefinition cd2 = configs.get(1);
ConfigDefinition a;
ConfigDefinition b;
if (cd1.getName().equals("a")) {
a = cd1;
b = cd2;
} else {
a = cd2;
b = cd1;
}
Assert.assertEquals(1, a.getDependsOnMap().size());
Assert.assertEquals(ImmutableSet.of("B"), new HashSet<>(a.getDependsOnMap().get("b")));
Assert.assertEquals("", b.getDependsOn());
Assert.assertNull(b.getTriggeredByValues());
}
public static class MultipleDependsOn {
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependencies = {
@Dependency(configName = "b", triggeredByValues = {"B", "C"}),
@Dependency(configName = "e", triggeredByValues = {"E", "F"})
}
)
public String a;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true
)
public String b;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true
)
public String e;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependsOn = "a",
triggeredByValue = "A"
)
public String f;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependencies = {
@Dependency(configName = "a", triggeredByValues = {"A"}),
@Dependency(configName = "e", triggeredByValues = {"G"}),
@Dependency(configName = "f", triggeredByValues = {"F", "FF"})
}
)
public String g;
}
@Test
public void testMultipleDependsOn() {
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(MultipleDependsOn.class,
Collections.<String>emptyList(),"x");
Assert.assertEquals(5, configs.size());
ConfigDefinition a = null;
ConfigDefinition b = null;
ConfigDefinition e = null;
ConfigDefinition f = null;
ConfigDefinition g = null;
for (ConfigDefinition config : configs) {
switch (config.getName()) {
case ("a"):
a = config;
break;
case ("b"):
b = config;
break;
case ("e"):
e = config;
break;
case ("f"):
f = config;
break;
default:
g = config;
}
}
Assert.assertEquals(2, a.getDependsOnMap().size());
Assert.assertEquals(ImmutableSet.of("B", "C"), new HashSet<>(a.getDependsOnMap().get("b")));
Assert.assertEquals(ImmutableSet.of("E", "F"), new HashSet<>(a.getDependsOnMap().get("e")));
Assert.assertEquals(3, f.getDependsOnMap().size());
Assert.assertEquals(ImmutableSet.of("A"), new HashSet<>(f.getDependsOnMap().get("a")));
Assert.assertEquals(ImmutableSet.of("B", "C"), new HashSet<>(f.getDependsOnMap().get("b")));
Assert.assertEquals(ImmutableSet.of("E", "F"), new HashSet<>(f.getDependsOnMap().get("e")));
Assert.assertEquals(4, g.getDependsOnMap().size());
Assert.assertEquals(ImmutableSet.of("A"), new HashSet<>(g.getDependsOnMap().get("a")));
Assert.assertEquals(ImmutableSet.of("B", "C"), new HashSet<>(g.getDependsOnMap().get("b")));
Assert.assertEquals(ImmutableSet.of("F", "FF"), new HashSet<>(g.getDependsOnMap().get("f")));
Assert.assertTrue(b.getDependsOnMap().isEmpty());
Assert.assertTrue(e.getDependsOnMap().isEmpty());
}
public static class DependsOnChain {
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependsOn = "b",
triggeredByValue = "B"
)
public String a;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependsOn = "c",
triggeredByValue = "C"
)
public String b;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true
)
public String c;
}
@Test
public void testDependsOnChain() {
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(DependsOnChain.class,
Collections.<String>emptyList(), "x");
Assert.assertEquals(3, configs.size());
ConfigDefinition a = null;
for (ConfigDefinition cd : configs) {
if (cd.getName().equals("a")) {
a = cd;
}
}
Assert.assertNotNull(a);
Assert.assertEquals(2, a.getDependsOnMap().size());
Assert.assertEquals(ImmutableSet.of("B"), new HashSet<>(a.getDependsOnMap().get("b")));
Assert.assertEquals(ImmutableSet.of("C"), new HashSet<>(a.getDependsOnMap().get("c")));
}
public static class CycleDependencies {
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependencies = {
@Dependency(configName = "c", triggeredByValues = {"anything"}),
}
)
public String a;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependsOn = "t",
triggeredByValue = "f"
)
public String c;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependsOn = "f",
triggeredByValue = "o"
)
public String b;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependencies = {
@Dependency(configName = "g", triggeredByValues = "random")
},
dependsOn = "b",
triggeredByValue = "really don't care"
)
public String e;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependencies = {
@Dependency(configName = "b", triggeredByValues = "random"),
@Dependency(configName = "e", triggeredByValues = "dds"),
@Dependency(configName = "f", triggeredByValues = "f"),
@Dependency(configName = "g", triggeredByValues = "g")
}
)
public String f;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependencies = {
@Dependency(configName = "a", triggeredByValues = {"A"}),
@Dependency(configName = "c", triggeredByValues = {"A"}),
}
)
public String g;
@ConfigDef(
label = "L",
type = ConfigDef.Type.STRING,
required = true,
dependsOn = "g",
triggeredByValue = "y"
)
public String t;
}
@Test
public void testDependsOnCycle() {
try {
ConfigDefinitionExtractor.get().extract(CycleDependencies.class, Collections.<String>emptyList(), "x");
Assert.fail();
} catch (IllegalStateException ex) {
Set<String> cycles = ConfigDefinitionExtractor.get().getCycles();
Assert.assertEquals(5, cycles.size());
Assert.assertTrue(cycles.contains("a -> c -> t -> g -> a"));
Assert.assertTrue(cycles.contains("b -> f -> e -> b"));
Assert.assertTrue(cycles.contains("c -> t -> g -> c"));
Assert.assertTrue(cycles.contains("b -> f -> b"));
Assert.assertTrue(cycles.contains("f -> f"));
}
}
public static class SubSubBean {
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.NUMBER )
public long prop5;
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.NUMBER,
dependsOn = "prop5",
triggeredByValue = "1",
group = "foo"
)
public long prop6;
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.NUMBER,
dependsOn = "^prop2.prop3",
triggeredByValue = "2",
group = "#1"
)
public long prop7;
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.NUMBER,
dependsOn = "prop1^^",
triggeredByValue = "3"
)
public long prop8;
}
public static class SubBean {
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.NUMBER,
group = "foo"
)
public long prop3;
@ConfigDefBean
public SubSubBean prop4;
}
public static class Bean {
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING
)
public String prop1;
@ConfigDefBean(groups = { "foo", "#0"})
public SubBean prop2;
}
@Test
public void testConfigDefBean() {
List<ErrorMessage> errors = ConfigDefinitionExtractor.get().validate(Bean.class, ImmutableList.of("bar", "foo"),
"x");
Assert.assertTrue(errors.isEmpty());
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(Bean.class,
ImmutableList.of("bar", "foo"), "x");
Assert.assertEquals(6, configs.size());
Set<String> expectedNames = ImmutableSet.of("prop1", "prop2.prop3", "prop2.prop4.prop5", "prop2.prop4.prop6",
"prop2.prop4.prop7", "prop2.prop4.prop8");
Set<String> expectedFieldNames = ImmutableSet.of("prop1", "prop3", "prop5", "prop6", "prop7", "prop8");
Set<String> gotNames = new HashSet<>();
Set<String> gotFieldNames = new HashSet<>();
for (ConfigDefinition cd : configs) {
gotNames.add(cd.getName());
gotFieldNames.add(cd.getFieldName());
}
Assert.assertEquals(expectedNames, gotNames);
Assert.assertEquals(expectedFieldNames, gotFieldNames);
}
@Test
public void testGroupsResolution() {
Assert.assertTrue(ConfigDefinitionExtractor.get().validate(Bean.class, ImmutableList.of("bar", "foo"),
"x").isEmpty());
List<ConfigDefinition> configs = ConfigDefinitionExtractor.get().extract(Bean.class,
ImmutableList.of("bar", "foo"), "x");
Map<String, ConfigDefinition> configMap = new HashMap<>();
for (ConfigDefinition config : configs) {
configMap.put(config.getName(), config);
}
Assert.assertEquals("", configMap.get("prop1").getGroup());
Assert.assertEquals("foo", configMap.get("prop2.prop3").getGroup());
Assert.assertEquals("", configMap.get("prop2.prop4.prop5").getGroup());
Assert.assertEquals("foo", configMap.get("prop2.prop4.prop6").getGroup());
Assert.assertEquals("bar", configMap.get("prop2.prop4.prop7").getGroup());
}
public static class SubBean1 {
public String prop4;
}
public static class Bean1 {
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING
)
public String prop1;
@ConfigDefBean
public SubBean1 prop2;
}
@Test
public void testConfigDefBeanInvalidBeanWithoutConfig() {
Assert.assertEquals(1, ConfigDefinitionExtractor.get().validate(Bean1.class, Collections.<String>emptyList(),
"x").size());
}
public static class SubBean2 {
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING,
dependsOn = "props^^"
)
public String prop3;
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING,
dependsOn = "^props^^"
)
public String prop4;
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING,
dependsOn = "props^3^"
)
public String prop5;
}
public static class Bean2 {
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING
)
public String prop1;
@ConfigDefBean
public SubBean2 prop2;
}
@Test
public void testConfigDefBeanInvalidDependsOn() {
List<ErrorMessage> errorMessages = ConfigDefinitionExtractor.get().validate(Bean2.class, Collections.<String>emptyList(),
"x");
Assert.assertEquals(3, errorMessages.size());
}
public static class SubBean3 {
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING,
dependsOn = "props^"
)
public String prop3;
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING,
dependencies = {
@Dependency(configName = "props3", triggeredByValues = {"someval1"}),
@Dependency(configName = "props6", triggeredByValues = {"someval2"})
}
)
public String prop6;
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING,
dependencies = {
@Dependency(configName = "propInt^", triggeredByValues = {"testing"})
}
)
public String prop7;
}
public static class Bean3 {
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.STRING
)
public String prop1;
@ConfigDef(
label = "L",
required = true,
type = ConfigDef.Type.NUMBER
)
public int propInt;
@ConfigDefBean
public SubBean3 prop2;
}
@Test
public void testConfigDefBeanNonExistentDependsOn() {
List<ErrorMessage> errorMessages =
ConfigDefinitionExtractor.get().validate(Bean3.class, Collections.<String>emptyList(), "x");
Assert.assertEquals(4, errorMessages.size());
}
}