diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/test/grpc/GrpcTestBase.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/test/grpc/GrpcTestBase.java index 88fcc00be632..e06f66f51325 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/test/grpc/GrpcTestBase.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/test/grpc/GrpcTestBase.java @@ -59,6 +59,7 @@ import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.HashSet; +import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.Executors; @@ -334,6 +335,17 @@ public String getValue(@NonNull String s) throws NoSuchElementException { }; } + @Override + public boolean isListProperty(@NonNull final String propertyName) throws NoSuchElementException { + return false; + } + + @NonNull + @Override + public List getListValue(@NonNull final String propertyName) throws NoSuchElementException { + return List.of(); + } + public int port() { return port; } diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/VersionedConfigImpl.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/VersionedConfigImpl.java index 57de26c83404..9c390501b230 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/VersionedConfigImpl.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/VersionedConfigImpl.java @@ -56,6 +56,11 @@ public boolean exists(@NonNull final String s) { return wrappedConfig.exists(s); } + @Override + public boolean isListValue(@NonNull final String propertyName) { + return wrappedConfig.isListValue(propertyName); + } + @Override public String getValue(@NonNull final String s) throws NoSuchElementException { return wrappedConfig.getValue(s); diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/AccountIDConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/AccountIDConverter.java index 43d4f4b70516..3a191eb0d962 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/AccountIDConverter.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/AccountIDConverter.java @@ -18,6 +18,7 @@ import com.hedera.hapi.node.base.AccountID; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.stream.Stream; /** @@ -26,7 +27,7 @@ public class AccountIDConverter implements ConfigConverter { @Override - public AccountID convert(final String value) throws IllegalArgumentException, NullPointerException { + public AccountID convert(@NonNull final String value) throws IllegalArgumentException, NullPointerException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/CongestionMultipliersConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/CongestionMultipliersConverter.java index a5c957a2e178..1b123f182991 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/CongestionMultipliersConverter.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/CongestionMultipliersConverter.java @@ -18,6 +18,7 @@ import com.hedera.node.config.types.CongestionMultipliers; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Config api {@link ConfigConverter} implementation for the type {@link CongestionMultipliers}. @@ -25,7 +26,8 @@ public class CongestionMultipliersConverter implements ConfigConverter { @Override - public CongestionMultipliers convert(final String value) throws IllegalArgumentException, NullPointerException { + public CongestionMultipliers convert(@NonNull final String value) + throws IllegalArgumentException, NullPointerException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ContractIDConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ContractIDConverter.java index f0ac1580dc1d..3b23efb9ef02 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ContractIDConverter.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ContractIDConverter.java @@ -18,6 +18,7 @@ import com.hedera.hapi.node.base.ContractID; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.stream.Stream; /** @@ -26,7 +27,7 @@ public class ContractIDConverter implements ConfigConverter { @Override - public ContractID convert(final String value) throws IllegalArgumentException, NullPointerException { + public ContractID convert(@NonNull final String value) throws IllegalArgumentException, NullPointerException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityScaleFactorsConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityScaleFactorsConverter.java index 11f884161131..d6e57f2baba1 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityScaleFactorsConverter.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityScaleFactorsConverter.java @@ -18,6 +18,7 @@ import com.hedera.node.config.types.EntityScaleFactors; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Config api {@link ConfigConverter} implementation for the type {@link EntityScaleFactors}. @@ -25,7 +26,8 @@ public class EntityScaleFactorsConverter implements ConfigConverter { @Override - public EntityScaleFactors convert(final String value) throws IllegalArgumentException, NullPointerException { + public EntityScaleFactors convert(@NonNull final String value) + throws IllegalArgumentException, NullPointerException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/FileIDConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/FileIDConverter.java index fce0b94aba2d..5c7703e874aa 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/FileIDConverter.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/FileIDConverter.java @@ -18,6 +18,7 @@ import com.hedera.hapi.node.base.FileID; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.stream.Stream; /** @@ -26,7 +27,7 @@ public class FileIDConverter implements ConfigConverter { @Override - public FileID convert(final String value) throws IllegalArgumentException, NullPointerException { + public FileID convert(@NonNull final String value) throws IllegalArgumentException, NullPointerException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/KeyValuePairConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/KeyValuePairConverter.java index f6c28ee1570b..b21df2adb40a 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/KeyValuePairConverter.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/KeyValuePairConverter.java @@ -18,6 +18,7 @@ import com.hedera.node.config.types.KeyValuePair; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import java.util.regex.Pattern; @@ -29,7 +30,7 @@ public class KeyValuePairConverter implements ConfigConverter { private static final String PATTERN = Pattern.quote("="); @Override - public KeyValuePair convert(final String value) throws IllegalArgumentException, NullPointerException { + public KeyValuePair convert(@NonNull final String value) throws IllegalArgumentException, NullPointerException { Objects.requireNonNull(value, "Parameter 'value' cannot be null"); final String[] split = value.split(PATTERN, 2); if (split.length == 1) { diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/KnownBlockValuesConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/KnownBlockValuesConverter.java index 0cd5ce932798..5047cf1f7c7a 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/KnownBlockValuesConverter.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/KnownBlockValuesConverter.java @@ -18,6 +18,7 @@ import com.hedera.node.app.hapi.utils.sysfiles.domain.KnownBlockValues; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Config api {@link ConfigConverter} implementation for the type {@link KnownBlockValues}. @@ -25,7 +26,7 @@ public class KnownBlockValuesConverter implements ConfigConverter { @Override - public KnownBlockValues convert(final String value) throws IllegalArgumentException, NullPointerException { + public KnownBlockValues convert(@NonNull final String value) throws IllegalArgumentException, NullPointerException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/LongPairConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/LongPairConverter.java index 272456f0b04b..e769b8aa339e 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/LongPairConverter.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/LongPairConverter.java @@ -18,6 +18,7 @@ import com.hedera.node.config.types.LongPair; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import java.util.regex.Pattern; @@ -29,7 +30,7 @@ public class LongPairConverter implements ConfigConverter { private static final String PATTERN = Pattern.quote("-"); @Override - public LongPair convert(final String value) throws IllegalArgumentException, NullPointerException { + public LongPair convert(@NonNull final String value) throws IllegalArgumentException, NullPointerException { Objects.requireNonNull(value, "Parameter 'value' cannot be null"); final String[] split = value.split(PATTERN, 2); if (split.length <= 1) { diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ScaleFactorConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ScaleFactorConverter.java index 6212c631b631..8a0c76177a60 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ScaleFactorConverter.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ScaleFactorConverter.java @@ -18,6 +18,7 @@ import com.hedera.node.app.hapi.utils.sysfiles.domain.throttling.ScaleFactor; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Config api {@link ConfigConverter} implementation for the type {@link ScaleFactor}. @@ -25,7 +26,7 @@ public class ScaleFactorConverter implements ConfigConverter { @Override - public ScaleFactor convert(final String value) throws IllegalArgumentException, NullPointerException { + public ScaleFactor convert(@NonNull final String value) throws IllegalArgumentException, NullPointerException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/DynamicConfigSource.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/DynamicConfigSource.java index 6cf7434cfbff..87163df11674 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/DynamicConfigSource.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/DynamicConfigSource.java @@ -19,6 +19,7 @@ import com.swirlds.config.api.source.ConfigSource; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Properties; @@ -54,6 +55,17 @@ public String getValue(@NonNull final String s) throws NoSuchElementException { return properties.getProperty(s); } + @Override + public boolean isListProperty(@NonNull final String propertyName) throws NoSuchElementException { + return false; + } + + @NonNull + @Override + public List getListValue(@NonNull final String propertyName) throws NoSuchElementException { + return List.of(); + } + @Override public int getOrdinal() { return 500; diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/PropertyConfigSource.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/PropertyConfigSource.java index 4046f6240bed..907d5233ee4b 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/PropertyConfigSource.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/PropertyConfigSource.java @@ -23,6 +23,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.List; import java.util.NoSuchElementException; import java.util.Properties; import java.util.Set; @@ -88,6 +89,17 @@ public String getValue(@NonNull String key) throws NoSuchElementException { return properties.getProperty(key); } + @Override + public boolean isListProperty(@NonNull final String propertyName) throws NoSuchElementException { + return false; + } + + @NonNull + @Override + public List getListValue(@NonNull final String propertyName) throws NoSuchElementException { + return List.of(); + } + @Override public int getOrdinal() { return ordinal; diff --git a/platform-sdk/docs/base/configuration/configuration.md b/platform-sdk/docs/base/configuration/configuration.md index 47249fcc0736..c77d5575fd93 100644 --- a/platform-sdk/docs/base/configuration/configuration.md +++ b/platform-sdk/docs/base/configuration/configuration.md @@ -119,22 +119,22 @@ final Configuration config = ConfigurationBuilder.create() String value1 = config.getValue("app.var"); // value1 will be null String value2 = config.getValue("app.var", "default"); // value2 will be null String value3 = config.getValue("app.var", String.class); // value3 will be null -String value4 = config.getValue("app.var", String.class, "default"); // value4 will be null - +String value4 = config.getValue("app.var", String.class, "default"); // value4 will be null + String value5 = config.getValue("app.foo"); // expression will throw a NoSuchElementException String value6 = config.getValue("app.foo", "default"); // value6 will be "default" String value7 = config.getValue("app.foo", String.class); // expression will throw a NoSuchElementException -String value8 = config.getValue("app.foo", String.class, "default"); // value8 will be "default" - +String value8 = config.getValue("app.foo", String.class, "default"); // value8 will be "default" + List value9 = config.getValues("app.vars"); // value9 will be null List value10 = config.getValues("app.vars", List.of()); // value10 will be null List value11 = config.getValues("app.vars", String.class); // value11 will be null -List value12 = config.getValues("app.vars", String.class, List.of()); // value12 will be null - +List value12 = config.getValues("app.vars", String.class, List.of()); // value12 will be null + List value13 = config.getValues("app.foos"); // expression will throw a NoSuchElementException -List value14 = config.getValues("app.foos", List.of()); // value14 will be the empty default list +List value14 = config.getValues("app.foos", List.of()); // value14 will be the empty default list List value15 = config.getValues("app.foos", String.class); // expression will throw a NoSuchElementException -List value16 = config.getValues("app.foos", String.class, List.of()); // value16 will be the empty default list +List value16 = config.getValues("app.foos", String.class, List.of()); // value16 will be the empty default list ``` **Note:** if you want to define an empty list as a value for a property you should define it by @@ -171,7 +171,8 @@ The config API supports several datatypes for reading config properties. The fol 1994-11-05T08:15:30-05:00" - `Duration`: the custom format from the old settings is supported; examples are "2ms" or "10s" - `ChronoUnit`: examples are "millis" or "seconds" -- `Enum` +- `Enum` +- `InetAddress` The support for all data types is done by using the `com.swirlds.config.api.converter.ConfigConverter` API. The `com.swirlds.config.api.ConfigurationBuilder` must be used to add support for custom datatypes. The following code @@ -494,4 +495,3 @@ be changed directly. For the platform the config is provided by the `com.swirlds.common.context.PlatformContext`. Like the context the config is defined in the `com.swirlds.platform.Browser` class. - diff --git a/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/Configuration.java b/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/Configuration.java index 14759a021220..f7f477f1e99b 100644 --- a/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/Configuration.java +++ b/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/Configuration.java @@ -51,6 +51,13 @@ public interface Configuration { */ boolean exists(@NonNull String propertyName); + /** + * Checks if the property with the given name is a list property. + * @param propertyName the name of the property that should be checked + * @return true if the property is a list property, false otherwise + */ + boolean isListValue(@NonNull String propertyName); + /** * Returns the {@link String} value of the property with the given name. * diff --git a/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/source/ConfigSource.java b/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/source/ConfigSource.java index 6a73b7e92888..e756d4a2a27d 100644 --- a/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/source/ConfigSource.java +++ b/platform-sdk/swirlds-config-api/src/main/java/com/swirlds/config/api/source/ConfigSource.java @@ -18,9 +18,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.NoSuchElementException; import java.util.Set; @@ -49,11 +47,30 @@ public interface ConfigSource { * * @param propertyName the name of the property * @return the string value of the property - * @throws NoSuchElementException if the property with the given name is not defined in the source + * @throws NoSuchElementException if the property with the given name is not defined in the source OR is a list property */ @Nullable String getValue(@NonNull String propertyName) throws NoSuchElementException; + /** + * Checks if the property with the given name is a list property. + * + * @param propertyName the name of the property + * @return {@code true} if the property is a list property, {@code false} otherwise + * @throws NoSuchElementException if the property with the given name is not defined in the source OR is not a list property + */ + boolean isListProperty(@NonNull String propertyName) throws NoSuchElementException; + + /** + * Returns the list value of the property with the given name. + * + * @param propertyName the name of the property + * @return the list value of the property + * @throws NoSuchElementException if the property with the given name is not defined in the source OR is not a list property + */ + @NonNull + List getListValue(@NonNull String propertyName) throws NoSuchElementException; + /** * Returns the ordinal. The ordinal is used to define a priority order of all config sources while the config source * with the highest ordinal has the highest priority. A config source will overwrite values of properties that are @@ -66,18 +83,6 @@ default int getOrdinal() { return DEFAULT_ORDINAL; } - /** - * Returns all properties of this source as an immutable map. - * - * @return the map with all properties - */ - @NonNull - default Map getProperties() { - final Map props = new HashMap<>(); - getPropertyNames().forEach(prop -> props.put(prop, getValue(prop))); - return Collections.unmodifiableMap(props); - } - /** * Returns the name of the config source. A name must not be unique. * diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/AbstractConfigSource.java b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/AbstractConfigSource.java index 730607974756..50bfd976cba9 100644 --- a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/AbstractConfigSource.java +++ b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/AbstractConfigSource.java @@ -19,6 +19,7 @@ import com.swirlds.base.ArgumentUtils; import com.swirlds.config.api.source.ConfigSource; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; @@ -38,6 +39,7 @@ public abstract class AbstractConfigSource implements ConfigSource { /** * {@inheritDoc} */ + @NonNull @Override public final Set getPropertyNames() { return getInternalProperties().keySet(); @@ -54,4 +56,21 @@ public final String getValue(@NonNull final String propertyName) { } return getInternalProperties().get(propertyName); } + + /** + * This implementation always returns {@code false}. + */ + @Override + public boolean isListProperty(@NonNull final String propertyName) throws NoSuchElementException { + return false; + } + + /** + * This implementation always returns an empty list. + */ + @NonNull + @Override + public List getListValue(@NonNull final String propertyName) throws NoSuchElementException { + return List.of(); + } } diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/ConfigMapping.java b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/ConfigMapping.java index 8f7847204353..39382d652d90 100644 --- a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/ConfigMapping.java +++ b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/ConfigMapping.java @@ -19,8 +19,6 @@ import com.swirlds.base.ArgumentUtils; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * Represents a mapping between an original name and a mapped name. This class is used to hold the mapping configuration @@ -31,8 +29,6 @@ */ public record ConfigMapping(@NonNull String mappedName, @NonNull String originalName) { - private static final Logger logger = LogManager.getLogger(MappedConfigSource.class); - /** * Creates a new {@code ConfigMapping}. * diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/MappedConfigSource.java b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/MappedConfigSource.java index 4b33c46d5456..a94cb8c607d4 100644 --- a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/MappedConfigSource.java +++ b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/MappedConfigSource.java @@ -18,12 +18,17 @@ import com.swirlds.config.api.source.ConfigSource; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Collections; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Queue; +import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -52,7 +57,7 @@ * @see ConfigSource * @see ConfigMapping */ -public class MappedConfigSource extends AbstractConfigSource { +public class MappedConfigSource implements ConfigSource { private static final String PROPERTY_NOT_FOUND = "Property '{}' not found in original config source"; private static final String PROPERTY_ALREADY_DEFINED = "Property '%s' already defined"; private static final String DUPLICATE_PROPERTY = "Property '{}' already found in original config source"; @@ -63,6 +68,7 @@ public class MappedConfigSource extends AbstractConfigSource { private final Queue configMappings; private final Map properties; + private final Map> listProperties; /** * Constructor that takes the wrapped config. @@ -73,6 +79,7 @@ public MappedConfigSource(@NonNull final ConfigSource wrappedSource) { this.wrappedSource = Objects.requireNonNull(wrappedSource, "wrappedSource must not be null"); configMappings = new ConcurrentLinkedQueue<>(); properties = new HashMap<>(); + listProperties = new HashMap<>(); } /** @@ -106,34 +113,90 @@ public void addMapping(@NonNull final ConfigMapping configMapping) { } configMappings.add(configMapping); + properties.clear(); + listProperties.clear(); } - /** - * {@inheritDoc} - */ - @Override - @NonNull - protected Map getInternalProperties() { - if (properties.isEmpty()) { - final Map internalProperties = wrappedSource.getProperties(); - final Map mappedProperties = new HashMap<>(); - - configMappings.forEach(configMapping -> { - if (internalProperties.containsKey(configMapping.mappedName())) { - logger.warn(DUPLICATE_PROPERTY, configMapping.mappedName()); - } else if (!internalProperties.containsKey(configMapping.originalName())) { - logger.warn(PROPERTY_NOT_FOUND, configMapping.originalName()); + private void generateMapping() { + if (!properties.isEmpty() || !listProperties.isEmpty()) { + return; + } + + final Map internalProperties = new HashMap<>(); + final Map> internalListProperties = new HashMap<>(); + wrappedSource.getPropertyNames().forEach(propertyName -> { + if (wrappedSource.isListProperty(propertyName)) { + internalListProperties.put(propertyName, wrappedSource.getListValue(propertyName)); + } else { + internalProperties.put(propertyName, wrappedSource.getValue(propertyName)); + } + }); + + final Map mappedProperties = new HashMap<>(); + final Map> mappedListProperties = new HashMap<>(); + + configMappings.forEach(configMapping -> { + final String mappedName = configMapping.mappedName(); + final String originalName = configMapping.originalName(); + + if (internalProperties.containsKey(mappedName) || internalListProperties.containsKey(mappedName)) { + logger.warn(DUPLICATE_PROPERTY, mappedName); + } else if (!internalProperties.containsKey(originalName) + && !internalListProperties.containsKey(originalName)) { + logger.warn(PROPERTY_NOT_FOUND, originalName); + } else { + if (wrappedSource.isListProperty(originalName)) { + mappedListProperties.put(mappedName, internalListProperties.get(originalName)); } else { - mappedProperties.put( - configMapping.mappedName(), internalProperties.get(configMapping.originalName())); - logger.debug("Added config mapping: {}", configMapping); + mappedProperties.put(mappedName, internalProperties.get(originalName)); } - }); + logger.debug("Added config mapping: {}", configMapping); + } + }); - properties.putAll(internalProperties); - properties.putAll(mappedProperties); + properties.putAll(internalProperties); + properties.putAll(mappedProperties); + listProperties.putAll(internalListProperties); + listProperties.putAll(mappedListProperties); + } + + @NonNull + @Override + public Set getPropertyNames() { + generateMapping(); + return Stream.concat(properties.keySet().stream(), listProperties.keySet().stream()) + .collect(Collectors.toSet()); + } + + @Nullable + @Override + public String getValue(@NonNull final String propertyName) throws NoSuchElementException { + generateMapping(); + Objects.requireNonNull(propertyName, "propertyName must not be null"); + if (isListProperty(propertyName)) { + throw new NoSuchElementException("Property " + propertyName + " is a list property"); } - return Collections.unmodifiableMap(properties); + + return properties.get(propertyName); + } + + @Override + public boolean isListProperty(@NonNull final String propertyName) throws NoSuchElementException { + generateMapping(); + Objects.requireNonNull(propertyName, "propertyName must not be null"); + return listProperties.containsKey(propertyName); + } + + @NonNull + @Override + public List getListValue(@NonNull final String propertyName) throws NoSuchElementException { + generateMapping(); + Objects.requireNonNull(propertyName, "propertyName must not be null"); + if (!isListProperty(propertyName)) { + throw new NoSuchElementException("Property " + propertyName + " is not a list property"); + } + + return listProperties.get(propertyName); } /** diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SimpleConfigSource.java b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SimpleConfigSource.java index 6d3d2e0d432a..a17fbf941877 100644 --- a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SimpleConfigSource.java +++ b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SimpleConfigSource.java @@ -19,41 +19,54 @@ import static com.swirlds.config.extensions.sources.ConfigSourceOrdinalConstants.PROGRAMMATIC_VALUES_ORDINAL; import com.swirlds.base.ArgumentUtils; -import com.swirlds.config.api.Configuration; +import com.swirlds.config.api.source.ConfigSource; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; /** - * A {@link com.swirlds.config.api.source.ConfigSource} implementation that can be used to provide values of properties + * A {@link ConfigSource} implementation that can be used to provide values of properties * programmatically by calling {@link #withValue(String, Long)} (String, String)}. */ -public final class SimpleConfigSource extends AbstractConfigSource { +public final class SimpleConfigSource implements ConfigSource { private final Map internalProperties; + private final Map> internalListProperties; - private int oridinal = PROGRAMMATIC_VALUES_ORDINAL; + private int ordinal = PROGRAMMATIC_VALUES_ORDINAL; /** * Creates an instance without any config properties. */ public SimpleConfigSource() { - this.internalProperties = new HashMap<>(); + internalProperties = new HashMap<>(); + internalListProperties = new HashMap<>(); } /** - * Creates an instance without any config properties. + * Creates an instance with the given config properties and empty list properties. */ public SimpleConfigSource(@NonNull final Map properties) { + this(properties, new HashMap<>()); + } + + /** + * Creates an instance with the given config properties and list properties. + */ + public SimpleConfigSource( + @NonNull final Map properties, @NonNull final Map> listProperties) { // defensive copy - this.internalProperties = new HashMap<>(Objects.requireNonNull(properties)); + internalProperties = new HashMap<>(Objects.requireNonNull(properties)); + internalListProperties = new HashMap<>(Objects.requireNonNull(listProperties)); } /** @@ -67,6 +80,11 @@ public SimpleConfigSource(final String propertyName, final String value) { withValue(propertyName, value); } + public SimpleConfigSource(final String propertyName, final List value) { + this(); + setValues(propertyName, value, Object::toString); + } + /** * Creates an instance and directly adds the given config property. * @@ -259,12 +277,12 @@ private void setValues( ArgumentUtils.throwArgBlank(propertyName, "propertyName"); Objects.requireNonNull(converter, "converter must not be null"); if (values == null) { - internalProperties.put(propertyName, null); + internalListProperties.put(propertyName, null); } else if (values.isEmpty()) { - internalProperties.put(propertyName, Configuration.EMPTY_LIST); + internalListProperties.put(propertyName, List.of()); } else { - String rawValues = values.stream().map(converter).collect(Collectors.joining(",")); - internalProperties.put(propertyName, rawValues); + final List rawValues = values.stream().map(converter).toList(); + internalListProperties.put(propertyName, rawValues); } } @@ -275,7 +293,7 @@ private void setValues( * @return this */ public SimpleConfigSource withOrdinal(final int ordinal) { - this.oridinal = ordinal; + this.ordinal = ordinal; return this; } @@ -289,9 +307,43 @@ public void reset() { /** * {@inheritDoc} */ + @NonNull + @Override + public Set getPropertyNames() { + return Stream.concat(internalProperties.keySet().stream(), internalListProperties.keySet().stream()) + .collect(Collectors.toSet()); + } + + /** + * {@inheritDoc} + */ + @Nullable @Override - protected Map getInternalProperties() { - return Collections.unmodifiableMap(internalProperties); + public String getValue(@NonNull final String propertyName) throws NoSuchElementException { + if (internalProperties.containsKey(propertyName)) { + return internalProperties.get(propertyName); + } + throw new NoSuchElementException("Property " + propertyName + " not found"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isListProperty(@NonNull final String propertyName) throws NoSuchElementException { + return internalListProperties.containsKey(propertyName); + } + + /** + * {@inheritDoc} + */ + @NonNull + @Override + public List getListValue(@NonNull final String propertyName) throws NoSuchElementException { + if (internalListProperties.containsKey(propertyName)) { + return internalListProperties.get(propertyName); + } + throw new NoSuchElementException("Property " + propertyName + " not found"); } /** @@ -299,6 +351,6 @@ protected Map getInternalProperties() { */ @Override public int getOrdinal() { - return oridinal; + return ordinal; } } diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SystemEnvironmentConfigSource.java b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SystemEnvironmentConfigSource.java index f747ea93d1ba..7702289ff69c 100644 --- a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SystemEnvironmentConfigSource.java +++ b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SystemEnvironmentConfigSource.java @@ -19,6 +19,7 @@ import com.swirlds.base.ArgumentUtils; import com.swirlds.config.api.source.ConfigSource; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; import java.util.NoSuchElementException; import java.util.Set; @@ -52,6 +53,7 @@ public static SystemEnvironmentConfigSource getInstance() { /** * {@inheritDoc} */ + @NonNull @Override public Set getPropertyNames() { return System.getenv().keySet(); @@ -69,6 +71,23 @@ public String getValue(@NonNull final String propertyName) { return System.getenv(propertyName); } + /** + * {@inheritDoc} + */ + @Override + public boolean isListProperty(@NonNull final String propertyName) throws NoSuchElementException { + return false; + } + + /** + * {@inheritDoc} + */ + @NonNull + @Override + public List getListValue(@NonNull final String propertyName) throws NoSuchElementException { + return List.of(); + } + /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SystemPropertiesConfigSource.java b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SystemPropertiesConfigSource.java index 8c060c08f20f..239aed5869fa 100644 --- a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SystemPropertiesConfigSource.java +++ b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/SystemPropertiesConfigSource.java @@ -19,6 +19,7 @@ import com.swirlds.base.ArgumentUtils; import com.swirlds.config.api.source.ConfigSource; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; import java.util.NoSuchElementException; import java.util.Set; @@ -52,6 +53,7 @@ public static SystemPropertiesConfigSource getInstance() { /** * {@inheritDoc} */ + @NonNull @Override public Set getPropertyNames() { return System.getProperties().stringPropertyNames(); @@ -69,6 +71,23 @@ public String getValue(@NonNull final String propertyName) { return System.getProperty(propertyName); } + /** + * {@inheritDoc} + */ + @Override + public boolean isListProperty(@NonNull final String propertyName) throws NoSuchElementException { + return false; + } + + /** + * {@inheritDoc} + */ + @NonNull + @Override + public List getListValue(@NonNull final String propertyName) throws NoSuchElementException { + return List.of(); + } + /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/YamlConfigSource.java b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/YamlConfigSource.java new file mode 100644 index 000000000000..f61911748a5b --- /dev/null +++ b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/sources/YamlConfigSource.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.config.extensions.sources; + +import static java.util.Objects.requireNonNull; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.swirlds.config.api.source.ConfigSource; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.yaml.snakeyaml.Yaml; + +/** + * A config source that reads properties from a YAML file. + *
+ * The config source reads the properties from the YAML file. + *
+ * The keys of the properties are the full path of the property in the YAML file, separated by dots. + * For example: + * + * a: + * b: + * c: value + * + * For the above YAML file, the key for the property would be "a.b.c" to retrieve the "value". This complies with the way the + * properties are stored and accessed in the {@link ConfigSource} interface. + *
+ * All list elements are stored as JSON strings and can be deserialized in an {@link com.swirlds.config.api.converter.ConfigConverter} + */ +public class YamlConfigSource implements ConfigSource { + + /** + * The {@link ObjectMapper} used to convert maps to JSON. + */ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** + * The map that contains all not-list properties. + */ + private final Map properties; + + /** + * The map that contains all list properties. + */ + private final Map> listProperties; + + /** + * The ordinal of this config source. + */ + private final int ordinal; + + /** + * Creates a new {@link YamlConfigSource} instance with the given file name and default ordinal. + * + * @param fileName the name of the file that contains the properties + * @see ConfigSource#DEFAULT_ORDINAL + */ + public YamlConfigSource(@NonNull final String fileName) { + this(fileName, DEFAULT_ORDINAL); + } + + /** + * Creates a new {@link YamlConfigSource} instance with the given file name and ordinal. + * + * @param fileName the name of the file that contains the properties + * @param ordinal the ordinal of this config source + */ + public YamlConfigSource(@NonNull final String fileName, final int ordinal) { + requireNonNull(fileName, "fileName must not be null"); + this.properties = new HashMap<>(); + this.listProperties = new HashMap<>(); + this.ordinal = ordinal; + + try (InputStream resource = + Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName)) { + if (resource == null) { + throw new UncheckedIOException(new IOException("Resource not found: " + fileName)); + } + convertYamlToMaps(resource); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read YAML file " + fileName, e); + } + } + + /** + * Creates a new {@link YamlConfigSource} instance with the given file path and default ordinal. + * + * @param filePath the name of the file that contains the properties + * @see ConfigSource#DEFAULT_ORDINAL + */ + public YamlConfigSource(@NonNull final Path filePath) { + this(filePath, DEFAULT_ORDINAL); + } + + /** + * Creates a new {@link YamlConfigSource} instance with the given file path and ordinal. + * + * @param filePath the name of the file that contains the properties + * @param ordinal the ordinal of this config source + */ + public YamlConfigSource(@NonNull final Path filePath, final int ordinal) { + requireNonNull(filePath, "filePath must not be null"); + this.properties = new HashMap<>(); + this.listProperties = new HashMap<>(); + this.ordinal = ordinal; + + try (InputStream resource = Files.newInputStream(filePath)) { + convertYamlToMaps(resource); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read YAML file " + filePath, e); + } + } + + private void convertYamlToMaps(@NonNull final InputStream resource) { + Objects.requireNonNull(resource, "resource must not be null"); + final Yaml yaml = new Yaml(); + final Object rawData = yaml.load(resource); + processYamlNode("", rawData, properties, listProperties); + } + + @SuppressWarnings("unchecked") + private void processYamlNode( + @NonNull final String prefix, + @NonNull final Object node, + @NonNull final Map simpleProps, + @NonNull final Map> listProps) { + + if (!(node instanceof Map)) { + return; + } + + final Map map = (Map) node; + for (final Map.Entry entry : map.entrySet()) { + final String newPrefix = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey(); + final Object value = entry.getValue(); + + try { + switch (value) { + case final List list -> handleList(listProps, list, newPrefix); + case final Map mapValue -> handleMap(simpleProps, listProps, mapValue, newPrefix); + case null -> simpleProps.put(newPrefix, null); + default -> simpleProps.put(newPrefix, value.toString()); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to convert map to JSON for key: " + newPrefix, e); + } + } + } + + private void handleMap( + final @NonNull Map simpleProps, + final @NonNull Map> listProps, + final Map mapValue, + final String newPrefix) + throws JsonProcessingException { + if (mapValue.values().stream().noneMatch(v -> v instanceof Map || v instanceof List)) { + simpleProps.put(newPrefix, OBJECT_MAPPER.writeValueAsString(mapValue)); + } else { + processYamlNode(newPrefix, mapValue, simpleProps, listProps); + } + } + + private void handleList( + final @NonNull Map> listProps, final List list, final String newPrefix) { + if (!list.isEmpty() && list.getFirst() instanceof Map) { + final List jsonList = list.stream() + .map(item -> { + try { + return OBJECT_MAPPER.writeValueAsString(item); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to convert map to JSON", e); + } + }) + .toList(); + listProps.put(newPrefix, jsonList); + } else { + listProps.put(newPrefix, list.stream().map(Object::toString).toList()); + } + } + + /** {@inheritDoc} */ + @NonNull + @Override + public Set getPropertyNames() { + return Stream.concat(properties.keySet().stream(), listProperties.keySet().stream()) + .collect(Collectors.toSet()); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getValue(@NonNull String propertyName) throws NoSuchElementException { + requireNonNull(propertyName, "propertyName must not be null"); + if (isListProperty(propertyName)) { + throw new NoSuchElementException("Property " + propertyName + " is a list property"); + } + return properties.get(propertyName); + } + + /** {@inheritDoc} */ + @Override + public boolean isListProperty(@NonNull final String propertyName) throws NoSuchElementException { + requireNonNull(propertyName, "propertyName must not be null"); + return listProperties.containsKey(propertyName); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public List getListValue(@NonNull final String propertyName) throws NoSuchElementException { + requireNonNull(propertyName, "propertyName must not be null"); + if (!isListProperty(propertyName)) { + throw new NoSuchElementException("Property " + propertyName + " is not a list property"); + } + return listProperties.get(propertyName); + } + + /** {@inheritDoc} */ + @Override + public int getOrdinal() { + return ordinal; + } +} diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java b/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java index 5c238d8d0cf9..be27eda4e69b 100644 --- a/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java +++ b/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java @@ -7,6 +7,9 @@ requires transitive com.swirlds.config.api; requires com.swirlds.base; + requires com.fasterxml.jackson.core; + requires com.fasterxml.jackson.databind; requires org.apache.logging.log4j; + requires org.yaml.snakeyaml; requires static transitive com.github.spotbugs.annotations; } diff --git a/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/MappedConfigSourceTest.java b/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/MappedConfigSourceTest.java index 20b4fb8e9887..22ea64ef2392 100644 --- a/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/MappedConfigSourceTest.java +++ b/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/MappedConfigSourceTest.java @@ -19,6 +19,7 @@ import com.swirlds.config.extensions.sources.MappedConfigSource; import com.swirlds.config.extensions.sources.SimpleConfigSource; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; +import java.util.HashMap; import java.util.NoSuchElementException; import java.util.Set; import org.junit.jupiter.api.Assertions; @@ -48,7 +49,7 @@ void testNoAlias() { final var mappedConfigSource = new MappedConfigSource(configSource); // when - final var properties = mappedConfigSource.getProperties(); + final var properties = getObjectObjectHashMap(mappedConfigSource); // then Assertions.assertNotNull(properties); @@ -65,7 +66,7 @@ void testAlias() { // when mappedConfigSource.addMapping("b", "a"); - final var properties = mappedConfigSource.getProperties(); + final var properties = getObjectObjectHashMap(mappedConfigSource); // then Assertions.assertNotNull(properties); @@ -97,7 +98,7 @@ void testAliasForNotExistingProperty() { mappedConfigSource.addMapping("b", "not-available"); // then - Assertions.assertEquals(1, mappedConfigSource.getProperties().size()); + Assertions.assertEquals(1, mappedConfigSource.getPropertyNames().size()); Assertions.assertEquals(Set.of("a"), mappedConfigSource.getPropertyNames()); } @@ -177,7 +178,7 @@ void testNoMappingIfAlreadyInSource() { // when mappedConfigSource.addMapping("foo.a", "a"); - final var properties = mappedConfigSource.getProperties(); + final var properties = getObjectObjectHashMap(mappedConfigSource); // then Assertions.assertNotNull(properties); @@ -185,4 +186,10 @@ void testNoMappingIfAlreadyInSource() { Assertions.assertEquals("1", properties.get("a")); Assertions.assertEquals("2", properties.get("foo.a")); } + + private static HashMap getObjectObjectHashMap(final MappedConfigSource mappedConfigSource) { + final var properties = new HashMap<>(); + mappedConfigSource.getPropertyNames().forEach(p -> properties.put(p, mappedConfigSource.getValue(p))); + return properties; + } } diff --git a/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/sources/YamlConfigSourceTest.java b/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/sources/YamlConfigSourceTest.java new file mode 100644 index 000000000000..81429dd54968 --- /dev/null +++ b/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/sources/YamlConfigSourceTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.config.extensions.test.sources; + +import com.swirlds.config.extensions.sources.YamlConfigSource; +import java.io.UncheckedIOException; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class YamlConfigSourceTest { + + @Test + void testPropertyConfigSourceWhenPropertyFileNotFound() { + // given + final String nonExistentFile = "non-existent-file.yaml"; + + // then + Assertions.assertThrows(UncheckedIOException.class, () -> new YamlConfigSource(nonExistentFile, 1)); + } + + @Test + void testLoadProperties() throws Exception { + // given + final String existingFile = "test.yaml"; + + // when + final YamlConfigSource yamlConfigSource = new YamlConfigSource(existingFile, 1); + + // then + Assertions.assertNotNull(yamlConfigSource); + + Assertions.assertTrue(yamlConfigSource.isListProperty("gossip.interfaceBindings")); + final List interfaceBindings = yamlConfigSource.getListValue("gossip.interfaceBindings"); + Assertions.assertEquals(4, interfaceBindings.size()); + Assertions.assertEquals("{\"nodeId\":0,\"hostname\":\"10.10.10.1\",\"port\":1234}", interfaceBindings.get(0)); + Assertions.assertEquals("{\"nodeId\":0,\"hostname\":\"10.10.10.2\",\"port\":1234}", interfaceBindings.get(1)); + Assertions.assertEquals("{\"nodeId\":1,\"hostname\":\"10.10.10.3\",\"port\":1234}", interfaceBindings.get(2)); + Assertions.assertEquals( + "{\"nodeId\":4,\"hostname\":\"2001:db8:3333:4444:5555:6666:7777:8888\",\"port\":1234}", + interfaceBindings.get(3)); + + Assertions.assertEquals("random", yamlConfigSource.getValue("gossip.randomStringValue")); + Assertions.assertEquals("42", yamlConfigSource.getValue("gossip.randomIntValue")); + Assertions.assertEquals( + "{\"nodeId\":5,\"hostname\":\"10.10.10.45\",\"port\":23424}", + yamlConfigSource.getValue("gossip.randomObj")); + + Assertions.assertTrue(yamlConfigSource.isListProperty("gossip.randomList")); + final List randomList = yamlConfigSource.getListValue("gossip.randomList"); + Assertions.assertEquals(2, randomList.size()); + Assertions.assertEquals("foo", randomList.get(0)); + Assertions.assertEquals("bar", randomList.get(1)); + + Assertions.assertTrue(yamlConfigSource.isListProperty("gossip.randomListOfList")); + } +} diff --git a/platform-sdk/swirlds-config-extensions/src/test/resources/test.yaml b/platform-sdk/swirlds-config-extensions/src/test/resources/test.yaml new file mode 100644 index 000000000000..1451416f62d8 --- /dev/null +++ b/platform-sdk/swirlds-config-extensions/src/test/resources/test.yaml @@ -0,0 +1,17 @@ +gossip: + interfaceBindings: + - { "nodeId": 0, "hostname": "10.10.10.1", "port": 1234 } + - { "nodeId": 0, "hostname": "10.10.10.2", "port": 1234 } + - { "nodeId": 1, "hostname": "10.10.10.3", "port": 1234 } + - { "nodeId": 4, "hostname": "2001:db8:3333:4444:5555:6666:7777:8888", "port": 1234 } + randomStringValue: "random" + randomIntValue: 42 + randomObj: { "nodeId": 5, "hostname": "10.10.10.45", "port": 23424 } + randomList: + - "foo" + - "bar" + randomListOfList: + - - "foo" + - "bar" + - - "baz" + - "qux" diff --git a/platform-sdk/swirlds-config-extensions/src/testFixtures/java/com/swirlds/config/extensions/test/fixtures/TestConfigBuilder.java b/platform-sdk/swirlds-config-extensions/src/testFixtures/java/com/swirlds/config/extensions/test/fixtures/TestConfigBuilder.java index 3d71edbcc53d..0315d13efbcb 100644 --- a/platform-sdk/swirlds-config-extensions/src/testFixtures/java/com/swirlds/config/extensions/test/fixtures/TestConfigBuilder.java +++ b/platform-sdk/swirlds-config-extensions/src/testFixtures/java/com/swirlds/config/extensions/test/fixtures/TestConfigBuilder.java @@ -16,7 +16,6 @@ package com.swirlds.config.extensions.test.fixtures; -import com.swirlds.common.config.singleton.ConfigurationHolder; import com.swirlds.common.threading.locks.AutoClosableLock; import com.swirlds.common.threading.locks.Locks; import com.swirlds.common.threading.locks.locked.Locked; @@ -165,7 +164,6 @@ public Configuration getOrCreateConfig() { try (final Locked ignore = configLock.lock()) { if (configuration == null) { configuration = builder.build(); - ConfigurationHolder.getInstance().setConfiguration(configuration); } return configuration; } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BigDecimalConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BigDecimalConverter.java index d164a5b853a9..f5cf9ea5cbcf 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BigDecimalConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BigDecimalConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigDecimal; /** @@ -29,7 +30,7 @@ public final class BigDecimalConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public BigDecimal convert(final String value) throws IllegalArgumentException { + public BigDecimal convert(@NonNull final String value) throws IllegalArgumentException { return new BigDecimal(value); } } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BigIntegerConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BigIntegerConverter.java index d86dacc3174f..85b164e235e9 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BigIntegerConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BigIntegerConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; /** @@ -29,7 +30,7 @@ public final class BigIntegerConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public BigInteger convert(final String value) throws IllegalArgumentException { + public BigInteger convert(@NonNull final String value) throws IllegalArgumentException { return new BigInteger(value); } } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BooleanConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BooleanConverter.java index 73dfdb43e1e8..530c59c65b0c 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BooleanConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/BooleanConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Concrete {@link ConfigConverter} implementation that provides the support for {@link Boolean} values in the @@ -28,7 +29,7 @@ public final class BooleanConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public Boolean convert(final String value) throws IllegalArgumentException { + public Boolean convert(@NonNull final String value) throws IllegalArgumentException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ByteConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ByteConverter.java index 02c355055895..88591bdb5e92 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ByteConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ByteConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Concrete {@link ConfigConverter} implementation that provides the support for {@link Byte} values in the @@ -28,7 +29,7 @@ public final class ByteConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public Byte convert(final String value) throws IllegalArgumentException { + public Byte convert(@NonNull final String value) throws IllegalArgumentException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ChronoUnitConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ChronoUnitConverter.java index 458231c6035c..8f016358dc67 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ChronoUnitConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ChronoUnitConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Objects; @@ -30,7 +31,7 @@ public class ChronoUnitConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public ChronoUnit convert(final String value) throws IllegalArgumentException { + public ChronoUnit convert(@NonNull final String value) throws IllegalArgumentException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/DoubleConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/DoubleConverter.java index 3852c735bc85..944c372bd62a 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/DoubleConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/DoubleConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Concrete {@link ConfigConverter} implementation that provides the support for {@link Double} values in the @@ -28,7 +29,7 @@ public final class DoubleConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public Double convert(final String value) throws IllegalArgumentException { + public Double convert(@NonNull final String value) throws IllegalArgumentException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/DurationConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/DurationConverter.java index 9e134ed95cc1..2c93d1eeca06 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/DurationConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/DurationConverter.java @@ -26,6 +26,7 @@ import static com.swirlds.base.units.UnitConstants.WEEKS_TO_DAYS; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; import java.time.format.DateTimeParseException; import java.util.regex.Matcher; @@ -51,7 +52,7 @@ public final class DurationConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public Duration convert(final String value) throws IllegalArgumentException { + public Duration convert(@NonNull final String value) throws IllegalArgumentException { return parseDuration(value); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/FileConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/FileConverter.java index b116ab0e4f5d..0c8c2367c459 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/FileConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/FileConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.File; /** @@ -29,7 +30,7 @@ public final class FileConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public File convert(final String value) throws IllegalArgumentException { + public File convert(@NonNull final String value) throws IllegalArgumentException { return new File(value); } } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/FloatConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/FloatConverter.java index d687edfba8ec..bb58225afd56 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/FloatConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/FloatConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Concrete {@link ConfigConverter} implementation that provides the support for {@link Float} values in the @@ -28,7 +29,7 @@ public final class FloatConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public Float convert(final String value) throws IllegalArgumentException { + public Float convert(@NonNull final String value) throws IllegalArgumentException { return Float.valueOf(value); } } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/InetAddressConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/InetAddressConverter.java new file mode 100644 index 000000000000..b95aa7570bdd --- /dev/null +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/InetAddressConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.config.impl.converters; + +import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Objects; + +/** + * A {@link ConfigConverter} that converts a string to an {@link InetAddress}. + */ +public class InetAddressConverter implements ConfigConverter { + + /** + * {{@inheritDoc}} + */ + @Nullable + @Override + public InetAddress convert(@NonNull final String value) throws IllegalArgumentException, NullPointerException { + Objects.requireNonNull(value, "value must not be null"); + try { + return InetAddress.getByName(value); + } catch (UnknownHostException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/IntegerConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/IntegerConverter.java index 41d794ff8596..758e88f3cd84 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/IntegerConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/IntegerConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Concrete {@link ConfigConverter} implementation that provides the support for {@link Integer} values in the @@ -28,7 +29,7 @@ public final class IntegerConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public Integer convert(final String value) throws IllegalArgumentException { + public Integer convert(@NonNull final String value) throws IllegalArgumentException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/LongConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/LongConverter.java index 5cebe9e9b5c8..51b9fb6a9a94 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/LongConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/LongConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Concrete {@link ConfigConverter} implementation that provides the support for {@link Long} values in the @@ -28,7 +29,7 @@ public final class LongConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public Long convert(final String value) throws IllegalArgumentException { + public Long convert(@NonNull final String value) throws IllegalArgumentException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/PathConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/PathConverter.java index a640a9736aed..8e48bdda56ce 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/PathConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/PathConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.nio.file.Path; import java.nio.file.Paths; @@ -30,7 +31,7 @@ public final class PathConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public Path convert(final String value) throws IllegalArgumentException { + public Path convert(@NonNull final String value) throws IllegalArgumentException { return Paths.get(value); } } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ShortConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ShortConverter.java index b76c6442a120..1f566b42cc1f 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ShortConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ShortConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Concrete {@link ConfigConverter} implementation that provides the support for {@link Short} values in the @@ -28,7 +29,7 @@ public final class ShortConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public Short convert(final String value) throws IllegalArgumentException { + public Short convert(@NonNull final String value) throws IllegalArgumentException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/StringConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/StringConverter.java index f8b59e7e5ad7..1fb5cabc2401 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/StringConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/StringConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; /** * Concrete {@link ConfigConverter} implementation that provides the support for {@link String} values in the @@ -28,7 +29,7 @@ public class StringConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public String convert(final String value) throws IllegalArgumentException { + public String convert(@NonNull final String value) throws IllegalArgumentException { if (value == null) { throw new NullPointerException("value must not be null"); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/UriConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/UriConverter.java index 0e41b09d21a0..1f685f370543 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/UriConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/UriConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.net.URI; import java.net.URISyntaxException; @@ -30,7 +31,7 @@ public final class UriConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public URI convert(final String value) throws IllegalArgumentException { + public URI convert(@NonNull final String value) throws IllegalArgumentException { try { return new URI(value); } catch (URISyntaxException e) { diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/UrlConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/UrlConverter.java index 8b51abe8146a..d06b8b6b793f 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/UrlConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/UrlConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.net.MalformedURLException; import java.net.URL; @@ -30,7 +31,7 @@ public final class UrlConverter implements ConfigConverter { * {@inheritDoc} */ @Override - public URL convert(final String value) throws IllegalArgumentException { + public URL convert(@NonNull final String value) throws IllegalArgumentException { if (value == null) { throw new NullPointerException("null can not be converted"); } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ZonedDateTimeConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ZonedDateTimeConverter.java index 40ea4731ed81..761fc9f8b028 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ZonedDateTimeConverter.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/ZonedDateTimeConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl.converters; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -30,7 +31,7 @@ public final class ZonedDateTimeConverter implements ConfigConverter genericType = getGenericSetType(component); return configuration.getValueSet(name, genericType); } - return configuration.getValue(name, valueType); + if (configuration.isListValue(name)) { + return configuration.getValues(name, valueType); + } else { + return configuration.getValue(name, valueType); + } } } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigPropertiesService.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigPropertiesService.java index b653b2477ae9..0de835917336 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigPropertiesService.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigPropertiesService.java @@ -17,12 +17,13 @@ package com.swirlds.config.impl.internal; import com.swirlds.base.ArgumentUtils; -import com.swirlds.config.api.source.ConfigSource; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.stream.Stream; /** @@ -35,6 +36,11 @@ class ConfigPropertiesService implements ConfigLifecycle { */ private final Map internalProperties; + /** + * stores all config properties. + */ + private final Map> internalListProperties; + /** * The services that is used to load all properties in the correct order. */ @@ -48,22 +54,30 @@ class ConfigPropertiesService implements ConfigLifecycle { ConfigPropertiesService(@NonNull final ConfigSourceService configSourceService) { this.configSourceService = Objects.requireNonNull(configSourceService, "configSourceService must not be null"); internalProperties = new HashMap<>(); + internalListProperties = new HashMap<>(); } @Override public void init() { throwIfInitialized(); - configSourceService - .getSources() - .map(ConfigSource::getProperties) - .flatMap(map -> map.entrySet().stream()) - .forEach(entry -> addProperty(entry.getKey(), entry.getValue())); + configSourceService.getSources().forEach(configSource -> { + final Set propertyNames = configSource.getPropertyNames(); + propertyNames.forEach(propertyName -> { + ArgumentUtils.throwArgBlank(propertyName, "propertyName"); + if (configSource.isListProperty(propertyName)) { + internalListProperties.put(propertyName, configSource.getListValue(propertyName)); + } else { + internalProperties.put(propertyName, configSource.getValue(propertyName)); + } + }); + }); initialized = true; } @Override public void dispose() { internalProperties.clear(); + internalListProperties.clear(); initialized = false; } @@ -75,13 +89,13 @@ public boolean isInitialized() { @NonNull Stream getPropertyNames() { throwIfNotInitialized(); - return internalProperties.keySet().stream(); + return Stream.concat(internalProperties.keySet().stream(), internalListProperties.keySet().stream()); } boolean containsKey(@NonNull final String propertyName) { throwIfNotInitialized(); ArgumentUtils.throwArgBlank(propertyName, "propertyName"); - return internalProperties.containsKey(propertyName); + return internalProperties.containsKey(propertyName) || internalListProperties.containsKey(propertyName); } @Nullable @@ -91,8 +105,16 @@ String getProperty(@NonNull final String propertyName) { return internalProperties.get(propertyName); } - private void addProperty(@NonNull final String propertyName, @Nullable final String propertyValue) { + boolean isListProperty(@NonNull final String propertyName) { + throwIfNotInitialized(); + ArgumentUtils.throwArgBlank(propertyName, "propertyName"); + return internalListProperties.containsKey(propertyName); + } + + @NonNull + List getListProperty(@NonNull final String propertyName) { + throwIfNotInitialized(); ArgumentUtils.throwArgBlank(propertyName, "propertyName"); - internalProperties.put(propertyName, propertyValue); + return internalListProperties.get(propertyName); } } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigurationImpl.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigurationImpl.java index 1c2b9e2285a0..4c3112de006e 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigurationImpl.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigurationImpl.java @@ -61,6 +61,11 @@ public boolean exists(@NonNull final String propertyName) { return propertiesService.containsKey(propertyName); } + @Override + public boolean isListValue(@NonNull final String propertyName) { + return propertiesService.isListProperty(propertyName); + } + @SuppressWarnings("unchecked") @Nullable @Override @@ -90,6 +95,9 @@ public T getValue( @Override public String getValue(@NonNull final String propertyName) { ArgumentUtils.throwArgBlank(propertyName, "propertyName"); + if (propertiesService.isListProperty(propertyName)) { + throw new NoSuchElementException("Property '" + propertyName + "' is a list property"); + } if (!exists(propertyName)) { throw new NoSuchElementException("Property '" + propertyName + "' not defined in configuration"); } @@ -100,6 +108,9 @@ public String getValue(@NonNull final String propertyName) { @Override public String getValue(@NonNull final String propertyName, @Nullable final String defaultValue) { ArgumentUtils.throwArgBlank(propertyName, "propertyName"); + if (propertiesService.isListProperty(propertyName)) { + throw new NoSuchElementException("Property '" + propertyName + "' is a list property"); + } if (!exists(propertyName)) { return defaultValue; } @@ -110,21 +121,26 @@ public String getValue(@NonNull final String propertyName, @Nullable final Strin @Override public List getValues(@NonNull final String propertyName) { ArgumentUtils.throwArgBlank(propertyName, "propertyName"); - final String rawValue = getValue(propertyName); - if (rawValue == null) { - return null; + if (!propertiesService.isListProperty(propertyName)) { + throw new NoSuchElementException("Property '" + propertyName + "' is not a list property"); } - return ConfigListUtils.createList(rawValue); + if (!exists(propertyName)) { + throw new NoSuchElementException("Property '" + propertyName + "' not defined in configuration"); + } + return propertiesService.getListProperty(propertyName); } @Nullable @Override public List getValues(@NonNull final String propertyName, @Nullable final List defaultValue) { ArgumentUtils.throwArgBlank(propertyName, "propertyName"); + if (!propertiesService.isListProperty(propertyName)) { + throw new NoSuchElementException("Property '" + propertyName + "' is not a list property"); + } if (!exists(propertyName)) { return defaultValue; } - return getValues(propertyName); + return propertiesService.getListProperty(propertyName); } @Nullable @@ -132,7 +148,13 @@ public List getValues(@NonNull final String propertyName, @Nullable fina public List getValues(@NonNull final String propertyName, @NonNull final Class propertyType) { ArgumentUtils.throwArgBlank(propertyName, "propertyName"); Objects.requireNonNull(propertyType, "propertyType must not be null"); - final List values = getValues(propertyName); + final List values; + if (!propertiesService.isListProperty(propertyName)) { + final String value = getValue(propertyName); + values = ConfigListUtils.createList(value); + } else { + values = getValues(propertyName); + } if (values == null) { return null; } diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java index 8307a1284849..0d5723ac7d71 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java @@ -27,6 +27,7 @@ import com.swirlds.config.impl.converters.EnumConverter; import com.swirlds.config.impl.converters.FileConverter; import com.swirlds.config.impl.converters.FloatConverter; +import com.swirlds.config.impl.converters.InetAddressConverter; import com.swirlds.config.impl.converters.IntegerConverter; import com.swirlds.config.impl.converters.LongConverter; import com.swirlds.config.impl.converters.PathConverter; @@ -40,6 +41,7 @@ import java.io.File; import java.math.BigDecimal; import java.math.BigInteger; +import java.net.InetAddress; import java.net.URI; import java.net.URL; import java.nio.file.Path; @@ -92,6 +94,8 @@ class ConverterService implements ConfigLifecycle { private static final ConfigConverter CHRONO_UNIT_CONVERTER = new ChronoUnitConverter(); + private static final ConfigConverter INET_ADDRESS_CONFIG_CONVERTER = new InetAddressConverter(); + ConverterService() { this.converters = new ConcurrentHashMap<>(); } @@ -173,6 +177,7 @@ public void init() { addConverter(ZonedDateTime.class, ZONED_DATE_TIME_CONVERTER); addConverter(Duration.class, DURATION_CONVERTER); addConverter(ChronoUnit.class, CHRONO_UNIT_CONVERTER); + addConverter(InetAddress.class, INET_ADDRESS_CONFIG_CONVERTER); initialized = true; } diff --git a/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/ConfigApiTests.java b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/ConfigApiTests.java index d3285f6a3d33..ca38fef9482e 100644 --- a/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/ConfigApiTests.java +++ b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/ConfigApiTests.java @@ -446,7 +446,7 @@ void registerCustomConverter() { void testNullList() { // given final Configuration configuration = ConfigurationBuilder.create() - .withSources(new SimpleConfigSource("sample.list", (String) null)) + .withSources(new SimpleConfigSource("sample.list", (List) null)) .build(); // when diff --git a/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/TestDateConverter.java b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/TestDateConverter.java index 5f2abce11768..cc9727d311d4 100644 --- a/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/TestDateConverter.java +++ b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/TestDateConverter.java @@ -17,6 +17,7 @@ package com.swirlds.config.impl; import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Date; /** @@ -25,7 +26,7 @@ public class TestDateConverter implements ConfigConverter { @Override - public Date convert(final String value) throws IllegalArgumentException, NullPointerException { + public Date convert(@NonNull final String value) throws IllegalArgumentException, NullPointerException { return new Date(Long.parseLong(value)); } }