From 0c1628f15d5a91065a82e58d78518f7d78182a76 Mon Sep 17 00:00:00 2001
From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr>
Date: Tue, 11 Jun 2024 14:54:47 +0200
Subject: [PATCH 1/8] correction #225

---
 .../builder/CheckerDescriptionBuilder.java    |  3 ++
 .../application/configuration/LtreeTest.java  |  4 +-
 .../inra/oresing/rest/OreSiResourcesTest.java | 50 +++++++++++++------
 .../configuration/data.result.example.json    | 21 ++++++--
 ui/src/services/TagService.js                 |  5 +-
 5 files changed, 63 insertions(+), 20 deletions(-)

diff --git a/src/main/java/fr/inra/oresing/rest/model/configuration/builder/CheckerDescriptionBuilder.java b/src/main/java/fr/inra/oresing/rest/model/configuration/builder/CheckerDescriptionBuilder.java
index 9ee24c252..c011ab33a 100644
--- a/src/main/java/fr/inra/oresing/rest/model/configuration/builder/CheckerDescriptionBuilder.java
+++ b/src/main/java/fr/inra/oresing/rest/model/configuration/builder/CheckerDescriptionBuilder.java
@@ -22,6 +22,9 @@ public record CheckerDescriptionBuilder(RootBuilder rootBuilder) {
             final JsonNode checkerNode,
             final String dataKey) {
         if (checkerNode == null || checkerNode.isMissingNode()) {
+            if(required){
+                return new StringChecker(CheckerDescription.CheckerDescriptionType.StringChecker, Multiplicity.ONE, required, null);
+            }
             return null;
         }
         CheckerEnum name = null;
diff --git a/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java b/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java
index 738acaf97..d5282cf17 100644
--- a/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java
+++ b/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java
@@ -32,8 +32,8 @@ class LtreeTest {
         Ltree nk = Ltree.fromUnescapedString(label);
         Assert.assertEquals(label, nk.getSql());
     }*/
-    @ParameterizedTest(name = "'{0}' match an encodingString")
-    @ValueSource(strings = {"°","%",">","²","؇","?","&","@","€","µ"})
+    @ParameterizedTest(name = "{0} match an encodingString")
+    @ValueSource(strings = {"°","%",">","²","$","?","&","@","@","µ"})
     void testIsEcodedString(String aSign){
         String aChar = Ltree.fromUnescapedString(aSign).getSql();
         Assert.assertTrue(Ltree.isEncodedString(aChar));
diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
index 017b87fc0..8d239465a 100644
--- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
+++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
@@ -13,6 +13,7 @@ import fr.inra.oresing.domain.data.deposit.validation.CsvRowValidationCheckResul
 import fr.inra.oresing.domain.data.deposit.validation.ValidationCheckResult;
 import fr.inra.oresing.domain.exceptions.OreSiTechnicalException;
 import fr.inra.oresing.domain.exceptions.SiOreIllegalArgumentException;
+import fr.inra.oresing.domain.exceptions.application.NoSuchApplicationException;
 import fr.inra.oresing.domain.exceptions.authentication.authentication.NotApplicationCanDeleteRightsException;
 import fr.inra.oresing.domain.exceptions.authentication.authentication.NotApplicationCreatorRightsException;
 import fr.inra.oresing.domain.exceptions.authorization.AuthorizationRequestException;
@@ -62,6 +63,7 @@ import org.springframework.test.web.servlet.ResultMatcher;
 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.util.NestedServletException;
+import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonParseException;
 
 import java.io.*;
 import java.net.URL;
@@ -246,20 +248,41 @@ public class OreSiResourcesTest {
             final MockMultipartFile configuration = new MockMultipartFile("file", "monsore.yaml", "text/plain", in);
 
             // on n'a pas le droit de creer de nouvelle application
-            NotApplicationCreatorRightsException resolvedException =
-                    (NotApplicationCreatorRightsException) fixtures.loadApplicationWithError(
-                            configuration,
-                            monsoreCookie,
-                            "monsore"
-                    );
-            addUserRightCreateApplication(monsoreUserId, "monsore");
-            assert resolvedException != null;
-            Assertions.assertEquals("monsore", resolvedException.getApplicationName());
-            addUserRightCreateApplication(monsoreUserId, "monsore");
+            Exception loadApplicationException = fixtures.loadApplicationWithError(
+                    configuration,
+                    monsoreCookie,
+                    "monsore"
+            );
+
+            switch (loadApplicationException) {
+                case NotApplicationCreatorRightsException notApplicationCreatorRightsException -> {
+                    Assertions.assertEquals("monsore", notApplicationCreatorRightsException.getApplicationName());
+                    addUserRightCreateApplication(monsoreUserId, "monsore");
+                }
+                case NoSuchApplicationException noSuchApplicationException -> {
+                    addUserRightCreateApplication(monsoreUserId, "monsore");
+
+                }
+                case JsonParseException jsonParseException -> {
+                    log.debug("******************jsonparseexception*****************");
+                    jsonParseException.getMessage();
+                    log.debug("******************jsonparseexception*****************");
+                    try (InputStream stream = resource.openStream();
+                         BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
+                        char[] buffer = new char[100]; // Changer cette valeur selon le nombre de caractères souhaités
+                        int n = reader.read(buffer);
+                        System.out.println(new String(buffer, 0, n));
+                    log.debug("******************jsonparseexception*****************");     
+                    }
+                    addUserRightCreateApplication(monsoreUserId, "monsore");
 
+                }
+                default -> throw loadApplicationException;
+            }
             final MvcResult resultApplication = fixtures.loadApplication(configuration, monsoreCookie, "monsore", "");
             appId = fixtures.getIdFromApplicationResult(resultApplication);
         } catch (final Throwable e) {
+            e.printStackTrace();
             throw new RuntimeException(e);
         }
 
@@ -1641,7 +1664,7 @@ public class OreSiResourcesTest {
                 .andReturn().getResponse().getContentAsString();
 
         //pour le createur auth on a les fichiers de scarff
-       response =  mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json")
+        response = mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json")
                         .cookie(authCookie))
                 .andExpect(status().is2xxSuccessful())
                 .andExpect(jsonPath("$.rows[*].values[?(@.chemin=='nivelle__p1' && @.projet == 'projet_manche')].chemin", hasSize(34)))
@@ -1660,7 +1683,7 @@ public class OreSiResourcesTest {
                         "to": "1984-01-02"
                       },
                       "requiredAuthorizations": {
-                        "localization": "plateforme.scarff"
+                        "sites": "type_de_sitesKplateforme.sitesKscarff"
                       }
                     }
                   ]
@@ -1670,8 +1693,7 @@ public class OreSiResourcesTest {
                         .param("downloadDatasetQuery", filter)
                         .cookie(authCookie))
                 .andExpect(status().is2xxSuccessful())
-                .andExpect(status().is2xxSuccessful())
-                .andExpect(jsonPath("$.rows.length()", is(15)))
+                .andExpect(jsonPath("$.rows.length()", is(14)))
                 .andExpect(jsonPath("$.rows[*].values[?(@.chemin=='scarff__p1' && @.projet == 'projet_manche')]", hasSize(7)))
                 .andExpect(jsonPath("$.rows[*].values[?(@.date=='date:1984-01-01T00:00:00:dd/MM/yyyy' && @.projet == 'projet_manche')]", hasSize(7)));
 
diff --git a/src/test/resources/data/configuration/data.result.example.json b/src/test/resources/data/configuration/data.result.example.json
index a120244a0..5b82415cd 100644
--- a/src/test/resources/data/configuration/data.result.example.json
+++ b/src/test/resources/data/configuration/data.result.example.json
@@ -502,7 +502,12 @@
         "exportHeaderName" : "ptx_propriete",
         "required" : true,
         "mandatory" : "OPTIONAL",
-        "checker" : null,
+        "checker" : {
+          "type" : "StringChecker",
+          "multiplicity" : "ONE",
+          "required" : true,
+          "pattern" : null
+        },
         "submissionAuthorizationScope" : null,
         "hidden" : false,
         "referenceCheckerType" : {
@@ -725,7 +730,12 @@
         "exportHeaderName" : "tax_taxon",
         "required" : true,
         "mandatory" : "OPTIONAL",
-        "checker" : null,
+        "checker" : {
+          "type" : "StringChecker",
+          "multiplicity" : "ONE",
+          "required" : true,
+          "pattern" : null
+        },
         "submissionAuthorizationScope" : null,
         "hidden" : false,
         "referenceCheckerType" : {
@@ -980,7 +990,12 @@
         } ],
         "required" : true,
         "mandatory" : "OPTIONAL",
