-
Notifications
You must be signed in to change notification settings - Fork 1
refactor(config): merge config files with BeanDefaults fallback #76
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
Changes from 1 commit
c8b53b6
afea1c9
418cb61
7de92be
650e8e1
7dd0137
2e44a07
627b85d
86f8d74
f8850be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| package org.tron.core.config; | ||
|
|
||
| import com.typesafe.config.Config; | ||
| import com.typesafe.config.ConfigFactory; | ||
| import java.beans.BeanInfo; | ||
| import java.beans.Introspector; | ||
| import java.beans.PropertyDescriptor; | ||
| import java.lang.reflect.Method; | ||
| import java.util.ArrayList; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Generates a Typesafe {@link Config} from a bean instance's current field values. | ||
| * | ||
| * <p>Used by each {@code XxxConfig.fromConfig()} to replace the role that | ||
| * {@code reference.conf} played: ensures every key ConfigBeanFactory needs is | ||
| * present, so a partial user config works without throwing | ||
| * {@code ConfigException.Missing}. | ||
| * | ||
| * <p>Only public getter+setter pairs (standard JavaBean properties) are included — | ||
| * the same set that {@code ConfigBeanFactory.create()} auto-binds. Keys are | ||
| * decapitalized exactly as ConfigBeanFactory does: | ||
| * {@code Character.toLowerCase(name.charAt(0)) + name.substring(1)}. | ||
| * | ||
| * <p>Nested bean fields are recursed into nested HOCON objects. | ||
| * {@code List} fields are serialized as HOCON lists (empty by default). | ||
| * Fields with no public setter (e.g. {@code @Getter(AccessLevel.NONE)} overrides) | ||
| * are automatically skipped — these are handled manually in each | ||
| * {@code fromConfig()} via {@code hasPath} guards. | ||
| */ | ||
| public final class BeanDefaults { | ||
|
|
||
| private BeanDefaults() {} | ||
|
|
||
| /** | ||
| * Convert {@code bean}'s public JavaBean properties to a Typesafe Config. | ||
| * The resulting Config can be used as a {@code withFallback()} for a user's | ||
| * config section to guarantee all keys are present for ConfigBeanFactory. | ||
| */ | ||
| public static Config toConfig(Object bean) { | ||
| return ConfigFactory.parseMap(toMap(bean)); | ||
| } | ||
|
|
||
| private static Map<String, Object> toMap(Object bean) { | ||
| Map<String, Object> map = new LinkedHashMap<>(); | ||
| try { | ||
| BeanInfo info = Introspector.getBeanInfo(bean.getClass()); | ||
| for (PropertyDescriptor pd : info.getPropertyDescriptors()) { | ||
| Method getter = pd.getReadMethod(); | ||
| Method setter = pd.getWriteMethod(); | ||
| // Skip read-only properties (no setter) — matches ConfigBeanFactory's contract | ||
| if (getter == null || setter == null) { | ||
| continue; | ||
| } | ||
| // Use the property name exactly as Introspector produced it. | ||
| // ConfigBeanFactory does configProps.get(beanProp.getName()) — the lookup key | ||
| // is the property name verbatim, not decapitalized. For ordinary camelCase | ||
| // setters (setMaxConnections → "MaxConnections" → decapitalize → "maxConnections") | ||
| // Introspector already returns the lowercase form. For setters that start with | ||
| // two consecutive uppercase letters (setPBFTEnable → "PBFTEnable") the JavaBean | ||
| // spec forbids decapitalization, so pd.getName() == "PBFTEnable" — matching the | ||
| // capital-P key that config.conf uses for those fields. | ||
| String key = pd.getName(); | ||
| Object value = getter.invoke(bean); | ||
| map.put(key, toValue(value)); | ||
| } | ||
| } catch (Exception ignored) { | ||
| // Best-effort: any unresolvable field is simply omitted. | ||
| // ConfigBeanFactory will throw with a clear path if a required key is missing. | ||
| } | ||
| return map; | ||
| } | ||
|
|
||
| private static Object toValue(Object value) { | ||
| if (value == null) { | ||
| return ""; | ||
| } | ||
| if (value instanceof Boolean || value instanceof Number || value instanceof String) { | ||
| return value; | ||
| } | ||
| if (value instanceof List) { | ||
| List<Object> list = new ArrayList<>(); | ||
| for (Object item : (List<?>) value) { | ||
| list.add(toValue(item)); | ||
| } | ||
| return list; | ||
| } | ||
| // Assume nested bean — recurse so it becomes a nested HOCON object. | ||
| return toMap(value); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -48,10 +48,11 @@ public static com.typesafe.config.Config getByFileName( | |||||||
|
|
||||||||
| private static void resolveConfigFile(String fileName, File confFile) { | ||||||||
| if (confFile.exists()) { | ||||||||
| config = ConfigFactory.parseFile(confFile) | ||||||||
| .withFallback(ConfigFactory.defaultReference()); | ||||||||
| config = ConfigFactory.parseFile(confFile); | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Prompt for AI agents
Suggested change
|
||||||||
| } else if (Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName) | ||||||||
| != null) { | ||||||||
| // ConfigFactory.load merges system properties (higher priority than the file), | ||||||||
| // which tests rely on to override storage.db.engine via -D flags. | ||||||||
| config = ConfigFactory.load(fileName); | ||||||||
| } else { | ||||||||
| throw new IllegalArgumentException( | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| import com.typesafe.config.Config; | ||
| import com.typesafe.config.ConfigBeanFactory; | ||
| import org.tron.core.config.BeanDefaults; | ||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
@@ -35,13 +36,11 @@ public static class InfluxDbConfig { | |
| private int metricsReportInterval = 10; | ||
| } | ||
|
Comment on lines
+29
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wire
Suggested fix public class MetricsConfig {
private PrometheusConfig prometheus = new PrometheusConfig();
+ private InfluxDbConfig influxDb = new InfluxDbConfig();
`@Getter`
`@Setter`
public static class PrometheusConfig {🤖 Prompt for AI Agents |
||
|
|
||
| // Defaults come from reference.conf (loaded globally via Configuration.java) | ||
|
|
||
| /** | ||
| * Create MetricsConfig from the "node.metrics" section of the application config. | ||
| */ | ||
| public static MetricsConfig fromConfig(Config config) { | ||
| Config section = config.getConfig("node.metrics"); | ||
| Config defaults = BeanDefaults.toConfig(new MetricsConfig()); | ||
| Config section = config.hasPath("node.metrics") | ||
| ? config.getConfig("node.metrics").withFallback(defaults) | ||
| : defaults; | ||
| return ConfigBeanFactory.create(section, MetricsConfig.class); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.