Implement ConfigKey.typeInheritance
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/6a1ac3f2 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/6a1ac3f2 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/6a1ac3f2 Branch: refs/heads/master Commit: 6a1ac3f2783d59d9cc9ff15f3e35614de864ed2d Parents: 57beafa Author: Aled Sage <[email protected]> Authored: Fri May 27 09:44:09 2016 +0100 Committer: Aled Sage <[email protected]> Committed: Mon Jun 6 15:10:09 2016 +0100 ---------------------------------------------------------------------- .../internal/AbstractBrooklynObjectSpec.java | 5 + .../BrooklynComponentTemplateResolver.java | 105 ++++++++++++++-- .../brooklyn/ConfigInheritanceYamlTest.java | 124 ++++++++++++++++++- .../brooklyn/util/core/flags/FlagUtils.java | 31 ++++- .../util/core/internal/FlagUtilsTest.java | 96 +++++++++++++- 5 files changed, 343 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6a1ac3f2/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java ---------------------------------------------------------------------- diff --git a/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java b/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java index ef99a27..a022883 100644 --- a/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java +++ b/api/src/main/java/org/apache/brooklyn/api/internal/AbstractBrooklynObjectSpec.java @@ -303,6 +303,11 @@ public abstract class AbstractBrooklynObjectSpec<T,SpecT extends AbstractBrookly return self(); } + public <V> SpecT removeFlag(String key) { + flags.remove( checkNotNull(key, "key") ); + return self(); + } + /** Clears the config map, removing any config previously set. */ public void clearConfig() { config.clear(); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6a1ac3f2/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java index e2eb9b6..ad5ca74 100644 --- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java +++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/creation/BrooklynComponentTemplateResolver.java @@ -23,7 +23,6 @@ import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -31,10 +30,11 @@ import javax.annotation.Nullable; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; -import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.framework.FrameworkLookup; +import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.classloading.BrooklynClassLoadingContext; +import org.apache.brooklyn.api.objs.SpecParameter; import org.apache.brooklyn.api.typereg.RegisteredType; import org.apache.brooklyn.camp.brooklyn.BrooklynCampConstants; import org.apache.brooklyn.camp.brooklyn.BrooklynCampReservedKeys; @@ -45,6 +45,8 @@ import org.apache.brooklyn.camp.spi.AbstractResource; import org.apache.brooklyn.camp.spi.ApplicationComponentTemplate; import org.apache.brooklyn.camp.spi.AssemblyTemplate; import org.apache.brooklyn.camp.spi.PlatformComponentTemplate; +import org.apache.brooklyn.config.ConfigInheritance; +import org.apache.brooklyn.config.ConfigInheritance.InheritanceMode; import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.catalog.internal.CatalogUtils; import org.apache.brooklyn.core.config.ConfigKeys; @@ -54,7 +56,9 @@ import org.apache.brooklyn.core.mgmt.EntityManagementUtils; import org.apache.brooklyn.core.mgmt.ManagementContextInjectable; import org.apache.brooklyn.core.mgmt.classloading.JavaBrooklynClassLoadingContext; import org.apache.brooklyn.core.resolve.entity.EntitySpecResolver; +import org.apache.brooklyn.util.collections.CollectionMerger; import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.flags.FlagUtils; @@ -257,14 +261,18 @@ public class BrooklynComponentTemplateResolver { // attrs will contain only brooklyn.xxx properties when coming from BrooklynEntityMatcher. // Any top-level flags will go into "brooklyn.flags". When resolving a spec from $brooklyn:entitySpec // top level flags remain in place. Have to support both cases. - + // + // For config values inherited from the super-type (be that the Java type or another catalog item + // being extended), we lookup the config key to find out if the values should be merged, overridden or + // cleared. + ConfigBag bag = ConfigBag.newInstance((Map<Object, Object>) attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_CONFIG)); ConfigBag bagFlags = ConfigBag.newInstanceCopying(attrs); if (attrs.containsKey(BrooklynCampReservedKeys.BROOKLYN_FLAGS)) { bagFlags.putAll((Map<String, Object>) attrs.getStringKey(BrooklynCampReservedKeys.BROOKLYN_FLAGS)); } - Collection<FlagConfigKeyAndValueRecord> topLevelApparentConfig = findAllFlagsAndConfigKeys(spec, bagFlags); + Collection<FlagConfigKeyAndValueRecord> topLevelApparentConfig = findAllFlagsAndConfigKeyValues(spec, bagFlags); for (FlagConfigKeyAndValueRecord r: topLevelApparentConfig) { if (r.getConfigKeyMaybeValue().isPresent()) bag.putIfAbsent((ConfigKey)r.getConfigKey(), r.getConfigKeyMaybeValue().get()); @@ -272,22 +280,44 @@ public class BrooklynComponentTemplateResolver { bag.putAsStringKeyIfAbsent(r.getFlagName(), r.getFlagMaybeValue().get()); } + // now set configuration for all the items in the bag - Collection<FlagConfigKeyAndValueRecord> records = findAllFlagsAndConfigKeys(spec, bag); + Map<String, ConfigKey<?>> entityConfigKeys = findAllConfigKeys(spec); + + Collection<FlagConfigKeyAndValueRecord> records = findAllFlagsAndConfigKeyValues(spec, bag); Set<String> keyNamesUsed = new LinkedHashSet<String>(); for (FlagConfigKeyAndValueRecord r: records) { if (r.getFlagMaybeValue().isPresent()) { - Object transformed = new SpecialFlagsTransformer(loader, encounteredRegisteredTypeIds).apply(r.getFlagMaybeValue().get()); - spec.configure(r.getFlagName(), transformed); - keyNamesUsed.add(r.getFlagName()); + String flag = r.getFlagName(); + Object ownVal = new SpecialFlagsTransformer(loader, encounteredRegisteredTypeIds).apply(r.getFlagMaybeValue().get()); + Maybe<?> superVal = spec.getFlags().containsKey(flag) ? Maybe.of(spec.getFlags().get(flag)) : Maybe.absent(); + Object combinedVal = combineValues(entityConfigKeys.get(flag), spec, Maybe.of(ownVal), superVal).get(); + spec.configure(flag, combinedVal); + keyNamesUsed.add(flag); } if (r.getConfigKeyMaybeValue().isPresent()) { - Object transformed = new SpecialFlagsTransformer(loader, encounteredRegisteredTypeIds).apply(r.getConfigKeyMaybeValue().get()); - spec.configure((ConfigKey<Object>)r.getConfigKey(), transformed); - keyNamesUsed.add(r.getConfigKey().getName()); + ConfigKey<Object> key = (ConfigKey<Object>) r.getConfigKey(); + Object ownVal = new SpecialFlagsTransformer(loader, encounteredRegisteredTypeIds).apply(r.getConfigKeyMaybeValue().get()); + Maybe<?> superVal = spec.getConfig().containsKey(key) ? Maybe.of(spec.getConfig().get(key)) : Maybe.absent(); + Object combinedVal = combineValues(entityConfigKeys.get(key.getName()), spec, Maybe.of(ownVal), superVal).get(); + spec.configure(key, combinedVal); + keyNamesUsed.add(key.getName()); } } + // For anything that should not be inherited, clear if from the spec + for (Map.Entry<String, ConfigKey<?>> entry : entityConfigKeys.entrySet()) { + if (keyNamesUsed.contains(entry.getKey())) { + continue; + } + ConfigKey<?> key = entry.getValue(); + InheritanceMode mode = getInheritanceMode(key, spec); + if (mode == InheritanceMode.NONE) { + spec.removeConfig(key); + spec.removeFlag(key.getName()); + } + } + // set unused keys as anonymous config keys - // they aren't flags or known config keys, so must be passed as config keys in order for // EntitySpec to know what to do with them (as they are passed to the spec as flags) @@ -301,10 +331,47 @@ public class BrooklynComponentTemplateResolver { } } + // TODO Duplicates some logic in EntityConfigMap.getConfig() + private Maybe<?> combineValues(ConfigKey<?> key, EntitySpec<?> spec, Maybe<?> ownVal, Maybe<?> superVal) { + InheritanceMode mode = getInheritanceMode(key, spec); + switch (mode) { + case IF_NO_EXPLICIT_VALUE: + return ownVal.isPresent() ? ownVal : superVal; + case MERGE: + return deepMerge(ownVal, superVal, key); + case NONE: + return ownVal; + default: + throw new IllegalStateException("Unsupported type-inheritance mode for "+key.getName()+": "+mode); + } + } + + private InheritanceMode getInheritanceMode(ConfigKey<?> key, EntitySpec<?> spec) { + ConfigInheritance inheritance = (key != null && key.getTypeInheritance() != null) ? key.getTypeInheritance() : ConfigInheritance.ALWAYS; + return inheritance.isInherited(key, spec, attrs); + } + + // TODO Duplicate of EntityConfigMap.deepMerge + private <T> Maybe<?> deepMerge(Maybe<? extends T> val1, Maybe<? extends T> val2, ConfigKey<?> keyForLogging) { + if (val2.isAbsent() || val2.isNull()) { + return val1; + } else if (val1.isAbsent()) { + return val2; + } else if (val1.isNull()) { + return val1; // an explicit null means an override; don't merge + } else if (val1.get() instanceof Map && val2.get() instanceof Map) { + return Maybe.of(CollectionMerger.builder().build().merge((Map<?,?>)val1.get(), (Map<?,?>)val2.get())); + } else { + // cannot merge; just return val1 + log.debug("Cannot merge values for "+keyForLogging.getName()+", because values are not maps: "+val1.get().getClass()+", and "+val2.get().getClass()); + return val1; + } + } + /** * Searches for config keys in the type, additional interfaces and the implementation (if specified) */ - private Collection<FlagConfigKeyAndValueRecord> findAllFlagsAndConfigKeys(EntitySpec<?> spec, ConfigBag bagFlags) { + private Collection<FlagConfigKeyAndValueRecord> findAllFlagsAndConfigKeyValues(EntitySpec<?> spec, ConfigBag bagFlags) { Set<FlagConfigKeyAndValueRecord> allKeys = MutableSet.of(); allKeys.addAll(FlagUtils.findAllFlagsAndConfigKeys(null, spec.getType(), bagFlags)); if (spec.getImplementation() != null) { @@ -317,6 +384,20 @@ public class BrooklynComponentTemplateResolver { return allKeys; } + private Map<String, ConfigKey<?>> findAllConfigKeys(EntitySpec<?> spec) { + Set<Class<?>> types = MutableSet.<Class<?>>builder() + .add(spec.getType()) + .add(spec.getImplementation()) + .addAll(spec.getAdditionalInterfaces()) + .remove(null) + .build(); + MutableMap<String, ConfigKey<?>> result = MutableMap.copyOf(FlagUtils.findAllConfigKeys(null, types)); + for (SpecParameter<?> param : spec.getParameters()) { + result.put(param.getConfigKey().getName(), param.getConfigKey()); + } + return result; + } + private static class SpecialFlagsTransformer implements Function<Object, Object> { protected final ManagementContext mgmt; /* TODO find a way to make do without loader here? http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6a1ac3f2/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigInheritanceYamlTest.java ---------------------------------------------------------------------- diff --git a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigInheritanceYamlTest.java b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigInheritanceYamlTest.java index 1d37589..9f52ded 100644 --- a/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigInheritanceYamlTest.java +++ b/camp/camp-brooklyn/src/test/java/org/apache/brooklyn/camp/brooklyn/ConfigInheritanceYamlTest.java @@ -30,7 +30,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.brooklyn.api.entity.Entity; -import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.core.config.MapConfigKey; import org.apache.brooklyn.core.entity.EntityAsserts; import org.apache.brooklyn.core.sensor.Sensors; @@ -99,6 +98,26 @@ public class ConfigInheritanceYamlTest extends AbstractYamlTest { addCatalogItems( "brooklyn.catalog:", + " id: EmptySoftwareProcess-with-env", + " itemType: entity", + " item:", + " type: org.apache.brooklyn.entity.software.base.EmptySoftwareProcess", + " brooklyn.config:", + " env:", + " ENV1: myEnv1"); + + addCatalogItems( + "brooklyn.catalog:", + " id: EmptySoftwareProcess-with-shell.env", + " itemType: entity", + " item:", + " type: org.apache.brooklyn.entity.software.base.EmptySoftwareProcess", + " brooklyn.config:", + " shell.env:", + " ENV1: myEnv1"); + + addCatalogItems( + "brooklyn.catalog:", " id: localhost-stub", " name: Localhost (stubbed-SSH)", " itemType: location", @@ -219,7 +238,7 @@ public class ConfigInheritanceYamlTest extends AbstractYamlTest { * attributeWhenReady apply to? */ @Test(groups={"Broken", "WIP"}, enabled=false) - public void testInheritsParentConfigTask() throws Exception { + public void testInheritsParentConfigTaskWithSelfScope() throws Exception { String yaml = Joiner.on("\n").join( "services:", "- type: org.apache.brooklyn.entity.stock.BasicApplication", @@ -241,6 +260,33 @@ public class ConfigInheritanceYamlTest extends AbstractYamlTest { public Object call() { return app.sensors().set(Sensors.newStringSensor("myOtherSensor"), "myObject"); }}); + assertEquals(app.config().get(TestEntity.CONF_OBJECT), "myObject"); + } + + @Test + public void testInheritsParentConfigTask() throws Exception { + String yaml = Joiner.on("\n").join( + "services:", + "- type: org.apache.brooklyn.entity.stock.BasicApplication", + " id: app", + " brooklyn.config:", + " test.confName: $brooklyn:config(\"myOtherConf\")", + " test.confObject: $brooklyn:component(\"app\").attributeWhenReady(\"myOtherSensor\")", + " myOtherConf: myOther", + " brooklyn.children:", + " - type: org.apache.brooklyn.core.test.entity.TestEntity"); + + final Entity app = createStartWaitAndLogApplication(new StringReader(yaml)); + TestEntity entity = (TestEntity) Iterables.getOnlyElement(app.getChildren()); + + // Task that resolves quickly + assertEquals(entity.config().get(TestEntity.CONF_NAME), "myOther"); + + // Task that resolves slowly + executor.submit(new Callable<Object>() { + public Object call() { + return app.sensors().set(Sensors.newStringSensor("myOtherSensor"), "myObject"); + }}); assertEquals(entity.config().get(TestEntity.CONF_OBJECT), "myObject"); } @@ -536,6 +582,80 @@ public class ConfigInheritanceYamlTest extends AbstractYamlTest { } } + @Test + public void testExtendsSuperTypeConfigSimple() throws Exception { + ImmutableMap<String, Object> expectedEnv = ImmutableMap.<String, Object>of("ENV1", "myEnv1", "ENV2", "myEnv2"); + + // super-type has shell.env; sub-type shell.env + String yaml = Joiner.on("\n").join( + "location: localhost-stub", + "services:", + "- type: EmptySoftwareProcess-with-shell.env", + " brooklyn.config:", + " shell.env:", + " ENV2: myEnv2"); + + Entity app = createStartWaitAndLogApplication(new StringReader(yaml)); + Entity entity = Iterables.getOnlyElement(app.getChildren()); + EntityAsserts.assertConfigEquals(entity, EmptySoftwareProcess.SHELL_ENVIRONMENT, expectedEnv); + } + + @Test + public void testExtendsSuperTypeConfigMixingLongOverridingShortNames() throws Exception { + ImmutableMap<String, Object> expectedEnv = ImmutableMap.<String, Object>of("ENV1", "myEnv1", "ENV2", "myEnv2"); + + // super-type has env; sub-type shell.env + String yaml = Joiner.on("\n").join( + "location: localhost-stub", + "services:", + "- type: EmptySoftwareProcess-with-env", + " brooklyn.config:", + " shell.env:", + " ENV2: myEnv2"); + + Entity app = createStartWaitAndLogApplication(new StringReader(yaml)); + Entity entity = Iterables.getOnlyElement(app.getChildren()); + EntityAsserts.assertConfigEquals(entity, EmptySoftwareProcess.SHELL_ENVIRONMENT, expectedEnv); + } + + @Test + public void testExtendsSuperTypeConfigMixingShortOverridingLongName() throws Exception { + ImmutableMap<String, Object> expectedEnv = ImmutableMap.<String, Object>of("ENV1", "myEnv1", "ENV2", "myEnv2"); + + // super-type has shell.env; sub-type env + String yaml = Joiner.on("\n").join( + "location: localhost-stub", + "services:", + "- type: EmptySoftwareProcess-with-shell.env", + " brooklyn.config:", + " env:", + " ENV2: myEnv2"); + + Entity app = createStartWaitAndLogApplication(new StringReader(yaml)); + Entity entity = Iterables.getOnlyElement(app.getChildren()); + EntityAsserts.assertConfigEquals(entity, EmptySoftwareProcess.SHELL_ENVIRONMENT, expectedEnv); + } + + // TODO Does not work, and probably hard to fix?! We need to figure out that "env" corresponds to the + // config key. Maybe FlagUtils could respect SetFromFlags when returning Map<String,ConfigKey>? + @Test(groups="WIP") + public void testExtendsSuperTypeConfigMixingShortOverridingShortName() throws Exception { + ImmutableMap<String, Object> expectedEnv = ImmutableMap.<String, Object>of("ENV1", "myEnv1", "ENV2", "myEnv2"); + + // super-type has env; sub-type env + String yaml = Joiner.on("\n").join( + "location: localhost-stub", + "services:", + "- type: EmptySoftwareProcess-with-env", + " brooklyn.config:", + " env:", + " ENV2: myEnv2"); + + Entity app = createStartWaitAndLogApplication(new StringReader(yaml)); + Entity entity = Iterables.getOnlyElement(app.getChildren()); + EntityAsserts.assertConfigEquals(entity, EmptySoftwareProcess.SHELL_ENVIRONMENT, expectedEnv); + } + protected void assertEmptySoftwareProcessConfig(Entity entity, Map<String, ?> expectedEnv, Map<String, String> expectedFiles, Map<String, ?> expectedProvisioningProps) { EntityAsserts.assertConfigEquals(entity, EmptySoftwareProcess.SHELL_ENVIRONMENT, MutableMap.<String, Object>copyOf(expectedEnv)); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6a1ac3f2/core/src/main/java/org/apache/brooklyn/util/core/flags/FlagUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/util/core/flags/FlagUtils.java b/core/src/main/java/org/apache/brooklyn/util/core/flags/FlagUtils.java index 3bc22e0..26f3e9d 100644 --- a/core/src/main/java/org/apache/brooklyn/util/core/flags/FlagUtils.java +++ b/core/src/main/java/org/apache/brooklyn/util/core/flags/FlagUtils.java @@ -218,6 +218,30 @@ public class FlagUtils { } } + /** + * Gets all the config keys for the given types (for duplicates, the first class in the list of types takes precedence). + */ + public static Map<String, ConfigKey<?>> findAllConfigKeys(Object optionalInstance, Iterable<? extends Class<?>> types) { + Map<String, ConfigKey<?>> result = Maps.newLinkedHashMap(); + for (Class<?> type : types) { + List<ConfigKey<?>> keys = FlagUtils.findAllConfigKeys(optionalInstance, type); + for (ConfigKey<?> key : keys) { + if (!result.containsKey(key.getName())) result.put(key.getName(), key); + } + } + return result; + } + + /** gets all the config keys for the given type */ + public static <T> List<ConfigKey<?>> findAllConfigKeys(T optionalInstance, Class<? extends T> type) { + List<ConfigKey<?>> output = new ArrayList<ConfigKey<?>>(); + for (Field f: getAllFields(type)) { + ConfigKey<?> key = getFieldAsConfigKey(optionalInstance, f); + if (key != null) output.add(key); + } + return output; + } + /** gets all the flags/keys in the given config bag which are applicable to the given type's config keys and flags */ public static <T> List<FlagConfigKeyAndValueRecord> findAllFlagsAndConfigKeys(T optionalInstance, Class<? extends T> type, ConfigBag input) { List<FlagConfigKeyAndValueRecord> output = new ArrayList<FlagUtils.FlagConfigKeyAndValueRecord>(); @@ -250,9 +274,10 @@ public class FlagUtils { if (f != null) { SetFromFlag flag = f.getAnnotation(SetFromFlag.class); if (flag!=null) { - result.flagName = flag.value(); - if (input.containsKey(flag.value())) - result.flagValue = Maybe.of(input.getStringKey(flag.value())); + String flagName = elvis(flag.value(), f.getName()); + result.flagName = flagName; + if (input.containsKey(flagName)) + result.flagValue = Maybe.of(input.getStringKey(flagName)); } } return result; http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6a1ac3f2/core/src/test/java/org/apache/brooklyn/util/core/internal/FlagUtilsTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/brooklyn/util/core/internal/FlagUtilsTest.java b/core/src/test/java/org/apache/brooklyn/util/core/internal/FlagUtilsTest.java index a75db17..905674a 100644 --- a/core/src/test/java/org/apache/brooklyn/util/core/internal/FlagUtilsTest.java +++ b/core/src/test/java/org/apache/brooklyn/util/core/internal/FlagUtilsTest.java @@ -21,6 +21,7 @@ package org.apache.brooklyn.util.core.internal; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; import java.lang.reflect.Field; import java.net.InetAddress; @@ -34,15 +35,19 @@ import org.apache.brooklyn.config.ConfigKey; import org.apache.brooklyn.config.ConfigKey.HasConfigKey; import org.apache.brooklyn.core.config.BasicConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.flags.FlagUtils; +import org.apache.brooklyn.util.core.flags.FlagUtils.FlagConfigKeyAndValueRecord; import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.guava.Maybe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; import com.google.common.base.Function; +import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -220,6 +225,91 @@ public class FlagUtilsTest { assertEquals(unused, ImmutableMap.of("ck2", "dont-set")); } + @Test + public void testFindAllConfigKeys() { + Map<String, ConfigKey<?>> keys = FlagUtils.findAllConfigKeys(null, ImmutableList.of(SubFooCK.class)); + assertEquals(keys, ImmutableMap.of( + FooCK.CK1.getName(), FooCK.CK1, + FooCK.CK2.getName(), FooCK.CK2, + FooCK.CK3.getName(), FooCK.CK3, + SubFooCK.CK4.getName(), SubFooCK.CK4)); + } + + @Test + public void testFindAllFlagsAndConfigKeysWithEmptyBag() { + ConfigBag bag = ConfigBag.newInstance(ImmutableMap.of()); + List<FlagConfigKeyAndValueRecord> vals = FlagUtils.<SubFooCK>findAllFlagsAndConfigKeys(null, SubFooCK.class, bag); + assertRecordsEqual(vals, ImmutableList.<ExpectedFlagConfigKeyAndValueRecord>of()); + } + + @Test + public void testFindAllFlagsAndConfigKeysWithConfigVals() { + ConfigBag bag = ConfigBag.newInstance(ImmutableMap.of(FooCK.CK1, "ck1.myval")); + List<FlagConfigKeyAndValueRecord> vals = FlagUtils.<SubFooCK>findAllFlagsAndConfigKeys(null, SubFooCK.class, bag); + assertRecordsEqual(vals, ImmutableList.of(new ExpectedFlagConfigKeyAndValueRecord(FooCK.CK1, "CK1", "ck1.myval"))); + } + + @Test + public void testFindAllFlagsAndConfigKeysWithFlagVals() { + ConfigBag bag = ConfigBag.newInstance(ImmutableMap.of("f1", 123)); + List<FlagConfigKeyAndValueRecord> vals = FlagUtils.<SubFooCK>findAllFlagsAndConfigKeys(null, SubFooCK.class, bag); + assertRecordsEqual(vals, ImmutableList.of(new ExpectedFlagConfigKeyAndValueRecord("f1", 123))); + } + + protected void assertRecordsEqual(List<FlagConfigKeyAndValueRecord> actual, List<ExpectedFlagConfigKeyAndValueRecord> expected) { + List<FlagConfigKeyAndValueRecord> actualCopy = MutableList.copyOf(actual); + for (ExpectedFlagConfigKeyAndValueRecord record : expected) { + boolean found = false; + for (FlagConfigKeyAndValueRecord contender : actualCopy) { + if (isRecordEqual(contender, record)) { + actualCopy.remove(contender); + found = true; + break; + } + } + if (!found) { + fail(record+" expected, but not found"); + } + } + if (!actualCopy.isEmpty()) { + fail(actualCopy+" additional records found"); + } + } + + protected boolean isRecordEqual(FlagConfigKeyAndValueRecord val1, ExpectedFlagConfigKeyAndValueRecord val2) { + return Objects.equal(val1.getConfigKey(), val2.configKey) + && Objects.equal(val1.getFlagName(), val2.flagName) + && Objects.equal(val1.getConfigKeyMaybeValue(), val2.configKeyValue) + && Objects.equal(val1.getFlagMaybeValue(), val2.flagValue); + } + + // The fields of FlagConfigKeyAndValueRecord are private; hence having this class to make assertions easier + static class ExpectedFlagConfigKeyAndValueRecord { + String flagName = ""; + ConfigKey<?> configKey = null; + Maybe<Object> flagValue = Maybe.absent(); + Maybe<Object> configKeyValue = Maybe.absent(); + + ExpectedFlagConfigKeyAndValueRecord(String flagName, Object val) { + this.flagName = flagName; + this.flagValue = Maybe.of(val); + } + ExpectedFlagConfigKeyAndValueRecord(ConfigKey<?> key, String flagName, Object val) { + this.configKey = key; + this.flagName = flagName; + this.configKeyValue = Maybe.of(val); + } + @Override + public String toString() { + return Objects.toStringHelper(this).omitNullValues() + .add("flag", flagName) + .add("configKey", configKey) + .add("flagValue", flagValue.orNull()) + .add("configKeyValue", configKeyValue.orNull()) + .toString(); + } + } + public static class Foo { @SetFromFlag int w; @@ -251,7 +341,7 @@ public class FlagUtilsTest { @SetFromFlag Set<?> set; @SetFromFlag InetAddress inet; } - + public static class FooCK implements Configurable { @SetFromFlag public static ConfigKey<String> CK1 = ConfigKeys.newStringConfigKey("ck1"); @@ -315,4 +405,8 @@ public class FlagUtilsTest { } } } + + public static class SubFooCK extends FooCK { + public static ConfigKey<String> CK4 = ConfigKeys.newStringConfigKey("ck4"); + } } \ No newline at end of file
