Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#5380 Introduced MappedConfigSource #6105

Merged
merged 24 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 9 additions & 5 deletions platform-sdk/docs/base/configuration/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,10 @@ following 3 interfaces:
- `com.swirlds.config.api.converter.ConfigConverter`
- `com.swirlds.config.api.validation.ConfigValidator`

The `swirlds-config-impl` module contains several implementations of those interfaces that are used for support, such as system properties, `BigDecimal` values and constraints annotations for validation.
The `swirlds-config-impl` module contains several implementations of those interfaces that are used for support, such as
system properties, `BigDecimal` values and constraints annotations for validation.

Implementations of all 3 interfaces must be registered before a configuration has been created.
Implementations of all 3 interfaces must be registered before a configuration has been created.
The `swirlds-config-impl` module already does this for several of the internal implementations. Custom implementations
can easily be registered when creating a `com.swirlds.config.api.Configuration` instance:

Expand Down Expand Up @@ -474,14 +475,17 @@ API today. For this 2 properties a custom parser exists (see

### Support for aliases

The platform contains the `com.swirlds.common.config.sources.AliasConfigSource` that will help to migrate all the config
The platform contains the `com.swirlds.common.config.sources.MappedConfigSource` that will help to migrate all the
config
properties to individual config data records. Having the properties separated by topics in records will make the code of
the platform much cleaner. By doing so configuration property names will change since topic based prefixes will be
added. If the property `csvOutputFolder` is, for example, only relevant for the metrics module of the platform it should
be migrated to a `MetricsConfig` record in the metrics module. By doing so the name of the property might change
to `metrics.csvOutputFolder`. Since such change will affect all installations of the platform we introduced the support
for aliases. In the `com.swirlds.platform.Browser` class an alias can be registered. For the given example we can create
the alias `metrics.csvOutputFolder->csvOutputFolder`. By doing so the property names in the config files do not need to
for aliases. In the `com.swirlds.platform.Browser` class a mapping can be registered. For the given example we can
create
the mapping `metrics.csvOutputFolder<->csvOutputFolder`. By doing so the property names in the config files do not need
to
be changed directly.

### Initialization of the config
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2023 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.common.config.sources;

import com.swirlds.common.utility.CommonUtils;
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
* between an original name and a mapped name.
*
* @param mappedName new property name
* @param originalName original property name
*/
public record ConfigMapping(@NonNull String mappedName, @NonNull String originalName) {

private static final Logger logger = LogManager.getLogger(MappedConfigSource.class);

/**
* Creates a new {@code ConfigMapping}
*
* @param mappedName new property name
* @param originalName original property name
* @throws IllegalArgumentException If {@code mappedName} and {@code originalName} are equal
*/
public ConfigMapping {
timo0 marked this conversation as resolved.
Show resolved Hide resolved
CommonUtils.throwArgBlank(mappedName, "mappedName");
CommonUtils.throwArgBlank(originalName, "originalName");
if (Objects.equals(originalName, mappedName)) {
throw new IllegalArgumentException(
"originalName and mappedName are the same (%s)! Will not create an mappedName"
.formatted(mappedName));
}
}

/**
* {@inheritDoc}
*/
@Override
public String toString() {
timo0 marked this conversation as resolved.
Show resolved Hide resolved
return "'" + mappedName + "'<->'" + originalName + "'";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2022-2023 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.common.config.sources;

import static com.swirlds.logging.LogMarker.CONFIG;

import com.swirlds.base.ArgumentUtils;
import com.swirlds.config.api.source.ConfigSource;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* A {@link ConfigSource} that can be used as a wrapper for other config sources, providing functionality to define
* mappings for given properties. This allows the same property value defined for one name in the wrapped
* {@link ConfigSource} to be accessed by both names.
* <p>
* For example, suppose you want to rename the "dbUrl" property to "general.databaseUrl". You can use a
* {@code MappingConfigSource} to create a mapping between "dbUrl" and "general.databaseUrl", so that you can use both
* names with the same value during the transition time.
* </p>
* <p>
* Note that multiple mappings defined for a property is not allowed and will throw an {@link IllegalArgumentException}
* at runtime.
* </p>
* <p>
* Note that adding a mapping to a property not defined in the original {@link ConfigSource} will throw an
* {@link IllegalArgumentException} at runtime.
* </p>
* <p>
* Note that the ordinal of this {@code ConfigSource} is taken from the original {@link ConfigSource} that was wrapped
* by this class.
* </p>
*
* @see ConfigSource
* @see ConfigMapping
*/
public class MappedConfigSource extends AbstractConfigSource {
private static final String PROPERTY_NOT_FOUND = "Property '{}' not found in original config source";
timo0 marked this conversation as resolved.
Show resolved Hide resolved
private static final String PROPERTY_ALREADY_DEFINED = "Property '%s' already defined";
private static final String DUPLICATE_PROPERTY = "Property '{}' already found in original config source";
private static final String PROPERTY_ALREADY_MAPPED = "Property '%s' has already a mapping defined";
private static final Logger logger = LogManager.getLogger(MappedConfigSource.class);

private final ConfigSource wrappedSource;

private final Queue<ConfigMapping> configMappings;
private final Map<String, String> properties;

/**
* Constructor that takes the wrapped config.
*
* @param wrappedSource the wrapped config
*/
public MappedConfigSource(@NonNull final ConfigSource wrappedSource) {
this.wrappedSource = ArgumentUtils.throwArgNull(wrappedSource, "wrappedSource");
configMappings = new ConcurrentLinkedQueue<>();
properties = new HashMap<>();
}

/**
* Adds the mappedName {@code 'mappedName'<->'originalName'}
*
* @param mappedName the mappedName name
* @param originalName the original name
*/
public void addMapping(@NonNull final String mappedName, @NonNull final String originalName) {
addMapping(new ConfigMapping(mappedName, originalName));
}

/**
* Adds the mappedName {@code 'mappedName'<->'originalName'}
*
* @param configMapping defined mapping
*/
public void addMapping(@NonNull final ConfigMapping configMapping) {
ArgumentUtils.throwArgNull(configMapping, "configMapping");

if (configMappings.stream()
.map(ConfigMapping::mappedName)
.anyMatch(m -> Objects.equals(m, configMapping.mappedName()))) {
throw new IllegalArgumentException(PROPERTY_ALREADY_DEFINED.formatted(configMapping.mappedName()));
}

if (configMappings.stream()
.map(ConfigMapping::originalName)
.anyMatch(o -> Objects.equals(o, configMapping.originalName()))) {
throw new IllegalArgumentException(PROPERTY_ALREADY_MAPPED.formatted(configMapping.originalName()));
}

configMappings.add(configMapping);
}

/**
* {@inheritDoc}
*/
@Override
@NonNull
protected Map<String, String> getInternalProperties() {
timo0 marked this conversation as resolved.
Show resolved Hide resolved
if (properties.isEmpty()) {
final Map<String, String> internalProperties = wrappedSource.getProperties();
final Map<String, String> 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());
} else {
mappedProperties.put(
configMapping.mappedName(), internalProperties.get(configMapping.originalName()));
logger.debug(CONFIG.getMarker(), "Added config mapping: {}", configMapping);
}
});

properties.putAll(internalProperties);
properties.putAll(mappedProperties);
}
return Collections.unmodifiableMap(properties);
}

/**
* {@inheritDoc}
*/
@Override
public int getOrdinal() {
timo0 marked this conversation as resolved.
Show resolved Hide resolved
return wrappedSource.getOrdinal();
}
}