From cadcac9a8c0501f86372eb05d107d7b089643d0a Mon Sep 17 00:00:00 2001 From: HÃ¥kon Hallingstad Date: Fri, 23 Nov 2018 17:30:13 +0100 Subject: Add flags module FileFlagSource reads flags from files in /etc/vespa/flags and is a component that can be injected in host admin, config server, etc. A flag named foo corresponds to filename foo. In general a FlagSource manages: - Feature flags: A feature is either set (true/enabled) or otherwise false. Touching a file foo means the feature flag foo is set (true). - Value flags: Either a String or empty if not set. The String corresponds to the file content. The plan is to make the config server another source of flags. A unified FlagSource can merge the two sources with some priority and used in e.g. parts of node-admin. In other parts one would only have access to the file source. Defines various flag facades: - FeatureFlag: Used to test whether a feature has been enabled or not. - IntFlag - JacksonFlag: Deserializes JSON to Jackson class, or return default if unset. - LongFlag - OptionalJacksonFlag: Deserializes JSON to Jackson class, or empty if unset. - OptionalStringFlag - StringFlag This is part of removing some of the last Chef recipes. Some minor tweaks have been necessary as part of this and are included in this PR (test whether a systemd service exists, task-friendly file deletion, allow capitalized letters in YUM package name). --- flags/pom.xml | 66 ++++++++++++++++++++ .../java/com/yahoo/vespa/flags/FeatureFlag.java | 48 +++++++++++++++ .../java/com/yahoo/vespa/flags/FileFlagSource.java | 67 ++++++++++++++++++++ .../src/main/java/com/yahoo/vespa/flags/Flag.java | 9 +++ .../main/java/com/yahoo/vespa/flags/FlagId.java | 42 +++++++++++++ .../java/com/yahoo/vespa/flags/FlagSource.java | 15 +++++ .../main/java/com/yahoo/vespa/flags/IntFlag.java | 49 +++++++++++++++ .../java/com/yahoo/vespa/flags/JacksonFlag.java | 58 +++++++++++++++++ .../main/java/com/yahoo/vespa/flags/LongFlag.java | 49 +++++++++++++++ .../com/yahoo/vespa/flags/OptionalJacksonFlag.java | 60 ++++++++++++++++++ .../com/yahoo/vespa/flags/OptionalStringFlag.java | 53 ++++++++++++++++ .../java/com/yahoo/vespa/flags/StringFlag.java | 49 +++++++++++++++ .../java/com/yahoo/vespa/flags/package-info.java | 5 ++ .../com/yahoo/vespa/flags/FileFlagSourceTest.java | 49 +++++++++++++++ .../com/yahoo/vespa/flags/JacksonFlagTest.java | 72 ++++++++++++++++++++++ 15 files changed, 691 insertions(+) create mode 100644 flags/pom.xml create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/FeatureFlag.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/Flag.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/FlagId.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/OptionalJacksonFlag.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/OptionalStringFlag.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java create mode 100644 flags/src/main/java/com/yahoo/vespa/flags/package-info.java create mode 100644 flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java create mode 100644 flags/src/test/java/com/yahoo/vespa/flags/JacksonFlagTest.java (limited to 'flags') diff --git a/flags/pom.xml b/flags/pom.xml new file mode 100644 index 00000000000..57c57f907d3 --- /dev/null +++ b/flags/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + com.yahoo.vespa + parent + 6-SNAPSHOT + ../parent/pom.xml + + + flags + 6-SNAPSHOT + container-plugin + ${project.artifactId} + + + + com.yahoo.vespa + defaults + ${project.version} + provided + + + com.yahoo.vespa + container-dev + ${project.version} + provided + + + + junit + junit + test + + + org.mockito + mockito-core + test + + + org.hamcrest + hamcrest-junit + 2.0.0.0 + test + + + com.yahoo.vespa + testutil + ${project.version} + test + + + + + + com.yahoo.vespa + bundle-plugin + true + + + + diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FeatureFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/FeatureFlag.java new file mode 100644 index 00000000000..957faee41cc --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/FeatureFlag.java @@ -0,0 +1,48 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import java.util.function.Function; + +/** + * A FeatureFlag is true only if set (enabled). + * + * @author hakonhall + */ +public class FeatureFlag implements Flag { + private final FlagId id; + private final FlagSource source; + + public static Function createUnbound(String flagId) { + return createUnbound(new FlagId(flagId)); + } + + public static Function createUnbound(FlagId flagId) { + return source -> new FeatureFlag(flagId, source); + } + + public FeatureFlag(String flagId, FlagSource source) { + this(new FlagId(flagId), source); + } + + public FeatureFlag(FlagId id, FlagSource source) { + this.id = id; + this.source = source; + } + + @Override + public FlagId id() { + return id; + } + + public boolean isSet() { + return source.hasFeature(id); + } + + @Override + public String toString() { + return "FeatureFlag{" + + "id=" + id + + ", source=" + source + + '}'; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java b/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java new file mode 100644 index 00000000000..f13a122b156 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/FileFlagSource.java @@ -0,0 +1,67 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import com.google.common.util.concurrent.UncheckedTimeoutException; + +import javax.inject.Inject; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.Optional; + +/** + * A {@link FlagSource} backed by local files. + * + * @author hakonhall + */ +public class FileFlagSource implements FlagSource { + static final String FLAGS_DIRECTORY = "/etc/vespa/flags"; + + private final Path flagsDirectory; + + @Inject + public FileFlagSource() { + this(FileSystems.getDefault()); + } + + public FileFlagSource(FileSystem fileSystem) { + this(fileSystem.getPath(FLAGS_DIRECTORY)); + } + + public FileFlagSource(Path flagsDirectory) { + this.flagsDirectory = flagsDirectory; + } + + @Override + public boolean hasFeature(FlagId id) { + return Files.exists(getPath(id)); + } + + @Override + public Optional getString(FlagId id) { + return getBytes(id).map(bytes -> new String(bytes, StandardCharsets.UTF_8)); + } + + public Optional getBytes(FlagId id) { + try { + return Optional.of(Files.readAllBytes(getPath(id))); + } catch (NoSuchFileException e) { + return Optional.empty(); + } catch (IOException e) { + throw new UncheckedTimeoutException(e); + } + } + + private Path getPath(FlagId id) { + return flagsDirectory.resolve(id.toString()); + } + + @Override + public String toString() { + return "FileFlagSource{" + flagsDirectory + '}'; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flag.java b/flags/src/main/java/com/yahoo/vespa/flags/Flag.java new file mode 100644 index 00000000000..831e0d0dab9 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flag.java @@ -0,0 +1,9 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +/** + * @author hakonhall + */ +public interface Flag { + FlagId id(); +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FlagId.java b/flags/src/main/java/com/yahoo/vespa/flags/FlagId.java new file mode 100644 index 00000000000..f004df063ed --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/FlagId.java @@ -0,0 +1,42 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import javax.annotation.concurrent.Immutable; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * @author hakonhall + */ +@Immutable +public class FlagId { + private static final Pattern ID_PATTERN = Pattern.compile("^[a-zA-Z0-9][a-zA-Z0-9._-]*$"); + + private final String id; + + public FlagId(String id) { + if (!ID_PATTERN.matcher(id).find()) { + throw new IllegalArgumentException("Not a valid FlagId: '" + id + "'"); + } + + this.id = id; + } + + @Override + public String toString() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FlagId flagId = (FlagId) o; + return Objects.equals(id, flagId.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java b/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java new file mode 100644 index 00000000000..56f4b5ee0ae --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/FlagSource.java @@ -0,0 +1,15 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import java.util.Optional; + +/** + * @author hakonhall + */ +public interface FlagSource { + /** Whether the source has the feature flag with the given id. */ + boolean hasFeature(FlagId id); + + /** The String value of a flag. */ + Optional getString(FlagId id); +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java new file mode 100644 index 00000000000..f7c9645c5db --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/IntFlag.java @@ -0,0 +1,49 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import java.util.function.Function; + +/** + * @author hakonhall + */ +public class IntFlag implements Flag { + private final FlagId id; + private final int defaultValue; + private final FlagSource source; + + public static Function createUnbound(String flagId, int defaultValue) { + return createUnbound(new FlagId(flagId), defaultValue); + } + + public static Function createUnbound(FlagId id, int defaultValue) { + return source -> new IntFlag(id, defaultValue, source); + } + + public IntFlag(String flagId, int defaultValue, FlagSource source) { + this(new FlagId(flagId), defaultValue, source); + } + + public IntFlag(FlagId id, int defaultValue, FlagSource source) { + this.id = id; + this.defaultValue = defaultValue; + this.source = source; + } + + @Override + public FlagId id() { + return id; + } + + public int value() { + return source.getString(id).map(String::trim).map(Integer::parseInt).orElse(defaultValue); + } + + @Override + public String toString() { + return "IntFlag{" + + "id=" + id + + ", defaultValue=" + defaultValue + + ", source=" + source + + '}'; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java new file mode 100644 index 00000000000..99add358e75 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/JacksonFlag.java @@ -0,0 +1,58 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.function.Function; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * @author hakonhall + */ +public class JacksonFlag implements Flag { + private final static ObjectMapper mapper = new ObjectMapper(); + + private final FlagId id; + private final Class jacksonClass; + private final T defaultValue; + private final FlagSource source; + + public static Function> createUnbound(String flagId, Class jacksonClass, T defaultValue) { + return createUnbound(new FlagId(flagId), jacksonClass, defaultValue); + } + + public static Function> createUnbound(FlagId id, Class jacksonClass, T defaultValue) { + return source -> new JacksonFlag<>(id, jacksonClass, defaultValue, source); + } + + public JacksonFlag(String flagId, Class jacksonClass, T defaultValue, FlagSource source) { + this(new FlagId(flagId), jacksonClass, defaultValue, source); + } + + public JacksonFlag(FlagId id, Class jacksonClass, T defaultValue, FlagSource source) { + this.id = id; + this.jacksonClass = jacksonClass; + this.defaultValue = defaultValue; + this.source = source; + } + + @Override + public FlagId id() { + return id; + } + + public T value() { + return source.getString(id).map(string -> uncheck(() -> mapper.readValue(string, jacksonClass))).orElse(defaultValue); + } + + @Override + public String toString() { + return "JacksonFlag{" + + "id=" + id + + ", jacksonClass=" + jacksonClass + + ", defaultValue=" + defaultValue + + ", source=" + source + + '}'; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java new file mode 100644 index 00000000000..d60dc7b5adc --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/LongFlag.java @@ -0,0 +1,49 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import java.util.function.Function; + +/** + * @author hakonhall + */ +public class LongFlag implements Flag { + private final FlagId id; + private final long defaultValue; + private final FlagSource source; + + public static Function createUnbound(String flagId, int defaultValue) { + return createUnbound(new FlagId(flagId), defaultValue); + } + + public static Function createUnbound(FlagId id, int defaultValue) { + return source -> new LongFlag(id, defaultValue, source); + } + + public LongFlag(String flagId, long defaultValue, FlagSource source) { + this(new FlagId(flagId), defaultValue, source); + } + + public LongFlag(FlagId id, long defaultValue, FlagSource source) { + this.id = id; + this.defaultValue = defaultValue; + this.source = source; + } + + @Override + public FlagId id() { + return id; + } + + public long value() { + return source.getString(id).map(String::trim).map(Long::parseLong).orElse(defaultValue); + } + + @Override + public String toString() { + return "LongFlag{" + + "id=" + id + + ", defaultValue=" + defaultValue + + ", source=" + source + + '}'; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/OptionalJacksonFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/OptionalJacksonFlag.java new file mode 100644 index 00000000000..9b25a5d6786 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/OptionalJacksonFlag.java @@ -0,0 +1,60 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.Optional; +import java.util.function.Function; + +import static com.yahoo.yolean.Exceptions.uncheck; + +/** + * @author hakonhall + */ +public class OptionalJacksonFlag implements Flag { + private final static ObjectMapper mapper = new ObjectMapper(); + + private final FlagId id; + private final Class jacksonClass; + private final FlagSource source; + + public static Function> createUnbound(String flagId, Class jacksonClass) { + return createUnbound(new FlagId(flagId), jacksonClass); + } + + public static Function> createUnbound(FlagId id, Class jacksonClass) { + return source -> new OptionalJacksonFlag<>(id, jacksonClass, source); + } + + public OptionalJacksonFlag(String flagId, Class jacksonClass, FlagSource source) { + this(new FlagId(flagId), jacksonClass, source); + } + + public OptionalJacksonFlag(FlagId id, Class jacksonClass, FlagSource source) { + this.id = id; + this.jacksonClass = jacksonClass; + this.source = source; + } + + @Override + public FlagId id() { + return id; + } + + public Optional value() { + return source.getString(id).map(string -> uncheck(() -> mapper.readValue(string, jacksonClass))); + } + + public JacksonFlag withDefault(T defaultValue) { + return new JacksonFlag(id, jacksonClass, defaultValue, source); + } + + @Override + public String toString() { + return "OptionalJacksonFlag{" + + "id=" + id + + ", jacksonClass=" + jacksonClass + + ", source=" + source + + '}'; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/OptionalStringFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/OptionalStringFlag.java new file mode 100644 index 00000000000..3e25b08cf9d --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/OptionalStringFlag.java @@ -0,0 +1,53 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import java.util.Optional; +import java.util.function.Function; + +/** + * An OptionalStringFlag is a flag which is either not set (empty), or set (String present). + * + * @author hakonhall + */ +public class OptionalStringFlag implements Flag { + private final FlagId id; + private final FlagSource source; + + public static Function createUnbound(String flagId) { + return createUnbound(new FlagId(flagId)); + } + + public static Function createUnbound(FlagId id) { + return source -> new OptionalStringFlag(id, source); + } + + public OptionalStringFlag(String flagId, FlagSource source) { + this(new FlagId(flagId), source); + } + + public OptionalStringFlag(FlagId id, FlagSource source) { + this.id = id; + this.source = source; + } + + @Override + public FlagId id() { + return id; + } + + public StringFlag bindDefault(String defaultValue) { + return new StringFlag(id, defaultValue, source); + } + + public Optional value() { + return source.getString(id); + } + + @Override + public String toString() { + return "OptionalStringFlag{" + + "id=" + id + + ", source=" + source + + '}'; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java b/flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java new file mode 100644 index 00000000000..8226e999238 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/StringFlag.java @@ -0,0 +1,49 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import java.util.function.Function; + +/** + * @author hakonhall + */ +public class StringFlag implements Flag { + private final FlagId id; + private final String defaultValue; + private final FlagSource source; + + public static Function createUnbound(String flagId, String defaultValue) { + return createUnbound(new FlagId(flagId), defaultValue); + } + + public static Function createUnbound(FlagId id, String defaultValue) { + return source -> new StringFlag(id, defaultValue, source); + } + + public StringFlag(String flagId, String defaultValue, FlagSource source) { + this(new FlagId(flagId), defaultValue, source); + } + + public StringFlag(FlagId id, String defaultValue, FlagSource source) { + this.id = id; + this.defaultValue = defaultValue; + this.source = source; + } + + @Override + public FlagId id() { + return id; + } + + public String value() { + return source.getString(id).orElse(defaultValue); + } + + @Override + public String toString() { + return "StringFlag{" + + "id=" + id + + ", defaultValue='" + defaultValue + '\'' + + ", source=" + source + + '}'; + } +} diff --git a/flags/src/main/java/com/yahoo/vespa/flags/package-info.java b/flags/src/main/java/com/yahoo/vespa/flags/package-info.java new file mode 100644 index 00000000000..42b9b057b29 --- /dev/null +++ b/flags/src/main/java/com/yahoo/vespa/flags/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.vespa.flags; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java b/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java new file mode 100644 index 00000000000..e422057f5fe --- /dev/null +++ b/flags/src/test/java/com/yahoo/vespa/flags/FileFlagSourceTest.java @@ -0,0 +1,49 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import com.yahoo.vespa.test.file.TestFileSystem; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class FileFlagSourceTest { + private final FileSystem fileSystem = TestFileSystem.create(); + private final FileFlagSource source = new FileFlagSource(fileSystem); + + @Test + public void absentThenSet() throws IOException { + FlagId id = new FlagId("foo"); + FeatureFlag featureFlag = new FeatureFlag(id, source); + StringFlag stringFlag = new StringFlag(id, "default", source); + OptionalStringFlag optionalStringFlag = new OptionalStringFlag(id, source); + IntFlag intFlag = new IntFlag(id, -1, source); + LongFlag longFlag = new LongFlag(id, -2L, source); + + assertFalse(source.hasFeature(id)); + assertFalse(source.getString(id).isPresent()); + assertFalse(featureFlag.isSet()); + assertEquals("default", stringFlag.value()); + assertFalse(optionalStringFlag.value().isPresent()); + assertEquals(-1, intFlag.value()); + assertEquals(-2L, longFlag.value()); + + Path featurePath = fileSystem.getPath(FileFlagSource.FLAGS_DIRECTORY).resolve(id.toString()); + Files.createDirectories(featurePath.getParent()); + Files.write(featurePath, "1\n".getBytes()); + + assertTrue(source.hasFeature(id)); + assertTrue(source.getString(id).isPresent()); + assertTrue(featureFlag.isSet()); + assertEquals("1\n", stringFlag.value()); + assertEquals("1\n", optionalStringFlag.value().get()); + assertEquals(1, intFlag.value()); + assertEquals(1L, longFlag.value()); + } +} \ No newline at end of file diff --git a/flags/src/test/java/com/yahoo/vespa/flags/JacksonFlagTest.java b/flags/src/test/java/com/yahoo/vespa/flags/JacksonFlagTest.java new file mode 100644 index 00000000000..e4424d9886a --- /dev/null +++ b/flags/src/test/java/com/yahoo/vespa/flags/JacksonFlagTest.java @@ -0,0 +1,72 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.flags; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.junit.Test; + +import java.util.Objects; +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class JacksonFlagTest { + private final FlagId id = new FlagId("id"); + private final ExampleJacksonClass defaultValue = new ExampleJacksonClass(); + private final FlagSource source = mock(FlagSource.class); + private final JacksonFlag jacksonFlag = new JacksonFlag<>(id.toString(), ExampleJacksonClass.class, defaultValue, source); + private final OptionalJacksonFlag optionalJacksonFlag = new OptionalJacksonFlag<>(id, ExampleJacksonClass.class, source); + + @Test + public void unsetThenSet() { + when(source.getString(id)).thenReturn(Optional.empty()); + ExampleJacksonClass value = jacksonFlag.value(); + assertEquals(1, value.integer); + assertEquals("2", value.string); + assertEquals("3", value.dummy); + assertFalse(optionalJacksonFlag.value().isPresent()); + + when(source.getString(id)).thenReturn(Optional.of("{\"integer\": 4, \"string\": \"foo\", \"stray\": 6}")); + value = jacksonFlag.value(); + assertEquals(4, value.integer); + assertEquals("foo", value.string); + assertEquals("3", value.dummy); + + assertTrue(optionalJacksonFlag.value().isPresent()); + value = optionalJacksonFlag.value().get(); + assertEquals(4, value.integer); + assertEquals("foo", value.string); + assertEquals("3", value.dummy); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private static class ExampleJacksonClass { + @JsonProperty("integer") + public int integer = 1; + + @JsonProperty("string") + public String string = "2"; + + @JsonProperty("dummy") + public String dummy = "3"; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExampleJacksonClass that = (ExampleJacksonClass) o; + return integer == that.integer && + Objects.equals(string, that.string) && + Objects.equals(dummy, that.dummy); + } + + @Override + public int hashCode() { + return Objects.hash(integer, string, dummy); + } + } +} \ No newline at end of file -- cgit v1.2.3