From aa76d40f01faf894f8d9e9338bc28a316f622865 Mon Sep 17 00:00:00 2001
From: Benoit Lavenier <benoit.lavenier@e-is.pro>
Date: Mon, 16 May 2022 11:05:16 +0200
Subject: [PATCH] [fix] Update to Spring 5.3 and Nuiton config 3.4

---
 duniter4j-client/pom.xml                      |   8 ++
 duniter4j-core-client/pom.xml                 |   8 ++
 .../core/client/config/Configuration.java     | 118 ++++++++++++------
 .../client/config/ConfigurationOption.java    |  19 +--
 .../duniter/core/client/model/ModelUtils.java |   1 +
 .../model/elasticsearch/UserProfile.java      |  53 +-------
 .../util/spring/ConfigurableEnvironments.java |  60 +++++++++
 .../duniter4j-core-client_en_GB.properties    |   1 +
 .../duniter4j-core-client_fr_FR.properties    |   1 +
 pom.xml                                       |   4 +-
 10 files changed, 176 insertions(+), 97 deletions(-)
 create mode 100644 duniter4j-core-client/src/main/java/org/duniter/core/client/util/spring/ConfigurableEnvironments.java

diff --git a/duniter4j-client/pom.xml b/duniter4j-client/pom.xml
index db9fed8e..e470589d 100644
--- a/duniter4j-client/pom.xml
+++ b/duniter4j-client/pom.xml
@@ -86,6 +86,14 @@
             <artifactId>jansi</artifactId>
             <version>${jansiVersion}</version>
         </dependency>
+
+        <!-- spring (need to extends configuration) -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <version>${spring.version}</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/duniter4j-core-client/pom.xml b/duniter4j-core-client/pom.xml
index 75a4c164..1f7a0c20 100644
--- a/duniter4j-core-client/pom.xml
+++ b/duniter4j-core-client/pom.xml
@@ -91,6 +91,14 @@
       <artifactId>jackson-databind</artifactId>
     </dependency>
 
