UNOMI-626: improve migration system to prepare Unomi 2.0.0 data model (#458)

* UNOMI-626: improve migration system to prepare Unomi 2.0.0 data models migrations

* UNOMI-626: improve migration system to prepare Unomi 2.0.0 data models migrations

* UNOMI-626: improve migration system to prepare Unomi 2.0.0 data models migrations
diff --git a/tools/shell-commands/pom.xml b/tools/shell-commands/pom.xml
index a19daa5..57ae9dc 100644
--- a/tools/shell-commands/pom.xml
+++ b/tools/shell-commands/pom.xml
@@ -79,6 +79,13 @@
             <version>4.11</version>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy</artifactId>
+            <version>${groovy.version}</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -88,6 +95,11 @@
                 <artifactId>maven-bundle-plugin</artifactId>
                 <configuration>
                     <instructions>
+                        <Export-Package>
+                            org.apache.unomi.shell.migration.utils
+                        </Export-Package>
+                        <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
+                        <DynamicImport-Package>*</DynamicImport-Package>
                         <Karaf-Commands>*</Karaf-Commands>
                     </instructions>
                 </configuration>
diff --git a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/Migration.java b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/Migration.java
index 2858e9e..8a64882 100644
--- a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/Migration.java
+++ b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/Migration.java
@@ -19,41 +19,23 @@
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.karaf.shell.api.console.Session;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.Version;
 
 import java.io.IOException;
+import java.util.Map;
 
 /**
- * This interface must be implemented if you create a new migration class
  * @author dgaillard
+ * @deprecated use groovy script for implementing new migrations
  */
 public interface Migration {
     /**
-     * This method return the minimal version before applying this migration
-     * TODO: not used for now
-     * @return return the version
-     */
-    Version getFromVersion();
-
-    /**
-     * This method return the target version after migration
-     * @return the target version
-     */
-    Version getToVersion();
-
-    /**
-     * This method returns a short description of the changes performed during this migration
-     * @return the migration description
-     */
-    String getDescription();
-
-    /**
      * This method is called to execute the migration
-     * @param session       the shell's session
-     * @param httpClient    CloseableHttpClient
-     * @param esAddress     the fully qualified address at which the ElasticSearch is reachable (ie http://localhost:9200)
-     * @param bundleContext the bundle context object
+     * @param session               the shell's session
+     * @param httpClient            CloseableHttpClient
+     * @param migrationConfig       config used to perform the migration, like esAddress, trustAllCertificates, etc ...
+     * @param bundleContext         the bundle context object
      * @throws IOException if there was an error while executing the migration
+     * @deprecated do groovy script for implementing new migrations
      */
-    void execute(Session session, CloseableHttpClient httpClient, String esAddress, BundleContext bundleContext) throws IOException;
+    void execute(Session session, CloseableHttpClient httpClient, Map<String, Object> migrationConfig, BundleContext bundleContext) throws IOException;
 }
diff --git a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/MigrationScript.java b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/MigrationScript.java
new file mode 100644
index 0000000..d2dc304
--- /dev/null
+++ b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/MigrationScript.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.unomi.shell.migration;
+
+import groovy.lang.Script;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Version;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Java bean representing a migration script, current implementation support groovy script as migration script
+ * following file name pattern need to be respected:
+ * migrate-VERSION-PRIORITY-NAME.groovy
+ *
+ * example:
+ * migrate-2.0.0-01-segmentReindex.groovy
+ */
+public class MigrationScript implements Comparable<MigrationScript> {
+
+    private static final Pattern SCRIPT_FILENAME_PATTERN = Pattern.compile("^migrate-(\\d.\\d.\\d)-(\\d+)-(\\w+).groovy$");
+
+    private final String script;
+    private Script compiledScript;
+    private final Bundle bundle;
+    private final Version version;
+    private final int priority;
+    private final String name;
+
+    public MigrationScript(URL scriptURL, Bundle bundle) throws IOException {
+        this.bundle = bundle;
+        this.script = IOUtils.toString(scriptURL);
+
+        String path = scriptURL.getPath();
+        String fileName = StringUtils.substringAfterLast(path, "/");
+        Matcher m = SCRIPT_FILENAME_PATTERN.matcher(fileName);
+        if (m.find()) {
+            this.version = new Version(m.group(1));
+            this.priority = Integer.parseInt(m.group(2));
+            this.name = m.group(3);
+        } else {
+            throw new IllegalStateException("Migration script file name do not respect the expected format: " + fileName +
+                    ". Expected format is: migrate-VERSION-PRIORITY-NAME.groovy. Example: migrate-2.0.0-01-segmentReindex.groovy");
+        }
+    }
+
+    public Script getCompiledScript() {
+        return compiledScript;
+    }
+
+    public void setCompiledScript(Script compiledScript) {
+        this.compiledScript = compiledScript;
+    }
+
+    public String getScript() {
+        return script;
+    }
+
+    public Bundle getBundle() {
+        return bundle;
+    }
+
+    public Version getVersion() {
+        return version;
+    }
+
+    public int getPriority() {
+        return priority;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                "version=" + version +
+                ", name='" + name + '\'' +
+                (bundle != null ? ", bundle=" + bundle.getSymbolicName() : "") +
+                '}';
+    }
+
+    @Override
+    public int compareTo(MigrationScript other) {
+        int result = version.compareTo(other.getVersion());
+        if (result != 0) {
+            return result;
+        }
+
+        result = priority - other.getPriority();
+        if (result != 0) {
+            return result;
+        }
+
+        return name.compareTo(other.getName());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (o == null || getClass() != o.getClass()) return false;
+
+        MigrationScript that = (MigrationScript) o;
+
+        return new EqualsBuilder().append(priority, that.priority).append(version, that.version).append(name, that.name).isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder(17, 37).append(version).append(priority).append(name).toHashCode();
+    }
+}
diff --git a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/actions/Migrate.java b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/actions/Migrate.java
index 6e73cbd..d325cee 100644
--- a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/actions/Migrate.java
+++ b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/actions/Migrate.java
@@ -16,6 +16,9 @@
  */
 package org.apache.unomi.shell.migration.actions;
 
+import groovy.lang.GroovyClassLoader;
+import groovy.lang.GroovyShell;
+import groovy.util.GroovyScriptEngine;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
@@ -23,19 +26,26 @@
 import org.apache.karaf.shell.api.action.lifecycle.Reference;
 import org.apache.karaf.shell.api.action.lifecycle.Service;
 import org.apache.karaf.shell.api.console.Session;
-import org.apache.unomi.shell.migration.Migration;
+import org.apache.unomi.shell.migration.MigrationScript;
 import org.apache.unomi.shell.migration.utils.ConsoleUtils;
 import org.apache.unomi.shell.migration.utils.HttpUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.Version;
+import org.osgi.framework.*;
+import org.osgi.framework.wiring.BundleWiring;
 
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
-@Command(scope = "unomi", name = "migrate", description = "This will Migrate your date in ES to be compliant with current version")
+@Command(scope = "unomi", name = "migrate", description = "This will Migrate your data in ES to be compliant with current version")
 @Service
 public class Migrate implements Action {
+    public static final String CONFIG_ES_ADDRESS = "esAddress";
+    public static final String CONFIG_TRUST_ALL_CERTIFICATES = "httpClient.trustAllCertificates";
 
     @Reference
     Session session;
@@ -43,93 +53,146 @@
     @Reference
     BundleContext bundleContext;
 
-    @Argument(name = "fromVersionWithoutSuffix", description = "Origin version without suffix/qualifier (e.g: 1.2.0)", multiValued = false, valueToShowInHelp = "1.2.0")
-    private String fromVersionWithoutSuffix;
+    @Argument(index = 0, name = "originVersion", description = "Origin version without suffix/qualifier (e.g: 1.2.0)", valueToShowInHelp = "1.2.0")
+    private String originVersion;
 
     public Object execute() throws Exception {
-        if (fromVersionWithoutSuffix == null) {
-            listMigrations();
+        // Load migration scrips
+        Set<MigrationScript> scripts = loadOSGIScripts();
+        scripts.addAll(loadFileSystemScripts());
+
+        if (originVersion == null) {
+            displayMigrations(scripts);
+            ConsoleUtils.printMessage(session, "Select your migration starting point by specifying the current version (e.g. 1.2.0) or the last script that was already run (e.g. 1.2.1)");
             return null;
         }
 
-        String confirmation = ConsoleUtils.askUserWithAuthorizedAnswer(session,"[WARNING] You are about to execute a migration, this a very sensitive operation, are you sure? (yes/no): ", Arrays.asList("yes", "no"));
-        if (confirmation.equalsIgnoreCase("no")) {
-            System.out.println("Migration process aborted");
+        // Check that there is some migration scripts available from given version
+        Version fromVersion = new Version(originVersion);
+        scripts = filterScriptsFromVersion(scripts, fromVersion);
+        if (scripts.size() == 0) {
+            ConsoleUtils.printMessage(session, "No migration scripts available found starting from version: " + originVersion);
+            return null;
+        } else {
+            ConsoleUtils.printMessage(session, "The following migration scripts starting from version: " + originVersion + " will be executed.");
+            displayMigrations(scripts);
+        }
+
+        // Check for user approval before migrate
+        if (ConsoleUtils.askUserWithAuthorizedAnswer(session,
+                "[WARNING] You are about to execute a migration, this a very sensitive operation, are you sure? (yes/no): ",
+                Arrays.asList("yes", "no")).equalsIgnoreCase("no")) {
+            ConsoleUtils.printMessage(session, "Migration process aborted");
             return null;
         }
 
-        System.out.println("Starting migration process from version: " + fromVersionWithoutSuffix);
+        // Build conf
+        Map<String, Object> migrationConfig = new HashMap<>();
+        migrationConfig.put(CONFIG_ES_ADDRESS, ConsoleUtils.askUserWithDefaultAnswer(session, "Enter ElasticSearch 7 TARGET address (default = http://localhost:9200): ", "http://localhost:9200"));
+        migrationConfig.put(CONFIG_TRUST_ALL_CERTIFICATES, ConsoleUtils.askUserWithAuthorizedAnswer(session,"We need to initialize a HttpClient, do we need to trust all certificates? (yes/no): ", Arrays.asList("yes", "no")).equalsIgnoreCase("yes"));
 
-        Version fromVersion = new Version(fromVersionWithoutSuffix);
-        Version currentVersion = getCurrentVersionWithoutQualifier();
-        System.out.println("current version: " + currentVersion.toString());
-        if (currentVersion.compareTo(fromVersion) <= 0) {
-            System.out.println("From version is same or superior than current version, nothing to migrate.");
-            return null;
-        }
+        try (CloseableHttpClient httpClient = HttpUtils.initHttpClient((Boolean) migrationConfig.get(CONFIG_TRUST_ALL_CERTIFICATES))) {
 
-        CloseableHttpClient httpClient = null;
-        try {
-            httpClient = HttpUtils.initHttpClient(session);
+            // Compile scripts
+            scripts = parseScripts(scripts, session, httpClient, migrationConfig);
 
-            String esAddress = ConsoleUtils.askUserWithDefaultAnswer(session, "Enter ElasticSearch 7 TARGET address (default = http://localhost:9200): ", "http://localhost:9200");
-
-            for (Migration migration : getMigrations()) {
-                if (fromVersion.compareTo(migration.getToVersion()) < 0) {
-                    String migrateConfirmation = ConsoleUtils.askUserWithAuthorizedAnswer(session,"Starting migration to version " + migration.getToVersion() + ", do you want to proceed? (yes/no): ", Arrays.asList("yes", "no"));
-                    if (migrateConfirmation.equalsIgnoreCase("no")) {
-                        System.out.println("Migration process aborted");
-                        break;
-                    }
-                    migration.execute(session, httpClient, esAddress, bundleContext);
-                    System.out.println("Migration to version " + migration.getToVersion() + " done successfully");
+            // Start migration
+            ConsoleUtils.printMessage(session, "Starting migration process from version: " + originVersion);
+            for (MigrationScript migrateScript : scripts) {
+                ConsoleUtils.printMessage(session, "Starting execution of: " + migrateScript);
+                try {
+                    migrateScript.getCompiledScript().run();
+                } catch (Exception e) {
+                    ConsoleUtils.printException(session, "Error executing: " + migrateScript, e);
+                    return null;
                 }
-            }
-        } finally {
-            if (httpClient != null) {
-                httpClient.close();
+
+                ConsoleUtils.printMessage(session, "Finnish execution of: " + migrateScript);
             }
         }
 
         return null;
     }
 
-    private Version getCurrentVersionWithoutQualifier() {
-        Version currentVersion = bundleContext.getBundle().getVersion();
-        return new Version(currentVersion.getMajor() + "." + currentVersion.getMinor() + "." + currentVersion.getMicro());
-    }
-
-    private void listMigrations() {
+    private void displayMigrations(Set<MigrationScript> scripts) {
         Version previousVersion = new Version("0.0.0");
-        for (Migration migration : getMigrations()) {
-            if (migration.getToVersion().getMajor() > previousVersion.getMajor() || migration.getToVersion().getMinor() > previousVersion.getMinor()) {
-                System.out.println("From " + migration.getToVersion().getMajor() + "." + migration.getToVersion().getMinor() + ".0:");
+        for (MigrationScript migration : scripts) {
+            if (migration.getVersion().getMajor() > previousVersion.getMajor() || migration.getVersion().getMinor() > previousVersion.getMinor()) {
+                ConsoleUtils.printMessage(session, "From " + migration.getVersion().getMajor() + "." + migration.getVersion().getMinor() + ".0:");
             }
-            System.out.println("- " + migration.getToVersion() + " " + migration.getDescription());
-            previousVersion = migration.getToVersion();
+            ConsoleUtils.printMessage(session, "- " + migration);
+            previousVersion = migration.getVersion();
         }
-        System.out.println("Select your migration starting point by specifying the current version (e.g. 1.2.0) or the last script that was already run (e.g. 1.2.1)");
-
     }
 
-    private List<Migration> getMigrations() {
-        Collection<ServiceReference<Migration>> migrationServiceReferences = null;
-        try {
-            migrationServiceReferences = bundleContext.getServiceReferences(Migration.class, null);
-        } catch (InvalidSyntaxException e) {
-            e.printStackTrace();
-        }
-        SortedSet<Migration> migrations = new TreeSet<>(new Comparator<Migration>() {
-            @Override
-            public int compare(Migration o1, Migration o2) {
-                return o1.getToVersion().compareTo(o2.getToVersion());
-            }
-        });
-        for (ServiceReference<Migration> migrationServiceReference : migrationServiceReferences) {
-            Migration migration = bundleContext.getService(migrationServiceReference);
-            migrations.add(migration);
-        }
-        return new ArrayList<>(migrations);
+    private Set<MigrationScript> filterScriptsFromVersion(Set<MigrationScript> scripts, Version fromVersion) {
+        return scripts.stream()
+                .filter(migrateScript -> fromVersion.compareTo(migrateScript.getVersion()) < 0)
+                .collect(Collectors.toCollection(TreeSet::new));
     }
 
+    private Set<MigrationScript> parseScripts(Set<MigrationScript> scripts, Session session, CloseableHttpClient httpClient, Map<String, Object> migrationConfig) {
+        Map<String, GroovyShell> shellsPerBundle = new HashMap<>();
+
+        return scripts.stream()
+                .peek(migrateScript -> {
+                    // fallback on current bundle if the scripts is not provided by OSGI
+                    Bundle scriptBundle = migrateScript.getBundle() != null ? migrateScript.getBundle() : bundleContext.getBundle();
+                    if (!shellsPerBundle.containsKey(scriptBundle.getSymbolicName())) {
+                        shellsPerBundle.put(scriptBundle.getSymbolicName(), buildShellForBundle(scriptBundle, session, httpClient, migrationConfig));
+                    }
+                    migrateScript.setCompiledScript(shellsPerBundle.get(scriptBundle.getSymbolicName()).parse(migrateScript.getScript()));
+                })
+                .collect(Collectors.toCollection(TreeSet::new));
+    }
+
+    private Set<MigrationScript> loadOSGIScripts() throws IOException {
+        SortedSet<MigrationScript> migrationScripts = new TreeSet<>();
+        for (Bundle bundle : bundleContext.getBundles()) {
+            Enumeration<URL> scripts = bundle.findEntries("META-INF/cxs/migration", "*.groovy", true);
+            if (scripts != null) {
+                // check for shell
+
+                while (scripts.hasMoreElements()) {
+                    URL scriptURL = scripts.nextElement();
+                    migrationScripts.add(new MigrationScript(scriptURL, bundle));
+                }
+            }
+        }
+
+        return migrationScripts;
+    }
+
+    private Set<MigrationScript> loadFileSystemScripts() throws IOException {
+        // check migration folder exists
+        Path migrationFolder = Paths.get(System.getProperty( "karaf.data" ), "migration", "scripts");
+        if (!Files.isDirectory(migrationFolder)) {
+            return Collections.emptySet();
+        }
+
+        List<Path> paths;
+        try (Stream<Path> walk = Files.walk(migrationFolder)) {
+            paths = walk
+                    .filter(path -> !Files.isDirectory(path))
+                    .filter(path -> path.toString().toLowerCase().endsWith("groovy"))
+                    .collect(Collectors.toList());
+        }
+
+        SortedSet<MigrationScript> migrationScripts = new TreeSet<>();
+        for (Path path : paths) {
+            migrationScripts.add(new MigrationScript(path.toUri().toURL(), null));
+        }
+        return migrationScripts;
+    }
+
+    private GroovyShell buildShellForBundle(Bundle bundle, Session session, CloseableHttpClient httpClient, Map<String, Object> migrationConfig) {
+        GroovyClassLoader groovyLoader = new GroovyClassLoader(bundle.adapt(BundleWiring.class).getClassLoader());
+        GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine((URL[]) null, groovyLoader);
+        GroovyShell groovyShell = new GroovyShell(groovyScriptEngine.getGroovyClassLoader());
+        groovyShell.setVariable("session", session);
+        groovyShell.setVariable("httpClient", httpClient);
+        groovyShell.setVariable("migrationConfig", migrationConfig);
+        groovyShell.setVariable("bundleContext", bundle.getBundleContext());
+        return groovyShell;
+    }
 }
diff --git a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo121.java b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo121.java
index dcc611e..0618329 100644
--- a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo121.java
+++ b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo121.java
@@ -25,8 +25,6 @@
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.Version;
-import org.osgi.service.component.annotations.Component;
 
 import java.io.IOException;
 import java.util.*;
@@ -34,7 +32,6 @@
 /**
  * @author dgaillard
  */
-@Component
 public class MigrationTo121 implements Migration {
 
     private CloseableHttpClient httpClient;
@@ -44,25 +41,10 @@
     private List propsTaggedAsPersonalIdentifier = Arrays.asList("firstName", "lastName", "email", "phoneNumber", "address", "facebookId", "googleId", "linkedInId", "twitterId");
 
     @Override
-    public Version getFromVersion() {
-        return null;
-    }
-
-    @Override
-    public Version getToVersion() {
-        return new Version("1.2.1");
-    }
-
-    @Override
-    public String getDescription() {
-        return "Migrate tags";
-    }
-
-    @Override
-    public void execute(Session session, CloseableHttpClient httpClient, String esAddress, BundleContext bundleContext) throws IOException {
+    public void execute(Session session, CloseableHttpClient httpClient, Map<String, Object> migrationConfig, BundleContext bundleContext) throws IOException {
         this.httpClient = httpClient;
         this.session = session;
-        this.esAddress = esAddress;
+        this.esAddress = (String) migrationConfig.get("esAddress");
         migrateTags();
     }
 
diff --git a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo122.java b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo122.java
index 7970a2a..b371f8a 100644
--- a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo122.java
+++ b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo122.java
@@ -23,37 +23,21 @@
 import org.apache.unomi.shell.migration.utils.HttpRequestException;
 import org.apache.unomi.shell.migration.utils.HttpUtils;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.Version;
 import org.osgi.service.component.annotations.Component;
 
 import java.io.IOException;
+import java.util.Map;
 
-@Component
 public class MigrationTo122 implements Migration {
     private CloseableHttpClient httpClient;
     private Session session;
     private String esAddress;
 
     @Override
-    public Version getFromVersion() {
-        return null;
-    }
-
-    @Override
-    public Version getToVersion() {
-        return new Version("1.2.2");
-    }
-
-    @Override
-    public String getDescription() {
-        return "Delete old index template";
-    }
-
-    @Override
-    public void execute(Session session, CloseableHttpClient httpClient, String esAddress, BundleContext bundleContext) throws IOException {
+    public void execute(Session session, CloseableHttpClient httpClient, Map<String, Object> migrationConfig, BundleContext bundleContext) throws IOException {
         this.httpClient = httpClient;
         this.session = session;
-        this.esAddress = esAddress;
+        this.esAddress = (String) migrationConfig.get("esAddress");
         deleteOldIndexTemplate();
 
     }
diff --git a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo150.java b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo150.java
index 54da2f5..f0aee21 100644
--- a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo150.java
+++ b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo150.java
@@ -25,38 +25,20 @@
 import org.json.JSONObject;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.Version;
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.URL;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.TreeSet;
+import java.util.*;
 
 public class MigrationTo150 implements Migration {
 
     public static final String INDEX_DATE_PREFIX = "date-";
 
     @Override
-    public Version getFromVersion() {
-        return new Version("1.3.0");
-    }
-
-    @Override
-    public Version getToVersion() {
-        return new Version("1.5.0");
-    }
-
-    @Override
-    public String getDescription() {
-        return "Migrate the data from ElasticSearch 5.6 to 7.4";
-    }
-
-    @Override
-    public void execute(Session session, CloseableHttpClient httpClient, String esAddress, BundleContext bundleContext) throws IOException {
+    public void execute(Session session, CloseableHttpClient httpClient, Map<String, Object> migrationConfig, BundleContext bundleContext) throws IOException {
+        String esAddress = (String) migrationConfig.get("esAddress");
         String es5Address = ConsoleUtils.askUserWithDefaultAnswer(session, "SOURCE Elasticsearch 5.6 cluster address (default: http://localhost:9210) : ", "http://localhost:9210");
         String sourceIndexPrefix = ConsoleUtils.askUserWithDefaultAnswer(session, "SOURCE index name (default: context) : ", "context");
         String destIndexPrefix = ConsoleUtils.askUserWithDefaultAnswer(session, "TARGET index prefix (default: context) : ", "context");
diff --git a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo200.java b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo200.java
index 410fbd1..a859bfb 100644
--- a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo200.java
+++ b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/impl/MigrationTo200.java
@@ -16,7 +16,6 @@
  */
 package org.apache.unomi.shell.migration.impl;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpStatus;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
@@ -29,25 +28,21 @@
 import org.apache.karaf.shell.api.console.Session;
 import org.apache.unomi.shell.migration.Migration;
 import org.apache.unomi.shell.migration.utils.ConsoleUtils;
+import org.apache.unomi.shell.migration.utils.MigrationUtils;
 import org.json.JSONArray;
 import org.json.JSONObject;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.Version;
-import org.osgi.service.component.annotations.Component;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.net.URI;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
-@Component
 public class MigrationTo200 implements Migration {
 
     private CloseableHttpClient httpClient;
@@ -56,28 +51,10 @@
     private BundleContext bundleContext;
 
     @Override
-    public Version getFromVersion() {
-        return new Version("1.5.0");
-    }
-
-    @Override
-    public Version getToVersion() {
-        return new Version("2.0.0");
-    }
-
-    @Override
-    public String getDescription() {
-        return "Updates mapping for an index \"event\" with prefix \"context\" by default. Adds the \"sourceId\" field and copies value "
-                + "from the \"scope\" field to it."
-                + "Creates the scope entries in the index \"scope\" from the existing scopes of the events. "
-                + "Creates the \"profileAlias\" documents based on \"profile\".";
-    }
-
-    @Override
-    public void execute(Session session, CloseableHttpClient httpClient, String esAddress, BundleContext bundleContext) throws IOException {
+    public void execute(Session session, CloseableHttpClient httpClient, Map<String, Object> migrationConfig, BundleContext bundleContext) throws IOException {
         this.httpClient = httpClient;
         this.session = session;
-        this.esAddress = esAddress;
+        this.esAddress = (String) migrationConfig.get("esAddress");
         this.bundleContext = bundleContext;
 
         doExecute();
@@ -100,7 +77,7 @@
         httpPut.addHeader("Accept", "application/json");
         httpPut.addHeader("Content-Type", "application/json");
 
-        String requestBody = resourceAsString("requestBody/updateMapping.json");
+        String requestBody = MigrationUtils.resourceAsString(bundleContext,"requestBody/updateMapping.json");
 
         httpPut.setEntity(new StringEntity(requestBody));
 
@@ -124,7 +101,7 @@
         httpPost.addHeader("Accept", "application/json");
         httpPost.addHeader("Content-Type", "application/json");
 
-        String requestBody = resourceAsString("requestBody/copyValueScopeToSourceId.json");
+        String requestBody = MigrationUtils.resourceAsString(bundleContext,"requestBody/copyValueScopeToSourceId.json");
 
         httpPost.setEntity(new StringEntity(requestBody));
 
@@ -181,7 +158,7 @@
             httpPost.addHeader("Accept", "application/json");
             httpPost.addHeader("Content-Type", "application/json");
 
-            String request = resourceAsString("requestBody/scopeMapping.json").replace("$numberOfShards", numberOfShards)
+            String request = MigrationUtils.resourceAsString(bundleContext,"requestBody/scopeMapping.json").replace("$numberOfShards", numberOfShards)
                     .replace("$numberOfReplicas", numberOfReplicas).replace("$mappingTotalFieldsLimit", mappingTotalFieldsLimit)
                     .replace("$maxDocValueFieldsSearch", maxDocValueFieldsSearch);
 
@@ -204,7 +181,7 @@
 
     private void createScopes(Set<String> scopes, String indexPrefix) throws IOException {
         final StringBuilder body = new StringBuilder();
-        String saveScopeBody = resourceAsString("requestBody/bulkSaveScope.ndjson");
+        String saveScopeBody = MigrationUtils.resourceAsString(bundleContext,"requestBody/bulkSaveScope.ndjson");
         scopes.forEach(scope -> body.append(saveScopeBody.replace("$scope", scope)));
 
         final HttpPost httpPost = new HttpPost(esAddress + "/" + indexPrefix + "-scope/_bulk");
@@ -231,7 +208,7 @@
         httpPost.addHeader("Accept", "application/json");
         httpPost.addHeader("Content-Type", "application/json");
 
-        String request = resourceAsString("requestBody/searchScope.json");
+        String request = MigrationUtils.resourceAsString(bundleContext,"requestBody/searchScope.json");
 
         httpPost.setEntity(new StringEntity(request));
 
@@ -287,7 +264,7 @@
                         JSONObject profile = hit.getJSONObject("_source");
                         if (profile.has("itemId")) {
                             String itemId = profile.getString("itemId");
-                            String bulkSaveProfileAliases = resourceAsString("requestBody/bulkSaveProfileAliases.ndjson");
+                            String bulkSaveProfileAliases = MigrationUtils.resourceAsString(bundleContext,"requestBody/bulkSaveProfileAliases.ndjson");
                             bulkCreateRequest.append(bulkSaveProfileAliases.
                                     replace("$itemId", itemId).
                                     replace("$migrationTime", migrationTime.toString()));
@@ -370,13 +347,4 @@
 
         return bulkRequest;
     }
-
-    protected String resourceAsString(final String resource) {
-        final URL url = bundleContext.getBundle().getResource(resource);
-        try (InputStream stream = url.openStream()) {
-            return IOUtils.toString(stream, StandardCharsets.UTF_8);
-        } catch (final Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
 }
diff --git a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/utils/HttpUtils.java b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/utils/HttpUtils.java
index dd64725..b5fa7b9 100644
--- a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/utils/HttpUtils.java
+++ b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/utils/HttpUtils.java
@@ -52,10 +52,7 @@
 public class HttpUtils {
     private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);
 
-    public static CloseableHttpClient initHttpClient(Session session) throws IOException {
-        String confirmation = ConsoleUtils.askUserWithAuthorizedAnswer(session,"We need to initialize a HttpClient, do we need to trust all certificates? (yes/no): ", Arrays.asList("yes", "no"));
-        boolean trustAllCertificates = confirmation.equalsIgnoreCase("yes");
-
+    public static CloseableHttpClient initHttpClient(boolean trustAllCertificates) throws IOException {
         long requestStartTime = System.currentTimeMillis();
 
         HttpClientBuilder httpClientBuilder = HttpClients.custom().useSystemProperties();
diff --git a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/utils/MigrationUtils.java b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/utils/MigrationUtils.java
index ccc67f2..22e1d37 100644
--- a/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/utils/MigrationUtils.java
+++ b/tools/shell-commands/src/main/java/org/apache/unomi/shell/migration/utils/MigrationUtils.java
@@ -16,12 +16,17 @@
  */
 package org.apache.unomi.shell.migration.utils;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.json.JSONObject;
+import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
 
 /**
  * @author dgaillard
@@ -44,4 +49,43 @@
     public static void bulkUpdate(CloseableHttpClient httpClient, String url, String jsonData) throws IOException {
         HttpUtils.executePostRequest(httpClient, url, jsonData, null);
     }
+
+    public static String resourceAsString(BundleContext bundleContext, final String resource) {
+        final URL url = bundleContext.getBundle().getResource(resource);
+        try (InputStream stream = url.openStream()) {
+            return IOUtils.toString(stream, StandardCharsets.UTF_8);
+        } catch (final Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void reIndex(CloseableHttpClient httpClient, BundleContext bundleContext, String esAddress, String indexName, String newIndexSettings) throws IOException {
+        String indexNameCloned = indexName + "-cloned";
+
+        // Init requests
+        JSONObject originalIndexSettings = new JSONObject(HttpUtils.executeGetRequest(httpClient, esAddress + "/" + indexName + "/_settings", null));
+        // TODO UNOMI-606 validate following lines: (normally those properties are automatically added to unomi indices so they should always be present on the existing indices)
+        String newIndexRequest = newIndexSettings
+                .replace("#numberOfShards", originalIndexSettings.getJSONObject(indexName).getJSONObject("settings").getJSONObject("index").getString("number_of_shards"))
+                .replace("#numberOfReplicas", originalIndexSettings.getJSONObject(indexName).getJSONObject("settings").getJSONObject("index").getString("number_of_replicas"))
+                .replace("#maxDocValueFieldsSearch", originalIndexSettings.getJSONObject(indexName).getJSONObject("settings").getJSONObject("index").getString("max_docvalue_fields_search"))
+                .replace("#mappingTotalFieldsLimit", originalIndexSettings.getJSONObject(indexName).getJSONObject("settings").getJSONObject("index").getJSONObject("mapping").getJSONObject("total_fields").getString("limit"));
+        String reIndexRequest = resourceAsString(bundleContext, "requestBody/2.0.0/base_reindex_request.json")
+                .replace("#source", indexNameCloned)
+                .replace("#dest", indexName);
+        String setIndexReadOnlyRequest = resourceAsString(bundleContext, "requestBody/2.0.0/base_set_index_readonly_request.json");
+
+        // Set original index as readOnly
+        HttpUtils.executePutRequest(httpClient, esAddress + "/" + indexName + "/_settings", setIndexReadOnlyRequest, null);
+        // Clone the original index for backup
+        HttpUtils.executePostRequest(httpClient, esAddress + "/" + indexName + "/_clone/" + indexNameCloned, null, null);
+        // Delete original index
+        HttpUtils.executeDeleteRequest(httpClient, esAddress + "/" + indexName, null);
+        // Recreate the original index with new mappings
+        HttpUtils.executePutRequest(httpClient, esAddress + "/" + indexName, newIndexRequest, null);
+        // Reindex data from clone
+        HttpUtils.executePostRequest(httpClient, esAddress + "/_reindex", reIndexRequest, null);
+        // Remove clone
+        HttpUtils.executeDeleteRequest(httpClient, esAddress + "/" + indexNameCloned, null);
+    }
 }
diff --git a/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-1.2.1-00-migrateTags.groovy b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-1.2.1-00-migrateTags.groovy
new file mode 100644
index 0000000..152f432
--- /dev/null
+++ b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-1.2.1-00-migrateTags.groovy
@@ -0,0 +1,20 @@
+import org.apache.unomi.shell.migration.impl.MigrationTo121
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+new MigrationTo121().execute(session, httpClient, migrationConfig, bundleContext)
\ No newline at end of file
diff --git a/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-1.2.2-00-deleteOldIndexTemplate.groovy b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-1.2.2-00-deleteOldIndexTemplate.groovy
new file mode 100644
index 0000000..99fbd8f
--- /dev/null
+++ b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-1.2.2-00-deleteOldIndexTemplate.groovy
@@ -0,0 +1,20 @@
+import org.apache.unomi.shell.migration.impl.MigrationTo122
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+new MigrationTo122().execute(session, httpClient, migrationConfig, bundleContext)
\ No newline at end of file
diff --git a/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-1.5.0-00-elasticSearch7.4.groovy b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-1.5.0-00-elasticSearch7.4.groovy
new file mode 100644
index 0000000..4fbf014
--- /dev/null
+++ b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-1.5.0-00-elasticSearch7.4.groovy
@@ -0,0 +1,20 @@
+import org.apache.unomi.shell.migration.impl.MigrationTo150
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+new MigrationTo150().execute(session, httpClient, migrationConfig, bundleContext)
\ No newline at end of file
diff --git a/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-2.0.0-00-scopesAndProfileAliases.groovy b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-2.0.0-00-scopesAndProfileAliases.groovy
new file mode 100644
index 0000000..80d66d9
--- /dev/null
+++ b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-2.0.0-00-scopesAndProfileAliases.groovy
@@ -0,0 +1,20 @@
+import org.apache.unomi.shell.migration.impl.MigrationTo200
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+new MigrationTo200().execute(session, httpClient, migrationConfig, bundleContext)
\ No newline at end of file
diff --git a/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-2.0.0-01-segmentReindex.groovy b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-2.0.0-01-segmentReindex.groovy
new file mode 100644
index 0000000..1ffe88a
--- /dev/null
+++ b/tools/shell-commands/src/main/resources/META-INF/cxs/migration/migrate-2.0.0-01-segmentReindex.groovy
@@ -0,0 +1,21 @@
+import org.apache.unomi.shell.migration.utils.MigrationUtils
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+String newIndexSettings = MigrationUtils.resourceAsString(bundleContext, "requestBody/2.0.0/segment_index.json");
+MigrationUtils.reIndex(httpClient, bundleContext, migrationConfig.get("esAddress"), "context-segment", newIndexSettings)
\ No newline at end of file
diff --git a/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 25e44f7..61f70c3 100644
--- a/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/tools/shell-commands/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -20,13 +20,6 @@
            xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
            xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
 
-    <bean id="migrateTo121" class="org.apache.unomi.shell.migration.impl.MigrationTo121"/>
-    <bean id="migrateTo122" class="org.apache.unomi.shell.migration.impl.MigrationTo122"/>
-    <bean id="migrateTo150" class="org.apache.unomi.shell.migration.impl.MigrationTo150"/>
-    <service ref="migrateTo121" interface="org.apache.unomi.shell.migration.Migration"/>
-    <service ref="migrateTo122" interface="org.apache.unomi.shell.migration.Migration"/>
-    <service ref="migrateTo150" interface="org.apache.unomi.shell.migration.Migration"/>
-
     <bean id="unomiManagementServiceImpl" class="org.apache.unomi.shell.services.internal.UnomiManagementServiceImpl" init-method="init">
         <property name="bundleSymbolicNames">
             <list>
@@ -66,5 +59,4 @@
     </bean>
     <service id="unomiManagementService" ref="unomiManagementServiceImpl"
              interface="org.apache.unomi.shell.services.UnomiManagementService"/>
-
 </blueprint>
diff --git a/tools/shell-commands/src/main/resources/requestBody/2.0.0/base_reindex_request.json b/tools/shell-commands/src/main/resources/requestBody/2.0.0/base_reindex_request.json
new file mode 100644
index 0000000..bf74061
--- /dev/null
+++ b/tools/shell-commands/src/main/resources/requestBody/2.0.0/base_reindex_request.json
@@ -0,0 +1,8 @@
+{
+  "source": {
+    "index": "#source"
+  },
+  "dest": {
+    "index": "#dest"
+  }
+}
\ No newline at end of file
diff --git a/tools/shell-commands/src/main/resources/requestBody/2.0.0/base_set_index_readonly_request.json b/tools/shell-commands/src/main/resources/requestBody/2.0.0/base_set_index_readonly_request.json
new file mode 100644
index 0000000..f18167f
--- /dev/null
+++ b/tools/shell-commands/src/main/resources/requestBody/2.0.0/base_set_index_readonly_request.json
@@ -0,0 +1,5 @@
+{
+  "settings": {
+    "index.blocks.write": true
+  }
+}
\ No newline at end of file
diff --git a/tools/shell-commands/src/main/resources/requestBody/2.0.0/segment_index.json b/tools/shell-commands/src/main/resources/requestBody/2.0.0/segment_index.json
new file mode 100644
index 0000000..ec8a3c3
--- /dev/null
+++ b/tools/shell-commands/src/main/resources/requestBody/2.0.0/segment_index.json
@@ -0,0 +1,64 @@
+{
+  "settings": {
+    "index": {
+      "number_of_shards": #numberOfShards,
+      "number_of_replicas": #numberOfReplicas,
+      "mapping.total_fields.limit": #mappingTotalFieldsLimit,
+      "max_docvalue_fields_search": #maxDocValueFieldsSearch
+    },
+    "analysis": {
+      "analyzer": {
+        "folding": {
+          "type": "custom",
+          "tokenizer": "keyword",
+          "filter": [
+            "lowercase",
+            "asciifolding"
+          ]
+        }
+      }
+    }
+  },
+  "mappings": {
+    "dynamic_templates": [
+      {
+        "all": {
+          "match": "*",
+          "match_mapping_type": "string",
+          "mapping": {
+            "type": "text",
+            "analyzer": "folding",
+            "fields": {
+              "keyword": {
+                "type": "keyword",
+                "ignore_above": 256
+              }
+            }
+          }
+        }
+      }
+    ],
+    "properties": {
+      "metadata": {
+        "properties": {
+          "enabled": {
+            "type": "boolean"
+          },
+          "hidden": {
+            "type": "boolean"
+          },
+          "missingPlugins": {
+            "type": "boolean"
+          },
+          "readOnly": {
+            "type": "boolean"
+          }
+        }
+      },
+      "condition": {
+        "type": "object",
+        "enabled": false
+      }
+    }
+  }
+}
\ No newline at end of file