-        "checker" : null,
+        "checker" : {
+          "type" : "StringChecker",
+          "multiplicity" : "ONE",
+          "required" : true,
+          "pattern" : null
+        },
         "constantImportHeader" : {
           "type" : "FileConstantHeader",
           "rowNumber" : 1,
diff --git a/ui/src/services/TagService.js b/ui/src/services/TagService.js
index 95122a5db..ee2a4fc36 100644
--- a/ui/src/services/TagService.js
+++ b/ui/src/services/TagService.js
@@ -8,10 +8,13 @@ export class TagService extends Fetcher {
   toBeShown(tags, datas) {
     datas = Object.values(datas || {})
         .sort((a,b) => {
+          if(!a.order) return 1
           if (a.order && b.order) {
+            if(a.order === b.order) return 0
             if (a.order > b.order) return 1;
-            return -1;
+            return -1
           }
+          return -1;
         })
     if (!tags || !Object.keys(tags || {}).length) {
       return datas.map((data)=> {
-- 
GitLab


From 1268b803c08c8ba3b406ea85f16eb2ff65dc0d9b Mon Sep 17 00:00:00 2001
From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr>
Date: Wed, 12 Jun 2024 14:48:39 +0200
Subject: [PATCH 2/8] =?UTF-8?q?Client=20d'import=20de=20donn=C3=A9es?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 pom.xml                                       |   5 +
 .../java/fr/inra/oresing/client/Client.java   | 427 ++++++++++++++++++
 2 files changed, 432 insertions(+)
 create mode 100644 src/main/java/fr/inra/oresing/client/Client.java

diff --git a/pom.xml b/pom.xml
index 85ed3aa1b..ca04329b1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -245,6 +245,11 @@
             <groupId>org.junit.platform</groupId>
             <artifactId>junit-platform-suite-engine</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents.client5</groupId>
+            <artifactId>httpclient5</artifactId>
+            <version>5.2.1</version>
+        </dependency>
 
     </dependencies>
 
diff --git a/src/main/java/fr/inra/oresing/client/Client.java b/src/main/java/fr/inra/oresing/client/Client.java
new file mode 100644
index 000000000..d09f59f65
--- /dev/null
+++ b/src/main/java/fr/inra/oresing/client/Client.java
@@ -0,0 +1,427 @@
+package fr.inra.oresing.client;
+
+import com.fasterxml.jackson.core.exc.StreamReadException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DatabindException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.io.file.AccumulatorPathVisitor;
+import org.apache.commons.io.file.Counters;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.cookie.BasicCookieStore;
+import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.entity.mime.FileBody;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+public class Client {
+
+    public static void main(String[] args) {
+        new Client().run();
+    }
+
+    public void run() {
+        String applicationName = "foret";
+        String openAdomInstanceUrl = "http://localhost:8081";
+        String login;
+        String password;
+        boolean interactive = false;
+        Scanner scanner = new Scanner(System.in);
+        if (interactive) {
+            System.out.println("Veuillez saisir les informations de connexion à " + openAdomInstanceUrl);
+            System.out.print("identifiant : ");
+            login = scanner.nextLine();
+            System.out.print("mot de passe : ");
+            password = scanner.nextLine();
+        } else {
+            login = "poussin";
+            password = "xxxx";
+        }
+
+        CookieStore cookieStore = new BasicCookieStore();
+
+        UriFactory uriFactory = new UriFactory(openAdomInstanceUrl, applicationName);
+
+        try (CloseableHttpClient httpclient = HttpClients.custom()
+                .setDefaultCookieStore(cookieStore)
+                .build()) {
+
+            ClassicHttpRequest loginRequest = ClassicRequestBuilder.post()
+                    .setUri(uriFactory.forLogin())
+                    .addParameter("login", login)
+                    .addParameter("password", password)
+                    .build();
+
+            httpclient.execute(loginRequest, response -> {
+                switch (response.getCode()) {
+                    case HttpURLConnection.HTTP_OK -> {
+                        EntityUtils.consume(response.getEntity());
+                        switch (cookieStore.getCookies().size()) {
+                            case 0 -> fail("authentification échouée : pas de cookie d’authentification retourné");
+                            case 1 -> {
+                                if (cookieStore.getCookies().get(0).getName().equals("si-ore-jwt")) {
+                                    log("authentification OK");
+                                } else {
+                                    fail("authentification échouée : pas de cookie d’authentification retourné");
+                                }
+                            }
+                            default ->
+                                    fail(cookieStore.getCookies().size() + " cookies retournés à l’authentification");
+                        }
+                    }
+                    case HttpURLConnection.HTTP_UNAUTHORIZED ->
+                            fail("authentification échouée : identifiant ou mot de passe faux ?");
+                    default ->
+                            fail("%d en code de retour HTTP inattendu à l’authentification".formatted(response.getCode()));
+                }
+                return null;
+            });
+
+            ClassicHttpRequest getApplicationReferenceTypesRequest = ClassicRequestBuilder
+                    .get(uriFactory.forApplicationReferenceTypes(applicationName))
+                    .build();
+
+            List<String> refTypes = httpclient.execute(getApplicationReferenceTypesRequest, response -> {
+                switch (response.getCode()) {
+                    case HttpURLConnection.HTTP_OK -> {
+                        return parseJsonInResponseBody(
+                                response,
+                                new TypeReference<List<String>>() {}
+                        );
+                    }
+                    case HttpURLConnection.HTTP_UNAUTHORIZED ->
+                            fail("pas l’autorisation pour l’application " + applicationName);
+                    default ->
+                            fail("%d en code de retour HTTP inattendu".formatted(response.getCode()));
+                }
+                return null;
+            });
+
+            log("référentiels à importer :%n" + String.join(System.lineSeparator(), refTypes));
+
+            ClassicHttpRequest getApplicationDataTypesRequest = ClassicRequestBuilder
+                    .get(uriFactory.forApplicationDataTypes(applicationName))
+                    .build();
+
+            List<String> dataTypes = httpclient.execute(getApplicationDataTypesRequest, response -> {
+                switch (response.getCode()) {
+                    case HttpURLConnection.HTTP_OK -> {
+                        return parseJsonInResponseBody(
+                                response,
+                                new TypeReference<List<String>>() {}
+                        );
+                    }
+                    case HttpURLConnection.HTTP_UNAUTHORIZED ->
+                            fail("pas l’autorisation pour l’application " + applicationName);
+                    default ->
+                            fail("%d en code de retour HTTP inattendu".formatted(response.getCode()));
+                }
+                return null;
+            });
+
+            log("données expérimentales à importer :%n" + String.join(System.lineSeparator(), dataTypes));
+
+            List<Command> commands = newCommands(refTypes, dataTypes);
+
+            if (interactive) {
+                String plan = commands.stream()
+                        .map(Command::getDescription)
+                        .collect(Collectors.joining(System.lineSeparator()));
+                log("Plan :");
+                log(plan);
+                System.out.print("est-ce que le plan convient ? [O/n]");
+                String planIsOkString = scanner.nextLine();
+                boolean planIsOk = Set.of("o", "oui", "").contains(planIsOkString.toLowerCase());
+                if (!planIsOk) {
+                    fail("Abandon");
+                }
+            }
+
+            for (Command command : commands) {
+                log("va traiter " + command.getDescription());
+                ClassicHttpRequest request = command.getRequest(uriFactory);
+                httpclient.execute(request, command);
+                log("a traité " + command.getDescription());
+            }
+        } catch (IOException e) {
+            fail("Erreur réseau HTTP : " + e.getMessage());
+        }
+    }
+
+    private List<Command> newCommands(List<String> refTypes, List<String> dataTypes) {
+        List<Command> referenceCommands = refTypes.stream()
+                .flatMap(refType -> getReferenceCommands(refType).stream())
+                .toList();
+        List<Command> dataCommands = dataTypes.stream()
+                .flatMap(dataType -> getUploadDataCommands(dataType).stream())
+                .toList();
+        List<Command> commands = new LinkedList<>();
+        commands.addAll(referenceCommands);
+        commands.addAll(dataCommands);
+        return commands;
+    }
+
+    private List<Command> getUploadDataCommands(String dataType) {
+        Path dataDirectoryForDataType = Path.of(dataType);
+        List<Command> commands;
+        if (dataDirectoryForDataType.toFile().exists()) {
+            if (dataDirectoryForDataType.toFile().isDirectory()) {
+                SortedSet<Path> csvFilePaths = findCsvFilePathsInDirectory(dataDirectoryForDataType);
+                commands = csvFilePaths.stream()
+                        .map(Path::toFile)
+                        .map(dataFile -> newUploadDataCommand(dataType, dataFile))
+                        .toList();
+            } else {
+                logError("le répertoire " + dataDirectoryForDataType + " est un fichier mais il devrait être un dossier. On l’ignore.");
+                commands = Collections.emptyList();
+            }
+        } else {
+            log("le répertoire " + dataDirectoryForDataType + " n’existe pas. Pas de données à importer pour " + dataType);
+            commands = Collections.emptyList();
+        }
+        return commands;
+    }
+
+    private Command newUploadDataCommand(String dataType, File dataFile) {
+        return new Command() {
+            @Override
+            public String getDescription() {
+                return "Téléversement de %s pour alimenter le types de données %s".formatted(dataFile, dataType);
+            }
+
+            @Override
+            public ClassicHttpRequest getRequest(UriFactory uriFactory) {
+                HttpPost httpPost = new HttpPost(uriFactory.forUploadingData(dataType));
+                FileBody dataFileBody = new FileBody(dataFile);
+                HttpEntity reqEntity = MultipartEntityBuilder.create()
+                        .addPart("file", dataFileBody)
+                        .build();
+                httpPost.setEntity(reqEntity);
+                return httpPost;
+            }
+
+            @Override
+            public Void handleResponse(ClassicHttpResponse response) {
+                switch (response.getCode()) {
+                    case HttpURLConnection.HTTP_CREATED -> {
+                        log("import de %s terminé".formatted(dataFile));
+                    }
+                    case HttpURLConnection.HTTP_BAD_REQUEST -> {
+                        List<CsvRowValidationCheckResult> csvRowValidationCheckResults =
+                                parseJsonInResponseBody(
+                                        response,
+                                        new TypeReference<List<CsvRowValidationCheckResult>>() {}
+                                );
+                        logError(csvRowValidationCheckResults.toString());
+                    }
+                    default -> fail(
+                            "%d en code de retour HTTP inattendu à l’import des données %s avec le fichier %s"
+                                    .formatted(response.getCode(), dataFile, dataFile)
+                    );
+                }
+                return null;
+            }
+        };
+    }
+
+    private List<Command> getReferenceCommands(String refType) {
+        File refFile = new File(new File("references"), refType + ".csv");
+        List<Command> commands;
+        if (refFile.exists()) {
+            Set<Path> csvFilePathsInDirectory;
+            if (refFile.isFile()) {
+                csvFilePathsInDirectory = Collections.singleton(refFile.toPath());
+            } else if (refFile.isDirectory()) {
+                csvFilePathsInDirectory = findCsvFilePathsInDirectory(refFile.toPath());
+            } else {
+                throw new IllegalStateException("ne comprend pas de quel type est " + refFile);
+            }
+            commands = csvFilePathsInDirectory.stream()
+                    .map(path -> newUploadReferenceCommand(refType, path.toFile()))
+                    .collect(Collectors.toList());
+        } else {
+            logError("le fichier %s n’existe pas, on ignore l’import du référentiel %s".formatted(refFile, refType));
+            commands = Collections.emptyList();
+        }
+        return commands;
+    }
+
+    private Command newUploadReferenceCommand(String refType, File refFile) {
+        return new Command() {
+            @Override
+            public String getDescription() {
+                return "Téléversement de %s pour alimenter le référentiel %s".formatted(refFile, refType);
+            }
+
+            @Override
+            public ClassicHttpRequest getRequest(UriFactory uriFactory) {
+                HttpPost httpPost = new HttpPost(uriFactory.forUploadingReference(refType));
+                FileBody refFileBody = new FileBody(refFile);
+                HttpEntity reqEntity = MultipartEntityBuilder.create()
+                        .addPart("file", refFileBody)
+                        .build();
+                httpPost.setEntity(reqEntity);
+                return httpPost;
+            }
+
+            @Override
+            public Void handleResponse(ClassicHttpResponse response) {
+                switch (response.getCode()) {
+                    case HttpURLConnection.HTTP_CREATED -> {
+                        String message = "import de " + refFile + " terminé";
+                        log(message);
+                    }
+                    case HttpURLConnection.HTTP_BAD_REQUEST -> {
+                        List<CsvRowValidationCheckResult> csvRowValidationCheckResults =
+                                parseJsonInResponseBody(
+                                        response,
+                                        new TypeReference<List<CsvRowValidationCheckResult>>() {}
+                                );
+                        logError(csvRowValidationCheckResults.toString());
+                    }
+                    default -> fail(
+                            "%d en code de retour HTTP inattendu à l’import du référentiel %s avec le fichier %s"
+                                    .formatted(response.getCode(), refType, refFile)
+                    );
+                }
+                return null;
+            }
+        };
+    }
+
+    private SortedSet<Path> findCsvFilePathsInDirectory(Path directory) {
+        try {
+            AccumulatorPathVisitor accumulatorPathVisitor = new AccumulatorPathVisitor(Counters.longPathCounters());
+            Files.walkFileTree(directory, accumulatorPathVisitor);
+            SortedSet<Path> csvFilePathsInDirectory = accumulatorPathVisitor.getFileList().stream()
+                    .filter(path -> path.getFileName().toString().endsWith(".csv"))
+                    .collect(Collectors.toCollection(TreeSet::new));
+            return Collections.unmodifiableSortedSet(csvFilePathsInDirectory);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void fail(String message) {
+        logError(message);
+        System.exit(1);
+    }
+
+    private static void logError(String string) {
+        System.err.println(string);
+    }
+
+    private static void log(String message) {
+        System.out.println(message);
+    }
+
+    private <T> T parseJsonInResponseBody(ClassicHttpResponse response, TypeReference<T> valueTypeRef) {
+        try (InputStream inputStream = response.getEntity().getContent()) {
+            T parsingResult = new ObjectMapper()
+                    .readValue(
+                            inputStream,
+                            valueTypeRef
+                    );
+            return parsingResult;
+        } catch (DatabindException e) {
+            throw new RuntimeException(e);
+        } catch (StreamReadException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    record CsvRowValidationCheckResult(
+            int lineNumber,
+            Client.ValidationCheckResult validationCheckResult
+    ) {
+    }
+
+    record ValidationCheckResult(
+            ValidationLevel level,
+            ValidationMessage message,
+            Map<String, Object> messageParams,
+            String target
+    ) {
+    }
+
+    enum ValidationLevel {
+        SUCCESS, WARN, ERROR;
+    }
+
+    enum ValidationMessage {
+        unexpectedHeaderColumn,
+        headerColumnPatternNotMatching,
+        unexpectedTokenCount,
+        invalidHeaders,
+        duplicatedHeaders,
+        emptyHeader
+    }
+
+    record UriFactory(String openAdomInstanceUrl, String applicationName) {
+
+        private URI newUri(String endpoint) {
+            try {
+                return new URI("%s/api/v1/%s".formatted(openAdomInstanceUrl, endpoint));
+            } catch (URISyntaxException e) {
+                throw new RuntimeException("ne devrait pas arriver", e);
+            }
+        }
+
+        public URI forLogin() {
+            return newUri("login");
+        }
+
+        public URI forUploadingReference(String refType) {
+            String endpoint = "applications/%s/references/%s".formatted(applicationName, refType);
+            return newUri(endpoint);
+        }
+
+        public URI forUploadingData(String dataType) {
+            String endpoint = "applications/%s/data/%s".formatted(applicationName, dataType);
+            return newUri(endpoint);
+        }
+
+        public URI forApplicationReferenceTypes(String applicationName) {
+            String endpoint = "applications/%s/references".formatted(applicationName);
+            return newUri(endpoint);
+        }
+
+        public URI forApplicationDataTypes(String applicationName) {
+            String endpoint = "applications/%s/data".formatted(applicationName);
+            return newUri(endpoint);
+        }
+    }
+
+    interface Command extends HttpClientResponseHandler<Void> {
+
+        String getDescription();
+
+        ClassicHttpRequest getRequest(UriFactory uriFactory);
+    }
+}
\ No newline at end of file
-- 
GitLab


From 7ed4504e273b75e05f14567a47aeebee46ab730f Mon Sep 17 00:00:00 2001
From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr>
Date: Wed, 12 Jun 2024 15:11:14 +0200
Subject: [PATCH 3/8] =?UTF-8?q?Bouton=20dans=20l'interface=20pour=20t?=
 =?UTF-8?q?=C3=A9l=C3=A9charger=20le=20kit=20de=20t=C3=A9l=C3=A9versement?=
 =?UTF-8?q?=20en=20masse?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../DetailApplicationModalCard.vue            | 141 ++++++++++--------
 ui/src/composable/applications/useFunction.js |   6 +
 ui/src/locales/en.json                        |   1 +
 ui/src/locales/fr.json                        |   1 +
 ui/src/services/rest/ApplicationService.js    |   4 +
 5 files changed, 88 insertions(+), 65 deletions(-)

diff --git a/ui/src/components/application/DetailApplicationModalCard.vue b/ui/src/components/application/DetailApplicationModalCard.vue
index 33542631f..8e2246431 100644
--- a/ui/src/components/application/DetailApplicationModalCard.vue
+++ b/ui/src/components/application/DetailApplicationModalCard.vue
@@ -1,9 +1,9 @@
 <template>
   <ModalCard
-    v-show="open || url"
-    :close-cb="closeCb"
-    :open="open || !!url"
-    :title="application && (application.localName || application.name)"
+      v-show="open || url"
+      :close-cb="closeCb"
+      :open="open || !!url"
+      :title="application && (application.localName || application.name)"
   >
     <div v-if="getUrl" id="charteValidate" class="modal-card" style="width: auto">
       <header class="modal-card-head">
@@ -12,7 +12,7 @@
         </p>
       </header>
       <div class="modal-card-body">
-        <CharteValidator :href="getUrl" />
+        <CharteValidator :href="getUrl"/>
       </div>
       <footer class="modal-card-foot">
         <form id="validateForm">
@@ -26,8 +26,8 @@
       <div class="card">
         <div class="infos card-content">
           <p
-            class="columns"
-            v-html="
+              class="columns"
+              v-html="
               $t('applications.version', {
                 applicationName: application.localName,
                 version: application.version,
@@ -48,42 +48,51 @@
             <b-tab-item aria-description="application">
               <template #header>
                 <span class="rubriqueTitle">{{
-                  $t("applications.functions.application-manage")
-                }}</span>
+                    $t("applications.functions.application-manage")
+                  }}</span>
               </template>
               <div v-if="canCreateApplication" class="buttonWarper">
                 <b-button
-                  icon-left="pen-square"
-                  type="is-warning"
-                  @click="updateApplication(application.name, application.version)"
+                    icon-left="pen-square"
+                    type="is-warning"
+                    @click="updateApplication(application.name, application.version)"
                 >
                   {{ $t("applications.change") }}
                 </b-button>
               </div>
               <div v-if="canCreateApplication" class="buttonWarper">
                 <b-button
-                  icon-left="download"
-                  type="is-primary"
-                  @click="downloadYamlApplication(application)"
+                    icon-left="download"
+                    type="is-primary"
+                    @click="downloadApplicationUploadBundle(application)"
+                >
+                  {{ $t("applications.downloadBundle") }}
+                </b-button>
+              </div>
+              <div v-if="canCreateApplication" class="buttonWarper">
+                <b-button
+                    icon-left="download"
+                    type="is-primary"
+                    @click="downloadYamlApplication(application)"
                 >
                   {{ $t("referencesManagement.download") }}
                 </b-button>
               </div>
               <div class="buttonWarper">
                 <b-button
-                  icon-left="download"
-                  @click="charteHREF = downloadCharteApplication(application.id)"
+                    icon-left="download"
+                    @click="charteHREF = downloadCharteApplication(application.id)"
                 >
                   {{ $t("applications.functions.charte-consult") }}
                 </b-button>
               </div>
               <div v-if="canCreateApplication" class="buttonWarper">
                 <b-upload
-                  v-model="tmpCharte"
-                  :validationMessage="$t('applications.functions.file-select')"
-                  class="file-label"
-                  required
-                  @input="uploadCharteApplication(application)"
+                    v-model="tmpCharte"
+                    :validationMessage="$t('applications.functions.file-select')"
+                    class="file-label"
+                    required
+                    @input="uploadCharteApplication(application)"
                 >
                   <span class="file-cta">
                     <b-icon class="file-icon" icon="upload"></b-icon>
@@ -95,21 +104,21 @@
             <b-tab-item aria-description="references">
               <template #header>
                 <span class="rubriqueTitle">{{
-                  $t("applications.functions.references-manage")
-                }}</span>
+                    $t("applications.functions.references-manage")
+                  }}</span>
               </template>
               <div class="buttonWarper">
                 <b-button
-                  icon-left="drafting-compass"
-                  @click="displayReferencesManagement(application.name)"
-                  >{{ $t("applications.references") }}
+                    icon-left="drafting-compass"
+                    @click="displayReferencesManagement(application.name)"
+                >{{ $t("applications.references") }}
                 </b-button>
               </div>
               <div class="buttonWarper">
                 <b-button
-                  icon-left="drafting-compass"
-                  @click="showReferenceRights(application.name)"
-                  >{{ $t("applications.functions.references-rights") }}
+                    icon-left="drafting-compass"
+                    @click="showReferenceRights(application.name)"
+                >{{ $t("applications.functions.references-rights") }}
                 </b-button>
               </div>
             </b-tab-item>
@@ -119,35 +128,35 @@
               </template>
               <div class="buttonWarper">
                 <b-button icon-left="poll" @click="displayDataSetManagement(application.name)"
-                  >{{ $t("applications.dataset") }}
+                >{{ $t("applications.dataset") }}
                 </b-button>
               </div>
               <div class="buttonWarper">
                 <b-button icon-left="drafting-compass" @click="showDataRights(application.name)"
-                  >{{ $t("applications.functions.data-rights") }}
+                >{{ $t("applications.functions.data-rights") }}
                 </b-button>
               </div>
             </b-tab-item>
             <b-tab-item aria-description="authorization">
               <template #header>
                 <span class="rubriqueTitle">{{
-                  $t("applications.functions.right-requests-manage")
-                }}</span>
+                    $t("applications.functions.right-requests-manage")
+                  }}</span>
               </template>
               <span v-if="!canCreateApplication" class="buttonWarper">
                 <b-button
-                  icon-left="users-cog"
-                  type="is-primary"
-                  @click="showRequestRights(application.name)"
+                    icon-left="users-cog"
+                    type="is-primary"
+                    @click="showRequestRights(application.name)"
                 >
                   {{ $t("dataTypeAuthorizations.showRequests") }}
                 </b-button>
               </span>
               <div v-else class="buttonWarper">
                 <b-button
-                  icon-left="users-cog"
-                  type="is-primary"
-                  @click="requestRights(application.name)"
+                    icon-left="users-cog"
+                    type="is-primary"
+                    @click="requestRights(application.name)"
                 >
                   {{ $t("dataTypeAuthorizations.request") }}
                 </b-button>
@@ -156,27 +165,27 @@
             <b-tab-item aria-description="additional files">
               <template #header>
                 <span class="rubriqueTitle">{{
-                  $t("applications.functions.additional-files-manage")
-                }}</span>
+                    $t("applications.functions.additional-files-manage")
+                  }}</span>
               </template>
               <div
-                v-if="application.additionalFile && application.additionalFile.length !== 0"
-                class="buttonWarper"
+                  v-if="application.additionalFile && application.additionalFile.length !== 0"
+                  class="buttonWarper"
               >
                 <b-button
-                  icon-left="file"
-                  @click="displayAdditionalFilesManagement(application.name)"
+                    icon-left="file"
+                    @click="displayAdditionalFilesManagement(application.name)"
                 >
                   {{ $t("applications.additionalFile") }}
                 </b-button>
               </div>
               <div
-                v-if="application.additionalFile && application.additionalFile.length !== 0"
-                class="buttonWarper"
+                  v-if="application.additionalFile && application.additionalFile.length !== 0"
+                  class="buttonWarper"
               >
                 <b-button
-                  icon-left="file"
-                  @click="showAdditionalFilesManagementRights(application.name)"
+                    icon-left="file"
+                    @click="showAdditionalFilesManagementRights(application.name)"
                 >
                   {{ $t("applications.functions.additional-files-rights") }}
                 </b-button>
@@ -191,15 +200,15 @@
 
 <script>
 import ModalCard from "@/components/charts/ModalCard";
-import { useRedirections } from "@/composable/applications/useFunction";
+import {useRedirections} from "@/composable/applications/useFunction";
 import useText from "@/composable/components/text";
 import CharteValidator from "@/components/application/CharteValidator.vue";
-import { computed } from "vue";
+import {computed} from "vue";
 import services from "@/composable/services";
 
 export default {
   name: "DetailApplicationModalCard",
-  components: { CharteValidator, ModalCard },
+  components: {CharteValidator, ModalCard},
   emits: ["setValidatedCharte"],
   props: {
     open: {
@@ -240,9 +249,9 @@ export default {
       get() {
         const authenticatedUser = () => services.loginService.getAuthenticatedUser();
         if (
-          authenticatedUser() &&
-          authenticatedUser().chartes &&
-          authenticatedUser().chartes[props.application.id]
+            authenticatedUser() &&
+            authenticatedUser().chartes &&
+            authenticatedUser().chartes[props.application.id]
         ) {
           return new Date().getTime() > authenticatedUser().chartes[props.application.id];
         }
@@ -251,22 +260,23 @@ export default {
       set(bool) {
         const authenticatedUser = () => services.loginService.getAuthenticatedUser();
         bool &&
-          services.loginService.modifAcount({
-            login: authenticatedUser().login,
-            email: authenticatedUser().email,
-            charte: props.application.id,
-          });
+        services.loginService.modifAcount({
+          login: authenticatedUser().login,
+          email: authenticatedUser().email,
+          charte: props.application.id,
+        });
         bool &&
-          ctx.emit("setValidatedCharte", {
-            applicationId: props.application.id,
-            validated: bool,
-          });
+        ctx.emit("setValidatedCharte", {
+          applicationId: props.application.id,
+          validated: bool,
+        });
       },
     });
     const {
       createApplication,
       updateApplication,
       downloadYamlApplication,
+      downloadApplicationUploadBundle,
       downloadCharteApplication,
       uploadCharteApplication,
       tmpCharte,
@@ -289,6 +299,7 @@ export default {
       updateApplication,
       downloadYamlApplication,
       downloadCharteApplication,
+      downloadApplicationUploadBundle,
       charteHREF,
       getUrl,
       uploadCharteApplication,
diff --git a/ui/src/composable/applications/useFunction.js b/ui/src/composable/applications/useFunction.js
index 76b3856a0..43508f365 100644
--- a/ui/src/composable/applications/useFunction.js
+++ b/ui/src/composable/applications/useFunction.js
@@ -36,6 +36,12 @@ export function useRedirections(application = { authorizations: [] }) {
     return false;
   }
 
+  async function downloadApplicationUploadBundle(application) {
+    await (canCreateApplication() &&
+        services.applicationService.downloadApplicationUploadBundle(application.name));
+    return false;
+  }
+
   /*
     forAll
      */
diff --git a/ui/src/locales/en.json b/ui/src/locales/en.json
index 6cacdfe45..b3646ba6e 100644
--- a/ui/src/locales/en.json
+++ b/ui/src/locales/en.json
@@ -125,6 +125,7 @@
     "create": "Create application",
     "creation-date": "Creation date",
     "dataset": "Data types",
+    "downloadBundle": "Download the mass-upload kit",
     "filter": "Filter by",
     "functions": {
       "additional-files-manage": "Additional files management",
diff --git a/ui/src/locales/fr.json b/ui/src/locales/fr.json
index 76f585558..9bafc01a7 100644
--- a/ui/src/locales/fr.json
+++ b/ui/src/locales/fr.json
@@ -125,6 +125,7 @@
     "create": "Créer l'application",
     "creation-date": "Date de création",
     "dataset": "Données",
+    "downloadBundle": "Télécharger le kit de téléversement en masse",
     "filter": "Filtrer",
     "functions": {
       "additional-files-manage": "Gestion des fichiers additionnels",
diff --git a/ui/src/services/rest/ApplicationService.js b/ui/src/services/rest/ApplicationService.js
index 5337a71c0..052703ed0 100644
--- a/ui/src/services/rest/ApplicationService.js
+++ b/ui/src/services/rest/ApplicationService.js
@@ -43,4 +43,8 @@ export class ApplicationService extends Fetcher {
   async getValidateConfiguration() {
     return this.post("validate-configuration");
   }
+
+  async downloadApplicationUploadBundle(applicationName) {
+    return this.downloadFile(`applications/${applicationName}/upload-bundle`);
+  }
 }
-- 
GitLab


From c9037750f373a585abbee01d045c46568938e203 Mon Sep 17 00:00:00 2001
From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr>
Date: Wed, 12 Jun 2024 15:32:42 +0200
Subject: [PATCH 4/8] =?UTF-8?q?Prototype=20du=20service=20de=20t=C3=A9l?=
 =?UTF-8?q?=C3=A9chargement=20du=20kit=20de=20t=C3=A9l=C3=A9versement=20en?=
 =?UTF-8?q?=20masse?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../fr/inra/oresing/rest/OreSiResources.java  | 30 +++++++++++++++++++
 .../fr/inra/oresing/rest/OreSiService.java    | 20 +++++++++++++
 .../inra/oresing/rest/OreSiResourcesTest.java | 27 +++++++++++++++++
 3 files changed, 77 insertions(+)

diff --git a/src/main/java/fr/inra/oresing/rest/OreSiResources.java b/src/main/java/fr/inra/oresing/rest/OreSiResources.java
index f8b091679..9c314a654 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiResources.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiResources.java
@@ -56,6 +56,7 @@ import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.enums.Explode;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.ExampleObject;
+import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.MapUtils;
@@ -1038,4 +1039,33 @@ public class OreSiResources {
                                            @PathVariable("dataType") final String dataType) throws IOException {
         return buidSynthesis(nameOrId, dataType, null);
     }
+
+
+    @GetMapping(value = "/applications/{nameOrId}/upload-bundle", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
+    public ResponseEntity<StreamingResponseBody> getUploadBundle(
+            @PathVariable("nameOrId") String nameOrId,
+            HttpServletRequest request,
+            HttpServletResponse response) {
+        String instanceUrl = "%s://%s:%s".formatted(
+                request.getScheme(),
+                request.getServerName(),
+                request.getServerPort()
+        );
+        StreamingResponseBody streamResponseBody = out -> {
+            try (ZipOutputStream zipOutputStream = new ZipOutputStream(out, StandardCharsets.UTF_8)) {
+                service.writeUploadBundle(instanceUrl, nameOrId, zipOutputStream);
+            } catch (IOException ioe) {
+                log.error("error while generating upload bundle for " + nameOrId, ioe);
+            }
+        };
+        response.setContentType("application/zip");
+        String fileName = "%s-upload-bundle.zip".formatted(nameOrId);
+        response.setHeader("Content-Disposition", "attachment; filename=%s".formatted(fileName));
+        response.addHeader("Pragma", "no-cache");
+        response.addHeader("Expires", "0");
+        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+        return ResponseEntity.ok()
+                .contentType(MediaType.APPLICATION_OCTET_STREAM)
+                .body(streamResponseBody);
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java
index 8ac1fea3e..29dc5079d 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiService.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java
@@ -74,6 +74,7 @@ import javax.annotation.Nullable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.*;
@@ -81,6 +82,7 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
 @Slf4j
@@ -1177,5 +1179,23 @@ public class OreSiService {
         return store;
     }
 
+    public void writeUploadBundle(String instanceUrl, String nameOrId, ZipOutputStream zipOutputStream) throws IOException {
+        Application application = applicationService.getApplication(nameOrId);
+        //application.getConfiguration().getReferences();
+        zipOutputStream.putNextEntry(new ZipEntry("OpenAdomClient.groovy"));
+        zipOutputStream.write("TAISTE".getBytes(StandardCharsets.UTF_8));
+        zipOutputStream.closeEntry();
+        zipOutputStream.putNextEntry(new ZipEntry("openAdom-client-configuration.json"));
+        String configurationJson = """
+                {
+                  "instanceUrl": "%s",
+                  "applicationName": "%s"
+                }
+                """.formatted(instanceUrl, nameOrId);
+        zipOutputStream.write(configurationJson.getBytes(StandardCharsets.UTF_8));
+        zipOutputStream.closeEntry();
+        zipOutputStream.putNextEntry(new ZipEntry("references/"));
+    }
+
 
 }
diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
index 8d239465a..f137c04ee 100644
--- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
+++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
@@ -3163,4 +3163,31 @@ on test le dépôt d'un fichier récursif
             log.debug(StringUtils.abbreviate(response, 50));
         }
     }
+
+    @Test
+    public void testGetUploadBundle() throws Exception {
+        addApplicationMonsore();
+        mockMvc.perform(asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsore/upload-bundle")
+                                .accept(MediaType.APPLICATION_OCTET_STREAM)
+                                .cookie(authCookie))
+                        .andExpect(status().is2xxSuccessful())
+                        .andExpect(request().asyncStarted())
+                        .andReturn()))
+                .andExpect(result -> {
+                    List<ZipEntry> entries = new ArrayList<>();
+                    try (ZipInputStream zi = new ZipInputStream(new ByteArrayInputStream(result.getResponse().getContentAsByteArray()))) {
+                        ZipEntry zipEntry;
+                        while ((zipEntry = zi.getNextEntry()) != null) {
+                            entries.add(zipEntry);
+                            zi.readAllBytes();
+                        }
+                    }
+                    final List<String> entryNames = entries.stream()
+                            .map(ZipEntry::getName)
+                            .toList();
+                    Assertions.assertTrue(() -> entryNames.contains("OpenAdomClient.groovy"));
+                    Assertions.assertTrue(() -> entryNames.contains("openAdom-client-configuration.json"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/"));
+                });
+    }
 }
-- 
GitLab


From 5e474c7248658a648cf35d2f99d9881a6cf3891c Mon Sep 17 00:00:00 2001
From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr>
Date: Wed, 12 Jun 2024 15:42:12 +0200
Subject: [PATCH 5/8] =?UTF-8?q?Am=C3=A9liore=20le=20client?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../java/fr/inra/oresing/client/Client.java   | 72 ++++++++++++++-----
 1 file changed, 53 insertions(+), 19 deletions(-)

diff --git a/src/main/java/fr/inra/oresing/client/Client.java b/src/main/java/fr/inra/oresing/client/Client.java
index d09f59f65..2d776da0f 100644
--- a/src/main/java/fr/inra/oresing/client/Client.java
+++ b/src/main/java/fr/inra/oresing/client/Client.java
@@ -40,19 +40,22 @@ import java.util.stream.Collectors;
 
 public class Client {
 
-    public static void main(String[] args) {
+    public static void main(String[] args) throws IOException {
         new Client().run();
     }
 
-    public void run() {
-        String applicationName = "foret";
-        String openAdomInstanceUrl = "http://localhost:8081";
+    public void run() throws IOException {
+
+        ClientConfiguration clientConfiguration = readConfiguration();
+
+        String applicationName = clientConfiguration.applicationName();
+        URI instanceUrl = clientConfiguration.instanceUrl();
         String login;
         String password;
-        boolean interactive = false;
+        boolean interactive = true;
         Scanner scanner = new Scanner(System.in);
         if (interactive) {
-            System.out.println("Veuillez saisir les informations de connexion à " + openAdomInstanceUrl);
+            System.out.println("Veuillez saisir les informations de connexion à " + instanceUrl);
             System.out.print("identifiant : ");
             login = scanner.nextLine();
             System.out.print("mot de passe : ");
@@ -64,7 +67,7 @@ public class Client {
 
         CookieStore cookieStore = new BasicCookieStore();
 
-        UriFactory uriFactory = new UriFactory(openAdomInstanceUrl, applicationName);
+        UriFactory uriFactory = new UriFactory(instanceUrl, applicationName);
 
         try (CloseableHttpClient httpclient = HttpClients.custom()
                 .setDefaultCookieStore(cookieStore)
@@ -110,18 +113,19 @@ public class Client {
                     case HttpURLConnection.HTTP_OK -> {
                         return parseJsonInResponseBody(
                                 response,
-                                new TypeReference<List<String>>() {}
+                                new TypeReference<List<String>>() {
+                                }
                         );
                     }
                     case HttpURLConnection.HTTP_UNAUTHORIZED ->
                             fail("pas l’autorisation pour l’application " + applicationName);
-                    default ->
-                            fail("%d en code de retour HTTP inattendu".formatted(response.getCode()));
+                    default -> fail("%d en code de retour HTTP inattendu".formatted(response.getCode()));
                 }
                 return null;
             });
 
-            log("référentiels à importer :%n" + String.join(System.lineSeparator(), refTypes));
+            log("référentiels à importer :" + System.lineSeparator()
+                    + String.join(System.lineSeparator(), refTypes));
 
             ClassicHttpRequest getApplicationDataTypesRequest = ClassicRequestBuilder
                     .get(uriFactory.forApplicationDataTypes(applicationName))
@@ -132,18 +136,19 @@ public class Client {
                     case HttpURLConnection.HTTP_OK -> {
                         return parseJsonInResponseBody(
                                 response,
-                                new TypeReference<List<String>>() {}
+                                new TypeReference<List<String>>() {
+                                }
                         );
                     }
                     case HttpURLConnection.HTTP_UNAUTHORIZED ->
                             fail("pas l’autorisation pour l’application " + applicationName);
-                    default ->
-                            fail("%d en code de retour HTTP inattendu".formatted(response.getCode()));
+                    default -> fail("%d en code de retour HTTP inattendu".formatted(response.getCode()));
                 }
                 return null;
             });
 
-            log("données expérimentales à importer :%n" + String.join(System.lineSeparator(), dataTypes));
+            log("données expérimentales à importer :" + System.lineSeparator()
+                    + String.join(System.lineSeparator(), dataTypes));
 
             List<Command> commands = newCommands(refTypes, dataTypes);
 
@@ -172,6 +177,16 @@ public class Client {
         }
     }
 
+    private ClientConfiguration readConfiguration() throws IOException {
+        File configurationFile = new File("openAdom-client-configuration.json");
+        ClientConfiguration clientConfiguration = new ObjectMapper()
+                .readValue(
+                        configurationFile,
+                        ClientConfiguration.class
+                );
+        return clientConfiguration;
+    }
+
     private List<Command> newCommands(List<String> refTypes, List<String> dataTypes) {
         List<Command> referenceCommands = refTypes.stream()
                 .flatMap(refType -> getReferenceCommands(refType).stream())
@@ -234,7 +249,8 @@ public class Client {
                         List<CsvRowValidationCheckResult> csvRowValidationCheckResults =
                                 parseJsonInResponseBody(
                                         response,
-                                        new TypeReference<List<CsvRowValidationCheckResult>>() {}
+                                        new TypeReference<List<CsvRowValidationCheckResult>>() {
+                                        }
                                 );
                         logError(csvRowValidationCheckResults.toString());
                     }
@@ -299,7 +315,8 @@ public class Client {
                         List<CsvRowValidationCheckResult> csvRowValidationCheckResults =
                                 parseJsonInResponseBody(
                                         response,
-                                        new TypeReference<List<CsvRowValidationCheckResult>>() {}
+                                        new TypeReference<List<CsvRowValidationCheckResult>>() {
+                                        }
                                 );
                         logError(csvRowValidationCheckResults.toString());
                     }
@@ -383,11 +400,14 @@ public class Client {
         emptyHeader
     }
 
-    record UriFactory(String openAdomInstanceUrl, String applicationName) {
+    /**
+     * Les routes disponibles sur le serveur.
+     */
+    record UriFactory(URI instanceUrl, String applicationName) {
 
         private URI newUri(String endpoint) {
             try {
-                return new URI("%s/api/v1/%s".formatted(openAdomInstanceUrl, endpoint));
+                return new URI("%s/api/v1/%s".formatted(instanceUrl, endpoint));
             } catch (URISyntaxException e) {
                 throw new RuntimeException("ne devrait pas arriver", e);
             }
@@ -418,10 +438,24 @@ public class Client {
         }
     }
 
+
+    /**
+     * Une étape du téléversement soit un fichier à téléverser, une requête HTTP et comment traiter la réponse.
+     */
     interface Command extends HttpClientResponseHandler<Void> {
 
         String getDescription();
 
         ClassicHttpRequest getRequest(UriFactory uriFactory);
     }
+
+    /**
+     * Le contenu du fichier de configuration du client.
+     *
+     * @param instanceUrl     l’adresse du serveur au format "http://hote:port"
+     * @param applicationName le nom de l’application
+     */
+    private record ClientConfiguration(URI instanceUrl, String applicationName) {
+
+    }
 }
\ No newline at end of file
-- 
GitLab


From 3ae2b54a7aa764cb052a9529552361644a2c39da Mon Sep 17 00:00:00 2001
From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr>
Date: Wed, 12 Jun 2024 16:11:31 +0200
Subject: [PATCH 6/8] =?UTF-8?q?Inclus=20le=20vrai=20groovy=20dans=20le=20k?=
 =?UTF-8?q?it=20de=20t=C3=A9l=C3=A9chargement=20en=20masse?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../java/fr/inra/oresing/client/Client.java   |  75 +--
 .../fr/inra/oresing/rest/OreSiResources.java  |   7 +-
 .../fr/inra/oresing/rest/OreSiService.java    |  34 +-
 .../inra/oresing/client/OpenAdomClient.groovy | 435 ++++++++++++++++++
 .../inra/oresing/rest/OreSiResourcesTest.java |  17 +-
 5 files changed, 523 insertions(+), 45 deletions(-)
 create mode 100644 src/main/resources/fr/inra/oresing/client/OpenAdomClient.groovy

diff --git a/src/main/java/fr/inra/oresing/client/Client.java b/src/main/java/fr/inra/oresing/client/Client.java
index 2d776da0f..24100b522 100644
--- a/src/main/java/fr/inra/oresing/client/Client.java
+++ b/src/main/java/fr/inra/oresing/client/Client.java
@@ -38,6 +38,14 @@ import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
 
+/**
+ * Cette classe n’est pas utile en soi, elle a vocation à être portée en Groovy pour
+ * devenir le script qui est inclus dans le kit de téléversement.
+ */
+// à ajouter en tête du fichier Groovy
+//
+//@Grab(group = "commons-io", module = "commons-io", version = "2.8.0")
+//@Grab(group = "org.apache.httpcomponents.client5", module = "httpclient5", version = "5.2.1")
 public class Client {
 
     public static void main(String[] args) throws IOException {
@@ -50,6 +58,7 @@ public class Client {
 
         String applicationName = clientConfiguration.applicationName();
         URI instanceUrl = clientConfiguration.instanceUrl();
+
         String login;
         String password;
         boolean interactive = true;
@@ -66,7 +75,6 @@ public class Client {
         }
 
         CookieStore cookieStore = new BasicCookieStore();
-
         UriFactory uriFactory = new UriFactory(instanceUrl, applicationName);
 
         try (CloseableHttpClient httpclient = HttpClients.custom()
@@ -108,21 +116,20 @@ public class Client {
                     .get(uriFactory.forApplicationReferenceTypes(applicationName))
                     .build();
 
-            List<String> refTypes = httpclient.execute(getApplicationReferenceTypesRequest, response -> {
-                switch (response.getCode()) {
-                    case HttpURLConnection.HTTP_OK -> {
-                        return parseJsonInResponseBody(
-                                response,
-                                new TypeReference<List<String>>() {
-                                }
-                        );
+            List<String> refTypes = httpclient.execute(getApplicationReferenceTypesRequest, response ->
+                    switch (response.getCode()) {
+                        case HttpURLConnection.HTTP_OK ->
+                                parseJsonInResponseBody(
+                                        response,
+                                        new TypeReference<List<String>>() {
+                                        }
+                                );
+                        case HttpURLConnection.HTTP_UNAUTHORIZED ->
+                                throw new IllegalStateException("pas l’autorisation pour l’application " + applicationName);
+                        default ->
+                                throw new IllegalStateException("%d en code de retour HTTP inattendu".formatted(response.getCode()));
                     }
-                    case HttpURLConnection.HTTP_UNAUTHORIZED ->
-                            fail("pas l’autorisation pour l’application " + applicationName);
-                    default -> fail("%d en code de retour HTTP inattendu".formatted(response.getCode()));
-                }
-                return null;
-            });
+            );
 
             log("référentiels à importer :" + System.lineSeparator()
                     + String.join(System.lineSeparator(), refTypes));
@@ -131,21 +138,18 @@ public class Client {
                     .get(uriFactory.forApplicationDataTypes(applicationName))
                     .build();
 
-            List<String> dataTypes = httpclient.execute(getApplicationDataTypesRequest, response -> {
-                switch (response.getCode()) {
-                    case HttpURLConnection.HTTP_OK -> {
-                        return parseJsonInResponseBody(
-                                response,
-                                new TypeReference<List<String>>() {
-                                }
-                        );
-                    }
-                    case HttpURLConnection.HTTP_UNAUTHORIZED ->
-                            fail("pas l’autorisation pour l’application " + applicationName);
-                    default -> fail("%d en code de retour HTTP inattendu".formatted(response.getCode()));
-                }
-                return null;
-            });
+            List<String> dataTypes = httpclient.execute(getApplicationDataTypesRequest, response ->
+                    switch (response.getCode()) {
+                        case HttpURLConnection.HTTP_OK ->
+                                parseJsonInResponseBody(
+                                        response,
+                                        new TypeReference<List<String>>() {}
+                                );
+                        case HttpURLConnection.HTTP_UNAUTHORIZED ->
+                                throw new IllegalStateException("pas l’autorisation pour l’application " + applicationName);
+                        default ->
+                                throw new IllegalStateException("%d en code de retour HTTP inattendu".formatted(response.getCode()));
+                    });
 
             log("données expérimentales à importer :" + System.lineSeparator()
                     + String.join(System.lineSeparator(), dataTypes));
@@ -249,8 +253,7 @@ public class Client {
                         List<CsvRowValidationCheckResult> csvRowValidationCheckResults =
                                 parseJsonInResponseBody(
                                         response,
-                                        new TypeReference<List<CsvRowValidationCheckResult>>() {
-                                        }
+                                        new TypeReference<List<CsvRowValidationCheckResult>>() {}
                                 );
                         logError(csvRowValidationCheckResults.toString());
                     }
@@ -315,8 +318,7 @@ public class Client {
                         List<CsvRowValidationCheckResult> csvRowValidationCheckResults =
                                 parseJsonInResponseBody(
                                         response,
-                                        new TypeReference<List<CsvRowValidationCheckResult>>() {
-                                        }
+                                        new TypeReference<List<CsvRowValidationCheckResult>>() {}
                                 );
                         logError(csvRowValidationCheckResults.toString());
                     }
@@ -438,7 +440,6 @@ public class Client {
         }
     }
 
-
     /**
      * Une étape du téléversement soit un fichier à téléverser, une requête HTTP et comment traiter la réponse.
      */
@@ -452,10 +453,10 @@ public class Client {
     /**
      * Le contenu du fichier de configuration du client.
      *
-     * @param instanceUrl     l’adresse du serveur au format "http://hote:port"
+     * @param instanceUrl l’adresse du serveur au format "http://hote:port"
      * @param applicationName le nom de l’application
      */
     private record ClientConfiguration(URI instanceUrl, String applicationName) {
 
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/fr/inra/oresing/rest/OreSiResources.java b/src/main/java/fr/inra/oresing/rest/OreSiResources.java
index 9c314a654..d61b6673b 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiResources.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiResources.java
@@ -326,12 +326,13 @@ public class OreSiResources {
      * Liste les noms des types de referenciels disponible
      *
      * @param nameOrId l'id ou le nom de l'application
-     * @return un tableau de chaine
+     * @return les noms triés selon l’ordre dans lequel il faut faire les imports (selon les dépendances entre
+     * référentiels).
      */
     @GetMapping(value = "/applications/{nameOrId}/references", produces = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<List<String>> listNameReferences(@PathVariable("nameOrId") final String nameOrId) {
+    public ResponseEntity<Set<String>> listNameReferences(@PathVariable("nameOrId") String nameOrId) {
         final Application application = applicationService.getApplication(nameOrId);
-        return ResponseEntity.ok(application.getData());
+        return ResponseEntity.ok(application.getConfiguration().dataDescription().keySet());
     }
 
     /**
diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java
index 29dc5079d..cb758ca06 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiService.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java
@@ -3,7 +3,9 @@ package fr.inra.oresing.rest;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import com.google.common.collect.*;
+import com.google.common.io.Resources;
 import com.google.common.primitives.Ints;
+import fr.inra.oresing.client.Client;
 import fr.inra.oresing.domain.*;
 import fr.inra.oresing.domain.additionalfiles.AdditionalBinaryFile;
 import fr.inra.oresing.domain.additionalfiles.AdditionalFilesInfos;
@@ -74,6 +76,7 @@ import javax.annotation.Nullable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -1181,9 +1184,11 @@ public class OreSiService {
 
     public void writeUploadBundle(String instanceUrl, String nameOrId, ZipOutputStream zipOutputStream) throws IOException {
         Application application = applicationService.getApplication(nameOrId);
-        //application.getConfiguration().getReferences();
-        zipOutputStream.putNextEntry(new ZipEntry("OpenAdomClient.groovy"));
-        zipOutputStream.write("TAISTE".getBytes(StandardCharsets.UTF_8));
+        String groovyScriptFileName = "OpenAdomClient.groovy";
+        zipOutputStream.putNextEntry(new ZipEntry(groovyScriptFileName));
+        URL scriptAsResource = Resources.getResource(Client.class, groovyScriptFileName);
+        byte[] scriptAsBytes = Resources.toByteArray(scriptAsResource);
+        zipOutputStream.write(scriptAsBytes);
         zipOutputStream.closeEntry();
         zipOutputStream.putNextEntry(new ZipEntry("openAdom-client-configuration.json"));
         String configurationJson = """
@@ -1194,7 +1199,28 @@ public class OreSiService {
                 """.formatted(instanceUrl, nameOrId);
         zipOutputStream.write(configurationJson.getBytes(StandardCharsets.UTF_8));
         zipOutputStream.closeEntry();
-        zipOutputStream.putNextEntry(new ZipEntry("references/"));
+        zipOutputStream.putNextEntry(new ZipEntry("LISEZ-MOI.txt"));
+        String readmeContent = """
+                Instructions :
+                
+                1. installer Groovy version 4 minimum https://groovy.apache.org/download.html#osinstall
+                
+                2. vérifier que Groovy fonctionne en lançant groovy --version
+                
+                Exemple de retour correct :
+                Groovy Version: 4.0.15 JVM: 17.0.8.1 Vendor: Private Build OS: Linux
+                
+                3. lancer le script d’import en masse
+                
+                groovy %s
+                """.formatted(groovyScriptFileName);
+        zipOutputStream.write(readmeContent.getBytes(StandardCharsets.UTF_8));
+        zipOutputStream.closeEntry();
+        for (String reference : application.getConfiguration().dataDescription().keySet()) {
+            String referenceCsvFilePath = "references/%s.csv".formatted(reference);
+            zipOutputStream.putNextEntry(new ZipEntry(referenceCsvFilePath));
+            zipOutputStream.closeEntry();
+        }
     }
 
 
diff --git a/src/main/resources/fr/inra/oresing/client/OpenAdomClient.groovy b/src/main/resources/fr/inra/oresing/client/OpenAdomClient.groovy
new file mode 100644
index 000000000..175ce3005
--- /dev/null
+++ b/src/main/resources/fr/inra/oresing/client/OpenAdomClient.groovy
@@ -0,0 +1,435 @@
+package fr.inra.oresing.client;
+
+import com.fasterxml.jackson.core.exc.StreamReadException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DatabindException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.io.file.AccumulatorPathVisitor;
+import org.apache.commons.io.file.Counters;
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.cookie.BasicCookieStore;
+import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.entity.mime.FileBody;
+import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.support.ClassicRequestBuilder
+import java.net.HttpURLConnection
+import java.io.InputStream
+
+import java.nio.file.Files;
+import java.nio.file.Path
+import java.util.stream.Collectors;
+
+@Grab(group = "commons-io", module = "commons-io", version = "2.8.0")
+@Grab(group = "org.apache.httpcomponents.client5", module = "httpclient5", version = "5.2.1")
+
+ClientConfiguration clientConfiguration = readConfiguration();
+
+String applicationName = clientConfiguration.applicationName();
+URI instanceUrl = clientConfiguration.instanceUrl();
+
+String login;
+String password;
+boolean interactive = true;
+Scanner scanner = new Scanner(System.in);
+if (interactive) {
+    System.out.println("Veuillez saisir les informations de connexion à " + instanceUrl);
+    System.out.print("identifiant : ");
+    login = scanner.nextLine();
+    System.out.print("mot de passe : ");
+    password = scanner.nextLine();
+} else {
+    login = "poussin";
+    password = "xxxx";
+}
+
+CookieStore cookieStore = new BasicCookieStore();
+UriFactory uriFactory = new UriFactory(instanceUrl, applicationName);
+
+try (CloseableHttpClient httpclient = HttpClients.custom()
+        .setDefaultCookieStore(cookieStore)
+        .build()) {
+
+    ClassicHttpRequest loginRequest = ClassicRequestBuilder.post()
+            .setUri(uriFactory.forLogin())
+            .addParameter("login", login)
+            .addParameter("password", password)
+            .build();
+
+    httpclient.execute(loginRequest, response -> {
+        switch (response.getCode()) {
+            case HttpURLConnection.HTTP_OK -> {
+                EntityUtils.consume(response.getEntity());
+                switch (cookieStore.getCookies().size()) {
+                    case 0 -> fail("authentification échouée : pas de cookie d’authentification retourné");
+                    case 1 -> {
+                        if (cookieStore.getCookies().get(0).getName().equals("si-ore-jwt")) {
+                            log("authentification OK");
+                        } else {
+                            fail("authentification échouée : pas de cookie d’authentification retourné");
+                        }
+                    }
+                    default ->
+                        fail(cookieStore.getCookies().size() + " cookies retournés à l’authentification");
+                }
+            }
+            case HttpURLConnection.HTTP_UNAUTHORIZED ->
+                fail("authentification échouée : identifiant ou mot de passe faux ?");
+            default ->
+                fail("%d en code de retour HTTP inattendu à l’authentification".formatted(response.getCode()));
+        }
+        return null;
+    });
+
+    ClassicHttpRequest getApplicationReferenceTypesRequest = ClassicRequestBuilder
+            .get(uriFactory.forApplicationReferenceTypes(applicationName))
+            .build();
+
+    List<String> refTypes = httpclient.execute(getApplicationReferenceTypesRequest, response ->
+            switch (response.getCode()) {
+                case HttpURLConnection.HTTP_OK ->
+                    parseJsonInResponseBody(
+                            response,
+                            new TypeReference<List<String>>() {
+                            }
+                    );
+                case HttpURLConnection.HTTP_UNAUTHORIZED ->
+                    throw new IllegalStateException("pas l’autorisation pour l’application " + applicationName);
+                default ->
+                    throw new IllegalStateException("%d en code de retour HTTP inattendu".formatted(response.getCode()));
+            }
+    );
+
+    log("référentiels à importer :" + System.lineSeparator()
+            + String.join(System.lineSeparator(), refTypes));
+
+    ClassicHttpRequest getApplicationDataTypesRequest = ClassicRequestBuilder
+            .get(uriFactory.forApplicationDataTypes(applicationName))
+            .build();
+
+    List<String> dataTypes = httpclient.execute(getApplicationDataTypesRequest, response ->
+            switch (response.getCode()) {
+                case HttpURLConnection.HTTP_OK ->
+                    parseJsonInResponseBody(
+                            response,
+                            new TypeReference<List<String>>() {}
+                    );
+                case HttpURLConnection.HTTP_UNAUTHORIZED ->
+                    throw new IllegalStateException("pas l’autorisation pour l’application " + applicationName);
+                default ->
+                    throw new IllegalStateException("%d en code de retour HTTP inattendu".formatted(response.getCode()));
+            });
+
+    log("données expérimentales à importer :" + System.lineSeparator()
+            + String.join(System.lineSeparator(), dataTypes));
+
+    List<Command> commands = newCommands(refTypes, dataTypes);
+
+    if (interactive) {
+        String plan = commands.stream()
+                .map(Command::getDescription)
+                .collect(Collectors.joining(System.lineSeparator()));
+        log("Plan :");
+        log(plan);
+        System.out.print("est-ce que le plan convient ? [O/n]");
+        String planIsOkString = scanner.nextLine();
+        boolean planIsOk = Set.of("o", "oui", "").contains(planIsOkString.toLowerCase());
+        if (!planIsOk) {
+            fail("Abandon");
+        }
+    }
+
+    for (Command command : commands) {
+        log("va traiter " + command.getDescription());
+        ClassicHttpRequest request = command.getRequest(uriFactory);
+        httpclient.execute(request, command);
+        log("a traité " + command.getDescription());
+    }
+} catch (IOException e) {
+    fail("Erreur réseau HTTP : " + e.getMessage());
+}
+
+private ClientConfiguration readConfiguration() throws IOException {
+    File configurationFile = new File("openAdom-client-configuration.json");
+    ClientConfiguration clientConfiguration = new ObjectMapper()
+            .readValue(
+                    configurationFile,
+                    ClientConfiguration.class
+            );
+    return clientConfiguration;
+}
+
+private List<Command> newCommands(List<String> refTypes, List<String> dataTypes) {
+    List<Command> referenceCommands = refTypes.stream()
+            .flatMap(refType -> getReferenceCommands(refType).stream())
+            .toList();
+    List<Command> dataCommands = dataTypes.stream()
+            .flatMap(dataType -> getUploadDataCommands(dataType).stream())
+            .toList();
+    List<Command> commands = new LinkedList<>();
+    commands.addAll(referenceCommands);
+    commands.addAll(dataCommands);
+    return commands;
+}
+
+private List<Command> getUploadDataCommands(String dataType) {
+    Path dataDirectoryForDataType = Path.of(dataType);
+    List<Command> commands;
+    if (dataDirectoryForDataType.toFile().exists()) {
+        if (dataDirectoryForDataType.toFile().isDirectory()) {
+            SortedSet<Path> csvFilePaths = findCsvFilePathsInDirectory(dataDirectoryForDataType);
+            commands = csvFilePaths.stream()
+                    .map(Path::toFile)
+                    .map(dataFile -> newUploadDataCommand(dataType, dataFile))
+                    .toList();
+        } else {
+            logError("le répertoire " + dataDirectoryForDataType + " est un fichier mais il devrait être un dossier. On l’ignore.");
+            commands = Collections.emptyList();
+        }
+    } else {
+        log("le répertoire " + dataDirectoryForDataType + " n’existe pas. Pas de données à importer pour " + dataType);
+        commands = Collections.emptyList();
+    }
+    return commands;
+}
+
+private Command newUploadDataCommand(String dataType, File dataFile) {
+    return new Command() {
+        @Override
+        String getDescription() {
+            return "Téléversement de %s pour alimenter le types de données %s".formatted(dataFile, dataType);
+        }
+
+        @Override
+        ClassicHttpRequest getRequest(UriFactory uriFactory) {
+            HttpPost httpPost = new HttpPost(uriFactory.forUploadingData(dataType));
+            FileBody dataFileBody = new FileBody(dataFile);
+            HttpEntity reqEntity = MultipartEntityBuilder.create()
+                    .addPart("file", dataFileBody)
+                    .build();
+            httpPost.setEntity(reqEntity);
+            return httpPost;
+        }
+
+        @Override
+        Void handleResponse(ClassicHttpResponse response) {
+            switch (response.getCode()) {
+                case HttpURLConnection.HTTP_CREATED -> {
+                    log("import de %s terminé".formatted(dataFile));
+                }
+                case HttpURLConnection.HTTP_BAD_REQUEST -> {
+                    List<CsvRowValidationCheckResult> csvRowValidationCheckResults =
+                            parseJsonInResponseBody(
+                                    response,
+                                    new TypeReference<List<CsvRowValidationCheckResult>>() {}
+                            );
+                    logError(csvRowValidationCheckResults.toString());
+                }
+                default -> fail(
+                        "%d en code de retour HTTP inattendu à l’import des données %s avec le fichier %s"
+                                .formatted(response.getCode(), dataFile, dataFile)
+                );
+            }
+            return null;
+        }
+    };
+}
+
+private List<Command> getReferenceCommands(String refType) {
+    File refFile = new File(new File("references"), refType + ".csv");
+    List<Command> commands;
+    if (refFile.exists()) {
+        Set<Path> csvFilePathsInDirectory;
+        if (refFile.isFile()) {
+            csvFilePathsInDirectory = Collections.singleton(refFile.toPath());
+        } else if (refFile.isDirectory()) {
+            csvFilePathsInDirectory = findCsvFilePathsInDirectory(refFile.toPath());
+        } else {
+            throw new IllegalStateException("ne comprend pas de quel type est " + refFile);
+        }
+        commands = csvFilePathsInDirectory.stream()
+                .map(path -> newUploadReferenceCommand(refType, path.toFile()))
+                .collect(Collectors.toList());
+    } else {
+        logError("le fichier %s n’existe pas, on ignore l’import du référentiel %s".formatted(refFile, refType));
+        commands = Collections.emptyList();
+    }
+    return commands;
+}
+
+private Command newUploadReferenceCommand(String refType, File refFile) {
+    return new Command() {
+        @Override
+        String getDescription() {
+            return "Téléversement de %s pour alimenter le référentiel %s".formatted(refFile, refType);
+        }
+
+        @Override
+        ClassicHttpRequest getRequest(UriFactory uriFactory) {
+            HttpPost httpPost = new HttpPost(uriFactory.forUploadingReference(refType));
+            FileBody refFileBody = new FileBody(refFile);
+            HttpEntity reqEntity = MultipartEntityBuilder.create()
+                    .addPart("file", refFileBody)
+                    .build();
+            httpPost.setEntity(reqEntity);
+            return httpPost;
+        }
+
+        @Override
+        Void handleResponse(ClassicHttpResponse response) {
+            switch (response.getCode()) {
+                case HttpURLConnection.HTTP_CREATED -> {
+                    String message = "import de " + refFile + " terminé";
+                    log(message);
+                }
+                case HttpURLConnection.HTTP_BAD_REQUEST -> {
+                    List<CsvRowValidationCheckResult> csvRowValidationCheckResults =
+                            parseJsonInResponseBody(
+                                    response,
+                                    new TypeReference<List<CsvRowValidationCheckResult>>() {}
+                            );
+                    logError(csvRowValidationCheckResults.toString());
+                }
+                default -> fail(
+                        "%d en code de retour HTTP inattendu à l’import du référentiel %s avec le fichier %s"
+                                .formatted(response.getCode(), refType, refFile)
+                );
+            }
+            return null;
+        }
+    };
+}
+
+private SortedSet<Path> findCsvFilePathsInDirectory(Path directory) {
+    try {
+        AccumulatorPathVisitor accumulatorPathVisitor = new AccumulatorPathVisitor(Counters.longPathCounters());
+        Files.walkFileTree(directory, accumulatorPathVisitor);
+        SortedSet<Path> csvFilePathsInDirectory = accumulatorPathVisitor.getFileList().stream()
+                .filter(path -> path.getFileName().toString().endsWith(".csv"))
+                .collect(Collectors.toCollection(TreeSet::new));
+        return Collections.unmodifiableSortedSet(csvFilePathsInDirectory);
+    } catch (IOException e) {
+        throw new RuntimeException(e);
+    }
+}
+
+private void fail(String message) {
+    logError(message);
+    System.exit(1);
+}
+
+private static void logError(String string) {
+    System.err.println(string);
+}
+
+private static void log(String message) {
+    System.out.println(message);
+}
+
+private <T> T parseJsonInResponseBody(ClassicHttpResponse response, TypeReference<T> valueTypeRef) {
+    try (InputStream inputStream = response.getEntity().getContent()) {
+        T parsingResult = new ObjectMapper()
+                .readValue(
+                        inputStream,
+                        valueTypeRef
+                );
+        return parsingResult;
+    } catch (DatabindException e) {
+        throw new RuntimeException(e);
+    } catch (StreamReadException e) {
+        throw new RuntimeException(e);
+    } catch (IOException e) {
+        throw new RuntimeException(e);
+    }
+}
+
+record CsvRowValidationCheckResult(
+        int lineNumber,
+        ValidationCheckResult validationCheckResult
+) {
+}
+
+record ValidationCheckResult(
+        ValidationLevel level,
+        ValidationMessage message,
+        Map<String, Object> messageParams,
+        String target
+) {
+}
+
+enum ValidationLevel {
+    SUCCESS, WARN, ERROR;
+}
+
+enum ValidationMessage {
+    unexpectedHeaderColumn,
+    headerColumnPatternNotMatching,
+    unexpectedTokenCount,
+    invalidHeaders,
+    duplicatedHeaders,
+    emptyHeader
+}
+
+/**
+ * Les routes disponibles sur le serveur.
+ */
+record UriFactory(URI instanceUrl, String applicationName) {
+
+    private URI newUri(String endpoint) {
+        try {
+            return new URI("%s/api/v1/%s".formatted(instanceUrl, endpoint));
+        } catch (URISyntaxException e) {
+            throw new RuntimeException("ne devrait pas arriver", e);
+        }
+    }
+
+    URI forLogin() {
+        return newUri("login");
+    }
+
+    URI forUploadingReference(String refType) {
+        String endpoint = "applications/%s/references/%s".formatted(applicationName, refType);
+        return newUri(endpoint);
+    }
+
+    URI forUploadingData(String dataType) {
+        String endpoint = "applications/%s/data/%s".formatted(applicationName, dataType);
+        return newUri(endpoint);
+    }
+
+    URI forApplicationReferenceTypes(String applicationName) {
+        String endpoint = "applications/%s/references".formatted(applicationName);
+        return newUri(endpoint);
+    }
+
+    URI forApplicationDataTypes(String applicationName) {
+        String endpoint = "applications/%s/data".formatted(applicationName);
+        return newUri(endpoint);
+    }
+}
+
+/**
+ * Une étape du téléversement soit un fichier à téléverser, une requête HTTP et comment traiter la réponse.
+ */
+interface Command extends HttpClientResponseHandler<Void> {
+
+    String getDescription();
+
+    ClassicHttpRequest getRequest(UriFactory uriFactory);
+}
+
+/**
+ * Le contenu du fichier de configuration du client.
+ *
+ * @param instanceUrl l’adresse du serveur au format "http://hote:port"
+ * @param applicationName le nom de l’application
+ */
+record ClientConfiguration(URI instanceUrl, String applicationName) {
+
+}
diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
index f137c04ee..dd0c33130 100644
--- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
+++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
@@ -3174,6 +3174,9 @@ on test le dépôt d'un fichier récursif
                         .andExpect(request().asyncStarted())
                         .andReturn()))
                 .andExpect(result -> {
+//                    try (FileOutputStream output = new FileOutputStream("/tmp/test.zip")) {
+//                        IOUtils.write(result.getResponse().getContentAsByteArray(), output);
+//                    }
                     List<ZipEntry> entries = new ArrayList<>();
                     try (ZipInputStream zi = new ZipInputStream(new ByteArrayInputStream(result.getResponse().getContentAsByteArray()))) {
                         ZipEntry zipEntry;
@@ -3187,7 +3190,19 @@ on test le dépôt d'un fichier récursif
                             .toList();
                     Assertions.assertTrue(() -> entryNames.contains("OpenAdomClient.groovy"));
                     Assertions.assertTrue(() -> entryNames.contains("openAdom-client-configuration.json"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/"));
+                    Assertions.assertTrue(() -> entryNames.contains("LISEZ-MOI.txt"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/pem.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/sites.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/projet.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/themes.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/unites.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/especes.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/variables.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/type_de_sites.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/type_de_fichiers.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/site_theme_datatype.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/valeurs_qualitatives.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("references/variables_et_unites_par_types_de_donnees.csv"));
                 });
     }
 }
-- 
GitLab


From 0faceb49f91548956372f730358359a99ceac94f Mon Sep 17 00:00:00 2001
From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr>
Date: Thu, 13 Jun 2024 09:10:08 +0200
Subject: [PATCH 7/8] =?UTF-8?q?G=C3=A9n=C3=A8re=20des=20dossiers=20pour=20?=
 =?UTF-8?q?chaque=20type=20de=20donn=C3=A9es=20exp=C3=A9rimentales=20plut?=
 =?UTF-8?q?=C3=B4t=20qu'un=20fichier=20dans=20le=20kit=20de=20t=C3=A9l?=
 =?UTF-8?q?=C3=A9chargement?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../fr/inra/oresing/rest/OreSiService.java    |  4 ++--
 .../inra/oresing/rest/OreSiResourcesTest.java | 24 +++++++++----------
 2 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java
index cb758ca06..74ffab825 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiService.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java
@@ -1217,8 +1217,8 @@ public class OreSiService {
         zipOutputStream.write(readmeContent.getBytes(StandardCharsets.UTF_8));
         zipOutputStream.closeEntry();
         for (String reference : application.getConfiguration().dataDescription().keySet()) {
-            String referenceCsvFilePath = "references/%s.csv".formatted(reference);
-            zipOutputStream.putNextEntry(new ZipEntry(referenceCsvFilePath));
+            String dataCsvFilePath = "%s/".formatted(reference);
+            zipOutputStream.putNextEntry(new ZipEntry(dataCsvFilePath));
             zipOutputStream.closeEntry();
         }
     }
diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
index dd0c33130..59a76a25f 100644
--- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
+++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
@@ -3191,18 +3191,18 @@ on test le dépôt d'un fichier récursif
                     Assertions.assertTrue(() -> entryNames.contains("OpenAdomClient.groovy"));
                     Assertions.assertTrue(() -> entryNames.contains("openAdom-client-configuration.json"));
                     Assertions.assertTrue(() -> entryNames.contains("LISEZ-MOI.txt"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/pem.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/sites.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/projet.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/themes.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/unites.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/especes.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/variables.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/type_de_sites.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/type_de_fichiers.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/site_theme_datatype.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/valeurs_qualitatives.csv"));
-                    Assertions.assertTrue(() -> entryNames.contains("references/variables_et_unites_par_types_de_donnees.csv"));
+                    Assertions.assertTrue(() -> entryNames.contains("pem/"));
+                    Assertions.assertTrue(() -> entryNames.contains("sites/"));
+                    Assertions.assertTrue(() -> entryNames.contains("projet/"));
+                    Assertions.assertTrue(() -> entryNames.contains("themes/"));
+                    Assertions.assertTrue(() -> entryNames.contains("unites/"));
+                    Assertions.assertTrue(() -> entryNames.contains("especes/"));
+                    Assertions.assertTrue(() -> entryNames.contains("variables/"));
+                    Assertions.assertTrue(() -> entryNames.contains("type_de_sites/"));
+                    Assertions.assertTrue(() -> entryNames.contains("type_de_fichiers/"));
+                    Assertions.assertTrue(() -> entryNames.contains("site_theme_datatype/"));
+                    Assertions.assertTrue(() -> entryNames.contains("valeurs_qualitatives/"));
+                    Assertions.assertTrue(() -> entryNames.contains("variables_et_unites_par_types_de_donnees/"));
                 });
     }
 }
-- 
GitLab


From e83e5cb2ef6307f2b7f8f3cce426df72d2f16aa7 Mon Sep 17 00:00:00 2001
From: philippe tcheriatinsky <philippe.tcherniatinsky@inrae.fr>
Date: Thu, 13 Jun 2024 15:34:17 +0200
Subject: [PATCH 8/8] Modification de la section OA_fileName
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Modification des tests pour s'adapter à la nouvelle section

Modification tu test monsoere en utilisant un nom différent monsoresimple
---
 .../ConfigurationSchemaNode.java              |   5 +-
 .../SubmissionFileNameTypeExampleBuilder.java |  22 ++-
 .../configuration/type/FileNameType.java      |   4 +-
 .../configuration/type/StaticMapType.java     |   2 +-
 .../builder/SubmissionFileNameBuilder.java    |  34 ++---
 .../application/configuration/LtreeTest.java  |   4 +-
 .../ApplicationConfigurationServiceTest.java  |  16 +--
 .../inra/oresing/rest/OreSiResourcesTest.java | 125 ++++++++----------
 .../data/configuration/configuration.yaml     |   8 +-
 .../configuration/data.result.example.json    |   4 +-
 .../data/configuration/data.result.json       |   4 +-
 .../configuration/data.result.monsore.json    |   4 +-
 .../data/configuration/schemaExample.yaml     |   6 +-
 .../data/monsore/monsore-with-repository.yaml |   8 +-
 src/test/resources/data/monsore/monsore.yaml  |   2 +-
 .../data/validation/broken-fake-app.yaml      |   6 +-
 16 files changed, 123 insertions(+), 131 deletions(-)

diff --git a/src/main/java/fr/inra/oresing/domain/application/configuration/ConfigurationSchemaNode.java b/src/main/java/fr/inra/oresing/domain/application/configuration/ConfigurationSchemaNode.java
index ef4b2c995..e19cb150d 100644
--- a/src/main/java/fr/inra/oresing/domain/application/configuration/ConfigurationSchemaNode.java
+++ b/src/main/java/fr/inra/oresing/domain/application/configuration/ConfigurationSchemaNode.java
@@ -48,12 +48,13 @@ public class ConfigurationSchemaNode {
     public static final String OA_TOKEN = "OA_token";
     public static final String OA_AUTHORIZATION_SCOPES = "OA_authorizationScope";
     public static final String OA_AUTHORIZATION = "OA_authorizations";
-    public static final String OA_START_DATE = "OA_startDate";
-    public static final String OA_END_DATE = "OA_endDate";
+    public static final String OA_START_DATE_MATCH_PATTERN = "__START_DATE__";
+    public static final String OA_END_DATE_MATCH_PATTERN = "__END_DATE__";
     public static final String OA_FILE_PATTERN = "OA_filePattern";
     public static final String OA_COMPONENT = "OA_component";
     public static final String OA_TIME_SCOPE = "OA_timeScope";
     public static final String OA_REFERENCE_SCOPES = "OA_referenceScopes";
+    public static final String OA_MATCH_PATTERN_SCOPES = "OA_matchPatternScopes";
     public static final String OA_SUBMISSION_SCOPE = "OA_submissionScope";
     public static final String OA_FILE_NAME = "OA_fileName";
     public static final String OA_STRATEGY = "OA_strategy";
diff --git a/src/main/java/fr/inra/oresing/domain/application/configuration/examples/SubmissionFileNameTypeExampleBuilder.java b/src/main/java/fr/inra/oresing/domain/application/configuration/examples/SubmissionFileNameTypeExampleBuilder.java
index cb43cc0fd..39d99841d 100644
--- a/src/main/java/fr/inra/oresing/domain/application/configuration/examples/SubmissionFileNameTypeExampleBuilder.java
+++ b/src/main/java/fr/inra/oresing/domain/application/configuration/examples/SubmissionFileNameTypeExampleBuilder.java
@@ -10,27 +10,25 @@ public class SubmissionFileNameTypeExampleBuilder {
     public static final FileNameType DATA_FILE_NAME = buildFileName(
             new StringType("(.*)_(.*)_(.*).csv", true),
             new CollectionType.ArrayType<>(
-                    List.of(new StringType("dat_site", false)),
+                    List.of(
+                            new StringType("dat_site", false),
+                            new StringType(ConfigurationSchemaNode.OA_START_DATE_MATCH_PATTERN, false),
+                            new StringType(ConfigurationSchemaNode.OA_END_DATE_MATCH_PATTERN, false)
+                    ),
                     false,
                     true,
                     StringType.EMPTY_INSTANCE()
-            ),
-            new IntegerType(2),
-            new IntegerType(3)
-            );
+            )
+    );
 
     private static FileNameType buildFileName(
             StringType fileNamePattern,
-            CollectionType.ArrayType<StringType> referenceScopeType,
-            IntegerType startDate,
-            IntegerType endDate
+            CollectionType.ArrayType<StringType> referenceScopeType
     ) {
         return new FileNameType(
-                new HashMap<>(){{
+                new HashMap<>() {{
                     put(ConfigurationSchemaNode.OA_FILE_PATTERN, fileNamePattern);
-                    put(ConfigurationSchemaNode.OA_REFERENCE_SCOPES, referenceScopeType);
-                    put(ConfigurationSchemaNode.OA_START_DATE, startDate);
-                    put(ConfigurationSchemaNode.OA_END_DATE, endDate);
+                    put(ConfigurationSchemaNode.OA_MATCH_PATTERN_SCOPES, referenceScopeType);
                 }}
         );
     }
diff --git a/src/main/java/fr/inra/oresing/domain/application/configuration/type/FileNameType.java b/src/main/java/fr/inra/oresing/domain/application/configuration/type/FileNameType.java
index 684341768..dee15eb36 100644
--- a/src/main/java/fr/inra/oresing/domain/application/configuration/type/FileNameType.java
+++ b/src/main/java/fr/inra/oresing/domain/application/configuration/type/FileNameType.java
@@ -14,9 +14,7 @@ public record FileNameType(SectionBuilder sectionBuilder, Map<String, Configurat
                         new LabelDescription(ConfigurationSchemaNode.OA_FILE_PATTERN, StringType.EMPTY_INSTANCE())
                 )
                 .withOptionalSections(
-                        new LabelDescription(ConfigurationSchemaNode.OA_REFERENCE_SCOPES, StaticMapType.FILE_AUTHORIZATION_SCOPE().type),
-                        new LabelDescription(ConfigurationSchemaNode.OA_START_DATE, IntegerType.EMPTY_INSTANCE()),
-                        new LabelDescription(ConfigurationSchemaNode.OA_END_DATE, IntegerType.EMPTY_INSTANCE())
+                        new LabelDescription(ConfigurationSchemaNode.OA_MATCH_PATTERN_SCOPES, StaticMapType.FILE_MATCH_PATTERN_SCOPES().type)
                 );
     }
     public static FileNameType  EMPTY_INSTANCE(){
diff --git a/src/main/java/fr/inra/oresing/domain/application/configuration/type/StaticMapType.java b/src/main/java/fr/inra/oresing/domain/application/configuration/type/StaticMapType.java
index 5c3dd7bd4..f5bb6cc51 100644
--- a/src/main/java/fr/inra/oresing/domain/application/configuration/type/StaticMapType.java
+++ b/src/main/java/fr/inra/oresing/domain/application/configuration/type/StaticMapType.java
@@ -36,7 +36,7 @@ public class StaticMapType {
 
 
 
-    public static final StaticMapType FILE_AUTHORIZATION_SCOPE() {
+    public static final StaticMapType FILE_MATCH_PATTERN_SCOPES() {
         return new StaticMapType(
                 new CollectionType.ArrayType<StringType>(
                         List.of(),
diff --git a/src/main/java/fr/inra/oresing/rest/model/configuration/builder/SubmissionFileNameBuilder.java b/src/main/java/fr/inra/oresing/rest/model/configuration/builder/SubmissionFileNameBuilder.java
index 4b5951c04..ec54792ff 100644
--- a/src/main/java/fr/inra/oresing/rest/model/configuration/builder/SubmissionFileNameBuilder.java
+++ b/src/main/java/fr/inra/oresing/rest/model/configuration/builder/SubmissionFileNameBuilder.java
@@ -20,25 +20,29 @@ public record SubmissionFileNameBuilder(RootBuilder rootBuilder) {
         final String pattern = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_FILE_PATTERN))
                 .map(JsonNode::asText)
                 .orElseThrow(() -> new IllegalArgumentException("MISSING_PATTERN_FOR_FILENAME_SUBMISSION"));
-        final List<String> referenceScope = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_REFERENCE_SCOPES))
+        List<String> referenceScope = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_MATCH_PATTERN_SCOPES))
                 .map(j -> rootBuilder.getMapper().convertValue(j, List.class))
-                .orElse(null);
-        for (final String key : referenceScope){
-            if(!listReferenceScope.contains(key)) {
+                .orElse(List.of());
+        Integer startDate = -1;
+        Integer endDate = -1;
+        for (int i = 0; i < referenceScope.size(); i++) {
+            String key = referenceScope.get(i);
+            if (ConfigurationSchemaNode.OA_START_DATE_MATCH_PATTERN.equals(key)) {
+                startDate = i + 1;
+                continue;
+            }
+            if (ConfigurationSchemaNode.OA_END_DATE_MATCH_PATTERN.equals(key)) {
+                endDate = i + 1;
+                continue;
+            }
+            if (!listReferenceScope.contains(key)) {
                 rootBuilder.buildError(ConfigurationException.UNKNOWN_NAME_REFERENCE_SCOPE, Map.of(
-                                    "unknownAuthorizationScope", key,
-                                    "knownAuthorizationScope", listReferenceScope),
-                            "OA_submission > OA_fileName > OA_referenceScopes > %1$s".formatted(key));
+                                "unknownAuthorizationScope", key,
+                                "knownAuthorizationScope", listReferenceScope),
+                        "OA_submission > OA_fileName > OA_referenceScopes > %1$s".formatted(key));
             }
         }
-        final Integer startDate = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_START_DATE))
-                .map(j -> j.get(ConfigurationSchemaNode.OA_TOKEN))
-                .map(JsonNode::asInt)
-                .orElse(null);
-        final Integer endDate = Optional.ofNullable(filenameNode.get(ConfigurationSchemaNode.OA_END_DATE))
-                .map(j -> j.get(ConfigurationSchemaNode.OA_TOKEN))
-                .map(JsonNode::asInt)
-                .orElse(null);
+        referenceScope = referenceScope.stream().filter(element->!element.startsWith("__")).toList();
         return new Parsing<Submission.SubmissionFileNameParsing>(i18n, new Submission.SubmissionFileNameParsing(pattern, referenceScope, startDate, endDate));
     }
 }
\ No newline at end of file
diff --git a/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java b/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java
index d5282cf17..8974d6d93 100644
--- a/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java
+++ b/src/test/java/fr/inra/oresing/domain/application/configuration/LtreeTest.java
@@ -33,7 +33,7 @@ class LtreeTest {
         Assert.assertEquals(label, nk.getSql());
     }*/
     @ParameterizedTest(name = "{0} match an encodingString")
-    @ValueSource(strings = {"°","%",">","²","$","?","&","@","@","µ"})
+    @ValueSource(strings = {"°","%",">","²","$","?","&","@","°","µ"})
     void testIsEcodedString(String aSign){
         String aChar = Ltree.fromUnescapedString(aSign).getSql();
         Assert.assertTrue(Ltree.isEncodedString(aChar));
@@ -44,4 +44,4 @@ class LtreeTest {
         String aChar = Ltree.fromUnescapedString(aSign).getSql();
         Assert.assertFalse(Ltree.isEncodedString(aChar));
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/fr/inra/oresing/rest/ApplicationConfigurationServiceTest.java b/src/test/java/fr/inra/oresing/rest/ApplicationConfigurationServiceTest.java
index a8009269a..d5cf52615 100644
--- a/src/test/java/fr/inra/oresing/rest/ApplicationConfigurationServiceTest.java
+++ b/src/test/java/fr/inra/oresing/rest/ApplicationConfigurationServiceTest.java
@@ -1023,22 +1023,22 @@ public class ApplicationConfigurationServiceTest {
     @Test
     public void testUnknownNameAuthorizationScopeInFileNameInSubmission() {
         CONFIGURATION_INSTANCE.builder("testUnknownNameAuthorizationScopeInFileNameSubmission")
-                .withReplace("        OA_referenceScopes:\n" +
-                             "          - projet\n" +
-                             "          - site_bassin",
-                        "        OA_referenceScopes:\n" +
-                        "          - proj\n" +
-                        "          - site_bassin")
+                .withReplace("        OA_matchPatternScopes:\n" +
+                                "          - projet\n" +
+                                "          - site_bassin",
+                        "        OA_matchPatternScopes:\n" +
+                                "          - projet\n" +
+                                "          - site_bassine")
                 .test(errors -> {
                     assertEquals(1, errors.size() );
                     final ValidationError validationError = errors.get(0);
                     assertEquals(ConfigurationException.UNKNOWN_NAME_REFERENCE_SCOPE.getMessage(), validationError.getMessage());
-                    assertEquals("proj", validationError.getParam("unknownAuthorizationScope"));
+                    assertEquals("site_bassine", validationError.getParam("unknownAuthorizationScope"));
                     final Set<String> expected = Arrays.stream(new String[]{"site_bassin", "projet"})
                             .collect(Collectors.toSet());
                     final Set<String> given = (Set<String>)  validationError.getParam("knownAuthorizationScope");
                     assertEquals(expected, given);
-                    assertEquals("OA_submission > OA_fileName > OA_referenceScopes > proj", validationError.getParam(("path")));
+                    assertEquals("OA_submission > OA_fileName > OA_referenceScopes > site_bassine", validationError.getParam(("path")));
                 });
     }
 
diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
index 59a76a25f..3f93d9325 100644
--- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
+++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java
@@ -45,6 +45,7 @@ import org.json.JSONArray;
 import org.junit.Assert;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
@@ -54,8 +55,12 @@ import org.springframework.http.MediaType;
 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
 import org.springframework.mock.web.MockMultipartFile;
 import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.TestPropertySource;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.springframework.test.context.web.WebAppConfiguration;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.test.web.servlet.ResultActions;
@@ -222,17 +227,18 @@ public class OreSiResourcesTest {
     @Test
     @Tag("OTHERS_TEST")
     @Tag("SUITE")
+    @Tag("MONSOERE")
     public void addApplicationMonsore() throws Exception {
         final String appId;
 
-        CreateUserResult monsoereUser = createUserIfNotExists("monsore", "xxxxxxxx", "monsore@inrae.fr");
+        CreateUserResult monsoereUser = createUserIfNotExists("monsoresimple", "xxxxxxxx", "monsoresimple@inrae.fr");
         setToActive(monsoereUser.userId());
         final UUID monsoreUserId = monsoereUser.userId();
         OreSiUser publicUser = userRepository.findByLogin("_public_").orElse(null);
         assert publicUser != null;
         UUID publicUserId = publicUser.getId();
         final Cookie monsoreCookie = mockMvc.perform(post("/api/v1/login")
-                        .param("login", "monsore")
+                        .param("login", "monsoresimple")
                         .param("password", "xxxxxxxx"))
                 .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME);
         final CreateUserResult withRightsUserResult = createUserIfNotExists("withrigths", "xxxxxxxx", "withrigths@inrae.fr");
@@ -245,41 +251,21 @@ public class OreSiResourcesTest {
 
         URL resource = getClass().getResource(Fixtures.getMonsoreApplicationConfigurationResourceName());
         try (final InputStream in = Objects.requireNonNull(resource).openStream()) {
-            final MockMultipartFile configuration = new MockMultipartFile("file", "monsore.yaml", "text/plain", in);
+            final MockMultipartFile configuration = new MockMultipartFile("file", "monsoresimple.yaml", "text/plain", in);
 
             // on n'a pas le droit de creer de nouvelle application
-            Exception loadApplicationException = fixtures.loadApplicationWithError(
-                    configuration,
-                    monsoreCookie,
-                    "monsore"
-            );
-
-            switch (loadApplicationException) {
-                case NotApplicationCreatorRightsException notApplicationCreatorRightsException -> {
-                    Assertions.assertEquals("monsore", notApplicationCreatorRightsException.getApplicationName());
-                    addUserRightCreateApplication(monsoreUserId, "monsore");
-                }
-                case NoSuchApplicationException noSuchApplicationException -> {
-                    addUserRightCreateApplication(monsoreUserId, "monsore");
-
-                }
-                case JsonParseException jsonParseException -> {
-                    log.debug("******************jsonparseexception*****************");
-                    jsonParseException.getMessage();
-                    log.debug("******************jsonparseexception*****************");
-                    try (InputStream stream = resource.openStream();
-                         BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
-                        char[] buffer = new char[100]; // Changer cette valeur selon le nombre de caractères souhaités
-                        int n = reader.read(buffer);
-                        System.out.println(new String(buffer, 0, n));
-                    log.debug("******************jsonparseexception*****************");     
-                    }
-                    addUserRightCreateApplication(monsoreUserId, "monsore");
-
-                }
-                default -> throw loadApplicationException;
-            }
-            final MvcResult resultApplication = fixtures.loadApplication(configuration, monsoreCookie, "monsore", "");
+            NotApplicationCreatorRightsException resolvedException =
+                    (NotApplicationCreatorRightsException) fixtures.loadApplicationWithError(
+                            configuration,
+                            monsoreCookie,
+                            "monsoresimple"
+                    );
+            addUserRightCreateApplication(monsoreUserId, "monsoresimple");
+            assert resolvedException != null;
+            Assertions.assertEquals("monsoresimple", resolvedException.getApplicationName());
+            addUserRightCreateApplication(monsoreUserId, "monsoresimple");
+
+            final MvcResult resultApplication = fixtures.loadApplication(configuration, monsoreCookie, "monsoresimple", "");
             appId = fixtures.getIdFromApplicationResult(resultApplication);
         } catch (final Throwable e) {
             e.printStackTrace();
@@ -299,7 +285,7 @@ public class OreSiResourcesTest {
         final ApplicationResult applicationResult = (ApplicationResult) jsonRowMapper.readValue(response, ApplicationResult.class);
 
         Assertions.assertEquals("Fichier de test de l'application brokenADOM", applicationResult.comment());
-        Assertions.assertEquals("monsore", applicationResult.name());
+        Assertions.assertEquals("monsoresimple", applicationResult.name());
         Assert.assertEquals(
                 new TreeSet<>(Set.of("themes", "especes", "site_theme_datatype", "variables", "type_de_sites", "unites", "projet", "valeurs_qualitatives", "type_de_fichiers", "variables_et_unites_par_types_de_donnees")),
                 new TreeSet<>(applicationResult.references().keySet())
@@ -312,7 +298,7 @@ public class OreSiResourcesTest {
                 final MockMultipartFile refFile = new MockMultipartFile("file", e.getValue(), "text/plain", refStream);
 
 
-                response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/{refType}", e.getKey())
+                response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/{refType}", e.getKey())
                                 .file(refFile)
                                 .cookie(monsoreCookie))
                         .andExpect(status().isCreated())
@@ -323,7 +309,7 @@ public class OreSiResourcesTest {
             }
         }
         //test de la reference especetoTrim
-        final String getEspecesResponse = mockMvc.perform(get("/api/v1/applications/monsore/data/especes/json")
+        final String getEspecesResponse = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/especes/json")
                         .contentType(MediaType.APPLICATION_JSON)
                         .cookie(monsoreCookie))
                 .andExpect(status().isOk())
@@ -339,7 +325,7 @@ public class OreSiResourcesTest {
             for (final Map.Entry<String, String> e : Fixtures.getMonsoreReferentielFiles().entrySet()) {
                 try (final InputStream refStream = getClass().getResourceAsStream(e.getValue())) {
                     final MockMultipartFile refFile = new MockMultipartFile("file", e.getValue(), "text/plain", refStream);
-                    response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/{refType}", e.getKey())
+                    response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/{refType}", e.getKey())
                                     .file(refFile)
                                     .cookie(monsoreCookie))
                             .andDo(result -> {
@@ -367,7 +353,7 @@ public class OreSiResourcesTest {
                             .cookie(monsoreCookie))
                     .andExpect(status().isOk());
 
-            final String getReferencesResponse = mockMvc.perform(get("/api/v1/applications/monsore/data/sites/json")
+            final String getReferencesResponse = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/sites/json")
                             .contentType(MediaType.APPLICATION_JSON)
                             .cookie(monsoreCookie))
                     .andExpect(status().isOk())
@@ -376,7 +362,7 @@ public class OreSiResourcesTest {
                     .andReturn().getResponse().getContentAsString();
 
             mockMvc.perform(
-                            asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsore/data/sites/csv")
+                            asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsoresimple/data/sites/csv")
                                             .contentType(MediaType.APPLICATION_OCTET_STREAM)
                                             .cookie(monsoreCookie))
                                     .andExpect(status().isOk())
@@ -408,7 +394,7 @@ public class OreSiResourcesTest {
 
         try (final InputStream refStream = Objects.requireNonNull(resource).openStream()) {
             final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", refStream);
-            response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/{refType}", "pem")
+            response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/{refType}", "pem")
                             .file(refFile)
                             .cookie(monsoreCookie))
                     .andExpect(status().isCreated())
@@ -423,7 +409,7 @@ public class OreSiResourcesTest {
         try (final InputStream refStream = Objects.requireNonNull(resource).openStream()) {
             final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", refStream);
             // sans droit on ne peut pas
-            response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem")
+            response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem")
                             .file(refFile)
                             .cookie(withRigthsCookie))
                     .andDo(result -> {
@@ -433,7 +419,7 @@ public class OreSiResourcesTest {
                         }
                     })
                     .andExpect(status().is4xxClientError())
-                    .andExpect(content().string("application inconnue 'monsore'"))
+                    .andExpect(content().string("application inconnue 'monsoresimple'"))
                     .andReturn().getResponse().getContentAsString();
             //ajout de droits withRignesthsUserId
             if (true) {
@@ -442,7 +428,7 @@ public class OreSiResourcesTest {
             String jsonRightsForMonsoere = setJsonRightsForMonsoere(monsoreCookie, withRigthsUserId, OperationType.publication.name(), "pem");
             String jsonRightsForMonsoereId = JsonPath.parse(jsonRightsForMonsoere).read("$.authorizationId");
             //avec les droits on peut publier
-            response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem")
+            response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem")
                             .file(refFile)
                             .cookie(monsoreCookie/*withRigthsCookie*/))
                     .andDo(result -> {
@@ -457,7 +443,7 @@ public class OreSiResourcesTest {
             log.debug(StringUtils.abbreviate(response, 50));
 
             //sans droit on ne peut pas
-            response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem")
+            response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem")
                             .file(refFile)
                             .cookie(lambdaCookie))
                     .andExpect(status().is4xxClientError())
@@ -468,18 +454,18 @@ public class OreSiResourcesTest {
 
 
             //avec les droits public on peut publier même sans droit
-            response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem")
+            response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem")
                             .file(refFile)
                             .cookie(lambdaCookie))
                     .andExpect(status().is2xxSuccessful())
                     .andReturn().getResponse().getContentAsString();
 
             // on supprime les droits public
-            mockMvc.perform(delete("/api/v1/applications/monsore/authorization/" + publicRightsId)
+            mockMvc.perform(delete("/api/v1/applications/monsoresimple/authorization/" + publicRightsId)
                             .cookie(monsoreCookie))
                     .andExpect(status().is2xxSuccessful());
 
-            response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem")
+            response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem")
                             .file(refFile)
                             .cookie(lambdaCookie))
                     .andExpect(status().is4xxClientError())
@@ -491,7 +477,7 @@ public class OreSiResourcesTest {
         resource = getClass().getResource(Fixtures.getPemDataToTrimResourceName());
         try (final InputStream refStream = Objects.requireNonNull(resource).openStream()) {
             final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", refStream);
-            response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem")
+            response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem")
                             .file(refFile)
                             .cookie(withRigthsCookie))
                     .andExpect(status().is2xxSuccessful())
@@ -499,7 +485,7 @@ public class OreSiResourcesTest {
             final String actualJson = getPemData(monsoreCookie);
             log.debug(StringUtils.abbreviate(actualJson, 50));
         }
-        final String contentAsString = mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json")
+        final String contentAsString = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json")
                         .cookie(monsoreCookie))
                 .andExpect(jsonPath("$.rows[*].values" +
                                 "[?(@.projet.value=='projet_atlantique' )]" +
@@ -528,7 +514,7 @@ public class OreSiResourcesTest {
                                 "%s"
                                 """, s))
                         .collect(Collectors.joining(",")));
-        mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json")
+        mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json")
                         .param("downloadDatasetQuery", query)
                         .cookie(monsoreCookie))
                 .andExpect(status().is2xxSuccessful())
@@ -543,7 +529,7 @@ public class OreSiResourcesTest {
             final String wrongData = data.replace("plateforme", "entete_inconnu");
             final byte[] bytes = wrongData.getBytes(StandardCharsets.UTF_8);
             final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", bytes);
-            response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem")
+            response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem")
                             .file(refFile)
                             .cookie(monsoreCookie))
                     .andExpect(status().isBadRequest())
@@ -554,7 +540,7 @@ public class OreSiResourcesTest {
         }
 
         // list des types de data
-        response = mockMvc.perform(get("/api/v1/applications/monsore/data")
+        response = mockMvc.perform(get("/api/v1/applications/monsoresimple/data")
                         .cookie(monsoreCookie))
                 .andExpect(status().isOk())
                 .andReturn().getResponse().getContentAsString();
@@ -562,7 +548,7 @@ public class OreSiResourcesTest {
         log.debug(StringUtils.abbreviate(response, 50));
 
         {
-            final String expectedJson = Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsore/compare/export.json")), Charsets.UTF_8);
+            final String expectedJson = Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsoresimple/compare/export.json")), Charsets.UTF_8);
             final JSONArray jsonArray = new JSONArray(expectedJson);
             final List<String> list = new ArrayList<>();
 
@@ -613,8 +599,8 @@ public class OreSiResourcesTest {
                   ]
                 }""";
         {
-            Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsore/compare/export.json")), Charsets.UTF_8);
-            final String actualJson = mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json")
+            Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsoresimple/compare/export.json")), Charsets.UTF_8);
+            final String actualJson = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json")
                             .cookie(monsoreCookie)
                             .accept(MediaType.APPLICATION_JSON))
                     .andExpect(status().isOk())
@@ -629,8 +615,8 @@ public class OreSiResourcesTest {
 
         // restitution de data csv
         {
-            final String expectedCsv = Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsore/compare/export.csv")), Charsets.UTF_8);
-            mockMvc.perform(asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsore/data/pem/zip")
+            final String expectedCsv = Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsoresimple/compare/export.csv")), Charsets.UTF_8);
+            mockMvc.perform(asyncDispatch(mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/zip")
                                     .cookie(monsoreCookie)
                                     .accept(MediaType.APPLICATION_OCTET_STREAM_VALUE))
                             .andExpect(status().isOk())
@@ -652,7 +638,7 @@ public class OreSiResourcesTest {
                     .replace("projet_manche", "projet_manch")
                     .replace("projet_atlantique", "projet_atlantiqu");
             final MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", invalidCsv.getBytes(StandardCharsets.UTF_8));
-            response = mockMvc.perform(multipart("/api/v1/applications/monsore/data/pem")
+            response = mockMvc.perform(multipart("/api/v1/applications/monsoresimple/data/pem")
                             .file(refFile)
                             .cookie(monsoreCookie))
                     .andExpect(status().is4xxClientError())
@@ -669,31 +655,31 @@ public class OreSiResourcesTest {
             Assertions.assertFalse(() -> finalResponse.contains("142"), "L'erreur doit être tronquée");
             Assertions.assertFalse(() -> finalResponse.contains("143"), "L'erreur doit être tronquée");
         }
-        String getMonsoere = mockMvc.perform(get("/api/v1/applications/monsore")
+        String getMonsoere = mockMvc.perform(get("/api/v1/applications/monsoresimple")
                         .cookie(authCookie)
                         .accept(MediaType.APPLICATION_JSON))
                 .andReturn().getResponse().getContentAsString();
-        String getSites = mockMvc.perform(get("/api/v1/applications/monsore/data/sites")
+        String getSites = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/sites")
                         .cookie(monsoreCookie)
                         .accept(MediaType.APPLICATION_JSON))
                 .andExpect(status().is2xxSuccessful())
                 .andReturn().getResponse().getContentAsString();
-        String getTypeSites = mockMvc.perform(get("/api/v1/applications/monsore/data/type_de_sites")
+        String getTypeSites = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/type_de_sites")
                         .cookie(monsoreCookie)
                         .accept(MediaType.APPLICATION_JSON))
                 .andExpect(status().is2xxSuccessful())
                 .andReturn().getResponse().getContentAsString();
-        String getProjet = mockMvc.perform(get("/api/v1/applications/monsore/data/projet")
+        String getProjet = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/projet")
                         .cookie(monsoreCookie)
                         .accept(MediaType.APPLICATION_JSON))
                 .andExpect(status().is2xxSuccessful())
                 .andReturn().getResponse().getContentAsString();
-        String getPem = mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json")
+        String getPem = mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json")
                         .cookie(monsoreCookie)
                         .accept(MediaType.APPLICATION_JSON))
                 .andExpect(status().is2xxSuccessful())
                 .andReturn().getResponse().getContentAsString();
-        mockMvc.perform(delete("/api/v1/applications/monsore/data/pem")
+        mockMvc.perform(delete("/api/v1/applications/monsoresimple/data/pem")
                         .param("downloadDatasetQuery", filter)
                         .cookie(monsoreCookie))
                 .andExpect(status().is2xxSuccessful())
@@ -735,7 +721,7 @@ public class OreSiResourcesTest {
                     .andExpect(jsonPath("$.rows", Matchers.not(Matchers.empty())));*/
 
         filter = filterPattern.formatted(null, "{\"from\":\"15\",\"to\":\"15\"}");
-        mockMvc.perform(get("/api/v1/applications/monsore/data/pem/json")
+        mockMvc.perform(get("/api/v1/applications/monsoresimple/data/pem/json")
                         .cookie(monsoreCookie)
                         .param("downloadDatasetQuery", filter)
                         .accept(MediaType.APPLICATION_JSON))
@@ -751,12 +737,12 @@ public class OreSiResourcesTest {
                 "1984,1,6",
                 monsoreCookie);
 
-        String getAuthorizations = mockMvc.perform(get("/api/v1/applications/monsore/authorization")
+        String getAuthorizations = mockMvc.perform(get("/api/v1/applications/monsoresimple/authorization")
                         .cookie(withRigthsCookie)
                         .accept(MediaType.APPLICATION_JSON))
                 .andExpect(status().is2xxSuccessful())
                 .andReturn().getResponse().getContentAsString();
-        String getGrantable = mockMvc.perform(get("/api/v1/applications/monsore/grantable")
+        String getGrantable = mockMvc.perform(get("/api/v1/applications/monsoresimple/grantable")
                         .cookie(withRigthsCookie)
                         .accept(MediaType.APPLICATION_JSON))
                 .andExpect(status().is2xxSuccessful())
@@ -933,6 +919,7 @@ public class OreSiResourcesTest {
     @Test
     @Tag("OTHERS_TEST")
     @Tag("SUITE")
+    @Tag("MONSOERE")
     public void addApplicationMonsoreWithRepository() throws Exception {
         URL resource = getClass().getResource(Fixtures.getMonsoreApplicationConfigurationWithRepositoryResourceName());
         final String oirFilesUUID;
diff --git a/src/test/resources/data/configuration/configuration.yaml b/src/test/resources/data/configuration/configuration.yaml
index 67bc55cd6..44865d3eb 100644
--- a/src/test/resources/data/configuration/configuration.yaml
+++ b/src/test/resources/data/configuration/configuration.yaml
@@ -717,9 +717,11 @@ OA_data:
       OA_strategy: OA_VERSIONING
       OA_fileName:
         OA_filePattern: "(.*)_(.*)_(.*)_(.*).csv"
-        OA_referenceScopes: [projet, site_bassin]
-        OA_startDate: 3
-        OA_endDate: 4
+        OA_matchPatternScopes:
+          - projet
+          - site_bassin
+          - __START_DATE__  #optional
+          - __END_DATE__  #optional
       OA_submissionScope:
         OA_referenceScopes:
           - OA_i18n:
diff --git a/src/test/resources/data/configuration/data.result.example.json b/src/test/resources/data/configuration/data.result.example.json
index 5b82415cd..d590ffc7e 100644
--- a/src/test/resources/data/configuration/data.result.example.json
+++ b/src/test/resources/data/configuration/data.result.example.json
@@ -1311,8 +1311,8 @@
       "fileNameParsing" : {
         "pattern" : "(.*)_(.*)_(.*).csv",
         "authorizationScopes" : [ "dat_site" ],
-        "startDate" : null,
-        "endDate" : null
+        "startDate" : 2,
+        "endDate" : 3
       },
       "submissionScope" : {
         "referenceScopes" : [ {
diff --git a/src/test/resources/data/configuration/data.result.json b/src/test/resources/data/configuration/data.result.json
index f0d97494d..121d20efd 100644
--- a/src/test/resources/data/configuration/data.result.json
+++ b/src/test/resources/data/configuration/data.result.json
@@ -3267,8 +3267,8 @@
       "fileNameParsing" : {
         "pattern" : "(.*)_(.*)_(.*)_(.*).csv",
         "authorizationScopes" : [ "projet", "site_bassin" ],
-        "startDate" : null,
-        "endDate" : null
+        "startDate" : 3,
+        "endDate" : 4
       },
       "submissionScope" : {
         "referenceScopes" : [ {
diff --git a/src/test/resources/data/configuration/data.result.monsore.json b/src/test/resources/data/configuration/data.result.monsore.json
index 93589b30b..98fd92b22 100644
--- a/src/test/resources/data/configuration/data.result.monsore.json
+++ b/src/test/resources/data/configuration/data.result.monsore.json
@@ -2269,8 +2269,8 @@
       "fileNameParsing" : {
         "pattern" : "(.*)!(.*)!(.*)!(.*).csv",
         "authorizationScopes" : [ "projet", "chemin" ],
-        "startDate" : null,
-        "endDate" : null
+        "startDate" : 3,
+        "endDate" : 4
       },
       "submissionScope" : {
         "referenceScopes" : [ {
diff --git a/src/test/resources/data/configuration/schemaExample.yaml b/src/test/resources/data/configuration/schemaExample.yaml
index 92d20ae6d..22abb091c 100644
--- a/src/test/resources/data/configuration/schemaExample.yaml
+++ b/src/test/resources/data/configuration/schemaExample.yaml
@@ -621,10 +621,10 @@ OA_data:   #optional
           OA_component: dat_date_heure  #mandatory
       OA_fileName:   #optional
         OA_filePattern: (.*)_(.*)_(.*).csv  #mandatory
-        OA_startDate: 2  #optional
-        OA_referenceScopes:   #optional
+        OA_matchPatternScopes:   #optional
           - dat_site  #optional
-        OA_endDate: 3  #optional
+          - __START_DATE__  #optional
+          - __END_DATE__  #optional
     OA_authorizations:   #mandatory
       OA_authorizationScope:   #optional
         - dat_site  #optional
diff --git a/src/test/resources/data/monsore/monsore-with-repository.yaml b/src/test/resources/data/monsore/monsore-with-repository.yaml
index e8f0caf2c..652cdc4c9 100644
--- a/src/test/resources/data/monsore/monsore-with-repository.yaml
+++ b/src/test/resources/data/monsore/monsore-with-repository.yaml
@@ -654,9 +654,11 @@ OA_data:
       OA_strategy: OA_VERSIONING
       OA_fileName:
         OA_filePattern: "(.*)!(.*)!(.*)!(.*).csv"
-        OA_referenceScopes: [projet, chemin]
-        OA_startDate: 3
-        OA_endDate: 4
+        OA_matchPatternScopes:
+          - projet
+          - chemin
+          - __START_DATE__  #optional
+          - __END_DATE__  #optional
       OA_submissionScope:
         OA_referenceScopes:
           - OA_component: projet
diff --git a/src/test/resources/data/monsore/monsore.yaml b/src/test/resources/data/monsore/monsore.yaml
index 5f87bb790..27ca85ef0 100644
--- a/src/test/resources/data/monsore/monsore.yaml
+++ b/src/test/resources/data/monsore/monsore.yaml
@@ -5,7 +5,7 @@ OA_application:
     fr: SOERE mon SOERE
     en: SOERE my SOERE
   OA_comment: Fichier de test de l'application brokenADOM
-  OA_name: monsore
+  OA_name: monsoresimple
   OA_version: 3.0.1
 OA_tags:
   context:
diff --git a/src/test/resources/data/validation/broken-fake-app.yaml b/src/test/resources/data/validation/broken-fake-app.yaml
index b525af605..e557d18b7 100644
--- a/src/test/resources/data/validation/broken-fake-app.yaml
+++ b/src/test/resources/data/validation/broken-fake-app.yaml
@@ -353,11 +353,11 @@ OA_data:
       OA_strategy: OA_VERSIONING
       OA_fileName:
         OA_filePattern: "(.*)_(.*)_(.*)_(.*).csv"
-        OA_referenceScopes:
+        OA_matchPatternScopes:
           - projet
           - site_bassin
-        OA_startDate: 3
-        OA_endDate: 4
+          - __START_DATE__  #optional
+          - __END_DATE__  #optional
       OA_submissionScope:
         OA_referenceScopes:
           - OA_i18n:
-- 
GitLab