+    <!-- Spring (need for spring compatibility) -->
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-core</artifactId>
+      <version>${spring.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
     <!-- Unit test -->
     <dependency>
       <groupId>junit</groupId>
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java
index d98646f4..fde296ab 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java
@@ -25,20 +25,19 @@ package org.duniter.core.client.config;
 
 import com.google.common.base.Charsets;
 import lombok.extern.slf4j.Slf4j;
+import org.duniter.core.client.util.spring.ConfigurableEnvironments;
 import org.duniter.core.exception.TechnicalException;
-import org.nuiton.config.ApplicationConfig;
-import org.nuiton.config.ApplicationConfigHelper;
-import org.nuiton.config.ApplicationConfigProvider;
-import org.nuiton.config.ArgumentsParserException;
+import org.nuiton.config.*;
 import org.nuiton.version.Version;
 import org.nuiton.version.VersionBuilder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.springframework.core.env.ConfigurableEnvironment;
 
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.Arrays;
 import java.util.Locale;
+import java.util.Properties;
 import java.util.Set;
 
 import static org.nuiton.i18n.I18n.t;
@@ -83,28 +82,35 @@ public class Configuration  {
         super();
         this.applicationConfig = applicationConfig;
         this.optionKeyToNotSave = null;
-
         // Override application version
         initVersion(applicationConfig);
     }
 
-    public Configuration(String file, String... args) {
+    public Configuration(ConfigurableEnvironment env,
+                                String... args) {
+        this(env, "application.fake.properties", args);
+    }
+
+    public Configuration(String file,
+                         String... args) {
+        this(null, file, args);
+    }
+
+    protected Configuration(ConfigurableEnvironment env,
+                         String file,
+                         String... args) {
         super();
-        this.applicationConfig = new ApplicationConfig();
+        // load all default options
+        Set<ApplicationConfigProvider> providers = getProviders();
+        Properties defaults = getDefaults(providers, env);
+
+        // Create Nuiton config instance
+        this.applicationConfig = new ApplicationConfig(ApplicationConfigInit.forAllScopesWithout(
+            ApplicationConfigScope.HOME
+        ).setDefaults(defaults));
         this.applicationConfig.setEncoding(Charsets.UTF_8.name());
         this.applicationConfig.setConfigFileName(file);
 
-        // get allOfToList config providers
-        Set<ApplicationConfigProvider> providers =
-                ApplicationConfigHelper.getProviders(null,
-                        null,
-                        null,
-                        true);
-
-        // load allOfToList default options
-        ApplicationConfigHelper.loadAllDefaultOption(applicationConfig,
-                providers);
-
         // Load actions
         for (ApplicationConfigProvider provider : providers) {
             applicationConfig.loadActions(provider.getActions());
@@ -132,30 +138,35 @@ public class Configuration  {
             throw new TechnicalException(t("duniter4j.config.parse.error"), e);
         }
 
-        // TODO Review this, this is very dirty to do this...
-        File appBasedir = applicationConfig.getOptionAsFile(
-                ConfigurationOption.BASEDIR.getKey());
+        // Prepare base dir
+        fixBaseDir(applicationConfig);
+    }
 
-        if (appBasedir == null) {
-            appBasedir = new File("");
-        }
-        if (!appBasedir.isAbsolute()) {
-            appBasedir = new File(appBasedir.getAbsolutePath());
-        }
-        if (appBasedir.getName().equals("..")) {
-            appBasedir = appBasedir.getParentFile().getParentFile();
-        }
-        if (appBasedir.getName().equals(".")) {
-            appBasedir = appBasedir.getParentFile();
-        }
-        if (log.isInfoEnabled()) {
-            log.info("Application basedir: " + appBasedir);
+    protected static Set<ApplicationConfigProvider> getProviders() {
+        // get allOfToList config providers
+        return ApplicationConfigHelper.getProviders(null,
+                        null,
+                        null,
+                        true);
+    }
+
+    protected Properties getDefaults(Set<ApplicationConfigProvider> providers, ConfigurableEnvironment env) {
+
+        // Populate defaults from providers
+        final Properties defaults = new Properties();
+        providers.forEach(provider -> Arrays.stream(provider.getOptions())
+            .filter(configOptionDef -> configOptionDef.getDefaultValue() != null)
+            .forEach(configOptionDef -> defaults.setProperty(configOptionDef.getKey(), configOptionDef.getDefaultValue())));
+
+        // Set options from env if provided
+        if (env != null) {
+            return ConfigurableEnvironments.readProperties(env, defaults);
         }
-        applicationConfig.setOption(
-                ConfigurationOption.BASEDIR.getKey(),
-                appBasedir.getAbsolutePath());
+
+        return defaults;
     }
 
+
     /**
      * Override the version default option, from the MANIFEST implementation version (if any)
      * @param applicationConfig
@@ -184,7 +195,32 @@ public class Configuration  {
         applicationConfig.addAlias("-c", "--option", ConfigurationOption.NODE_CURRENCY.getKey());
         applicationConfig.addAlias("--salt", "--option", ConfigurationOption.USER_SALT.getKey());
         applicationConfig.addAlias("--passwd", "--option", ConfigurationOption.USER_PASSWD.getKey());
-     }
+    }
+
+    protected void fixBaseDir(ApplicationConfig applicationConfig) {
+        // TODO Review this, this is very dirty to do this...
+        File appBasedir = applicationConfig.getOptionAsFile(
+                ConfigurationOption.BASEDIR.getKey());
+
+        if (appBasedir == null) {
+            appBasedir = new File("");
+        }
+        if (!appBasedir.isAbsolute()) {
+            appBasedir = new File(appBasedir.getAbsolutePath());
+        }
+        if (appBasedir.getName().equals("..")) {
+            appBasedir = appBasedir.getParentFile().getParentFile();
+        }
+        if (appBasedir.getName().equals(".")) {
+            appBasedir = appBasedir.getParentFile();
+        }
+        if (log.isInfoEnabled()) {
+            log.info("Application basedir: " + appBasedir);
+        }
+        applicationConfig.setOption(
+                ConfigurationOption.BASEDIR.getKey(),
+                appBasedir.getAbsolutePath());
+    }
 
     public File getConfigFile() {
         if (configFile == null) {
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java
index ea5d6ef0..35b44db7 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java
@@ -44,11 +44,22 @@ public enum ConfigurationOption implements ConfigOptionDef {
     // ------------------------------------------------------------------------//
     // -- READ-ONLY OPTIONS ---------------------------------------------------//
     // ------------------------------------------------------------------------//
+    APP_NAME(
+            "duniter4j.name",
+            n("duniter4j.config.option.app.name.description"),
+            "duniter4j",
+            File.class),
+
+    VERSION(
+            "duniter4j.version",
+            n("duniter4j.config.option.version.description"),
+            "1.0.0",
+            Version.class),
 
     BASEDIR(
             "duniter4j.basedir",
             n("duniter4j.config.option.basedir.description"),
-            "${user.home}/.config/duniter4j",
+            "${user.home}/.config/${duniter4j.name}",
             File.class),
 
     DATA_DIRECTORY(
@@ -75,12 +86,6 @@ public enum ConfigurationOption implements ConfigOptionDef {
             "${duniter4j.data.directory}/cache",
             File.class),
 
-    VERSION(
-            "duniter4j.version",
-            n("duniter4j.config.option.version.description"),
-            "1.0",
-            Version.class),
-
     SITE_URL(
             "duniter4j.site.url",
             n("duniter4j.config.option.site.url.description"),
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/ModelUtils.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/ModelUtils.java
index e4106f05..f73c8c02 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/ModelUtils.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/ModelUtils.java
@@ -126,6 +126,7 @@ public class ModelUtils {
         if (pubkey == null || pubkey.length() < 6) {
             return pubkey;
         }
+        // TODO: use new pubkey minify
         return pubkey.substring(0, 8);
     }
 
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java
index dca4bc89..76d46e79 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java
@@ -22,61 +22,20 @@ package org.duniter.core.client.model.elasticsearch;
  * #L%
  */
 
+import lombok.Data;
+import lombok.experimental.FieldNameConstants;
+
 /**
  * Created by blavenie on 01/03/16.
  */
+@Data
+@FieldNameConstants
 public class UserProfile extends Record {
 
-    public static final String PROPERTY_TITLE = "title";
-    public static final String PROPERTY_DESCRIPTION="description";
-    public static final String PROPERTY_ADDRESS="address";
-    public static final String PROPERTY_CITY="city";
-    public static final String PROPERTY_EMAIL="email";
-    public static final String PROPERTY_LOCALE="locale";
-
     private String title;
     private String description;
     private String address;
+    private String city;
     private String email;
     private String locale;
-
-    public String getTitle() {
-        return title;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-    public String getEmail() {
-        return email;
-    }
-
-    public void setEmail(String email) {
-        this.email = email;
-    }
-
-    public String getLocale() {
-        return locale;
-    }
-
-    public void setLocale(String locale) {
-        this.locale = locale;
-    }
-
-    public String getAddress() {
-        return address;
-    }
-
-    public void setAddress(String address) {
-        this.address = address;
-    }
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/util/spring/ConfigurableEnvironments.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/util/spring/ConfigurableEnvironments.java
new file mode 100644
index 00000000..8422c09e
--- /dev/null
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/util/spring/ConfigurableEnvironments.java
@@ -0,0 +1,60 @@
+package org.duniter.core.client.util.spring;
+
+import com.google.common.collect.Lists;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.stream.Collectors;
+
+@Slf4j
+public class ConfigurableEnvironments {
+
+    protected ConfigurableEnvironments() {
+        // Helper class
+    }
+
+    public static Properties readProperties(@NonNull ConfigurableEnvironment env, Properties defaultOptions) {
+        boolean debug = log.isDebugEnabled();
+        List<MapPropertySource> sources = env.getPropertySources().stream()
+            .filter(source -> source instanceof MapPropertySource)
+            .map(source -> (MapPropertySource)source).collect(Collectors.toList());
+        final Properties target = new Properties(defaultOptions);
+
+        if (debug) log.debug("-- Reading environment properties... ---\n");
+        for (MapPropertySource source: Lists.reverse(sources)) {
+
+            if (debug) log.debug("Processing source {} ...", source.getName());
+
+            // Cascade properties (keep original order)
+            for (String key: source.getPropertyNames()) {
+                Object value = source.getProperty(key);
+                if (value != null) {
+                    if (debug) {
+                        if (target.containsKey(key)) log.debug(" {}={} /!\\ Overriding previous value", key, value);
+                        else log.debug(" {}={}", key, value);
+                    }
+                    target.setProperty(key, value.toString());
+                }
+            }
+        }
+
+        // DEBUG
+        if (debug) {
+            log.debug("-- Environment properties - final summary ---\n");
+            target.keySet()
+                .stream()
+                .map(Object::toString)
+                .sorted()
+                .forEach(key -> {
+                    Object value = target.getProperty(key);
+                    log.debug(" {}={}", key, value);
+                });
+        }
+
+        return target;
+    }
+}
diff --git a/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_en_GB.properties b/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_en_GB.properties
index 6d8cde9d..84c6ceff 100644
--- a/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_en_GB.properties
+++ b/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_en_GB.properties
@@ -7,6 +7,7 @@ duniter4j.client.core.timeout=
 duniter4j.client.notFound=Resource non found [%s]
 duniter4j.client.status=Http request error\: %s
 duniter4j.config=
+duniter4j.config.option.app.name.description=
 duniter4j.config.option.basedir.description=
 duniter4j.config.option.cache.directory.description=
 duniter4j.config.option.data.directory.description=
diff --git a/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_fr_FR.properties b/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_fr_FR.properties
index 291be52e..470918a3 100644
--- a/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_fr_FR.properties
+++ b/duniter4j-core-client/src/main/resources/i18n/duniter4j-core-client_fr_FR.properties
@@ -7,6 +7,7 @@ duniter4j.client.core.timeout=Délai d'attente de la requête dépassé
 duniter4j.client.notFound=Ressource non trouvée [%s]
 duniter4j.client.status=Echec de requete HTTP [%s] \: %s
 duniter4j.config=Options de configuration Duniter4j \:\: client
+duniter4j.config.option.app.name.description=
 duniter4j.config.option.basedir.description=Répertoire de travail de l'application
 duniter4j.config.option.cache.directory.description=Répertoire pour le cache applicatif
 duniter4j.config.option.data.directory.description=Répertoire de stockage des données
diff --git a/pom.xml b/pom.xml
index 417cac1c..c56c69f2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,12 +36,12 @@
     <lombok.version>1.18.20</lombok.version>
     <httpclient.version>4.5.10</httpclient.version>
 
-    <nuitonConfigVersion>3.0</nuitonConfigVersion>
+    <nuitonConfigVersion>3.4</nuitonConfigVersion>
     <nuitonVersionVersion>1.0-rc-2</nuitonVersionVersion>
     <nuitonI18nVersion>3.6.3</nuitonI18nVersion>
 
     <!-- UI versions -->
-    <spring.version>4.2.1.RELEASE</spring.version>
+    <spring.version>5.3.5</spring.version>
     <aspectj.version>1.8.7</aspectj.version>
 
     <!-- Unit test -->
-- 
GitLab