diff options
author | Håkon Hallingstad <hakon@oath.com> | 2018-11-23 17:30:13 +0100 |
---|---|---|
committer | Håkon Hallingstad <hakon@oath.com> | 2018-11-23 17:30:13 +0100 |
commit | cadcac9a8c0501f86372eb05d107d7b089643d0a (patch) | |
tree | 9593cc78763cba8e790e49c21f28a19182002765 /flags | |
parent | f67aa7bfa1553d8cc19ce4eef96f42ff8c31a320 (diff) |
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).
Diffstat (limited to 'flags')
15 files changed, 691 insertions, 0 deletions
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 @@ +<?xml version="1.0"?> +<!-- Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.vespa</groupId> + <artifactId>parent</artifactId> + <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + + <artifactId>flags</artifactId> + <version>6-SNAPSHOT</version> + <packaging>container-plugin</packaging> + <name>${project.artifactId}</name> + + <dependencies> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>defaults</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>container-dev</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-junit</artifactId> + <version>2.0.0.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>testutil</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>com.yahoo.vespa</groupId> + <artifactId>bundle-plugin</artifactId> + <extensions>true</extensions> + </plugin> + </plugins> + </build> +</project> 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<FlagSource, FeatureFlag> createUnbound(String flagId) { + return createUnbound(new FlagId(flagId)); + } + + public static Function<FlagSource, FeatureFlag> 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<String> getString(FlagId id) { + return getBytes(id).map(bytes -> new String(bytes, StandardCharsets.UTF_8)); + } + + public Optional<byte[]> 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<String> 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<FlagSource, IntFlag> createUnbound(String flagId, int defaultValue) { + return createUnbound(new FlagId(flagId), defaultValue); + } + + public static Function<FlagSource, IntFlag> 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<T> implements Flag { + private final static ObjectMapper mapper = new ObjectMapper(); + + private final FlagId id; + private final Class<T> jacksonClass; + private final T defaultValue; + private final FlagSource source; + + public static <T> Function<FlagSource, JacksonFlag<T>> createUnbound(String flagId, Class<T> jacksonClass, T defaultValue) { + return createUnbound(new FlagId(flagId), jacksonClass, defaultValue); + } + + public static <T> Function<FlagSource, JacksonFlag<T>> createUnbound(FlagId id, Class<T> jacksonClass, T defaultValue) { + return source -> new JacksonFlag<>(id, jacksonClass, defaultValue, source); + } + + public JacksonFlag(String flagId, Class<T> jacksonClass, T defaultValue, FlagSource source) { + this(new FlagId(flagId), jacksonClass, defaultValue, source); + } + + public JacksonFlag(FlagId id, Class<T> 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<FlagSource, LongFlag> createUnbound(String flagId, int defaultValue) { + return createUnbound(new FlagId(flagId), defaultValue); + } + + public static Function<FlagSource, LongFlag> 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<T> implements Flag { + private final static ObjectMapper mapper = new ObjectMapper(); + + private final FlagId id; + private final Class<T> jacksonClass; + private final FlagSource source; + + public static <T> Function<FlagSource, OptionalJacksonFlag<T>> createUnbound(String flagId, Class<T> jacksonClass) { + return createUnbound(new FlagId(flagId), jacksonClass); + } + + public static <T> Function<FlagSource, OptionalJacksonFlag<T>> createUnbound(FlagId id, Class<T> jacksonClass) { + return source -> new OptionalJacksonFlag<>(id, jacksonClass, source); + } + + public OptionalJacksonFlag(String flagId, Class<T> jacksonClass, FlagSource source) { + this(new FlagId(flagId), jacksonClass, source); + } + + public OptionalJacksonFlag(FlagId id, Class<T> jacksonClass, FlagSource source) { + this.id = id; + this.jacksonClass = jacksonClass; + this.source = source; + } + + @Override + public FlagId id() { + return id; + } + + public Optional<T> value() { + return source.getString(id).map(string -> uncheck(() -> mapper.readValue(string, jacksonClass))); + } + + public JacksonFlag<T> withDefault(T defaultValue) { + return new JacksonFlag<T>(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<FlagSource, OptionalStringFlag> createUnbound(String flagId) { + return createUnbound(new FlagId(flagId)); + } + + public static Function<FlagSource, OptionalStringFlag> 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<String> 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<FlagSource, StringFlag> createUnbound(String flagId, String defaultValue) { + return createUnbound(new FlagId(flagId), defaultValue); + } + + public static Function<FlagSource, StringFlag> 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<ExampleJacksonClass> jacksonFlag = new JacksonFlag<>(id.toString(), ExampleJacksonClass.class, defaultValue, source); + private final OptionalJacksonFlag<ExampleJacksonClass> 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 |