From 9dca1232c9d8a47e7b651a920ac5e879fca8698f Mon Sep 17 00:00:00 2001
From: Brendan Le Ny <bleny@codelutin.com>
Date: Thu, 3 Feb 2022 16:30:36 +0100
Subject: [PATCH 1/3] =?UTF-8?q?=C3=89chappement=20des=20composants=20des?=
 =?UTF-8?q?=20cl=C3=A9s=20(ltree)=20en=20utilisant=20le=20nom=20du=20carac?=
 =?UTF-8?q?t=C3=A8re=20unicode?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../fr/inra/oresing/rest/OreSiService.java    | 46 ++++++++++++++-----
 1 file changed, 35 insertions(+), 11 deletions(-)

diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java
index aa3c7c380..e2301459c 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiService.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java
@@ -98,22 +98,46 @@ public class OreSiService {
     private RelationalService relationalService;
 
     public static String escapeKeyComponent(String key) {
-        String toEscape = StringUtils.stripAccents(key.toLowerCase());
-        String escaped = StringUtils.remove(
-                RegExUtils.replaceAll(
-                        StringUtils.replace(toEscape, " ", "_"),
-                        "[^a-z0-9_]",
-                        ""
-                ), "-"
-        );
+        String lowerCased = key.toLowerCase();
+        String withAccentsStripped = StringUtils.stripAccents(lowerCased);
+        String toEscape = StringUtils.replace(withAccentsStripped, " ", "_");
+        String escaped = toEscape.chars()
+                .mapToObj(x -> (char) x)
+                .map(OreSiService::escapeSymbolFromKeyComponent)
+                .collect(Collectors.joining());
         checkNaturalKeySyntax(escaped);
         return escaped;
     }
 
     public static void checkNaturalKeySyntax(String keyComponent) {
-        if (keyComponent.isEmpty())
-            Preconditions.checkState(keyComponent.matches("[a-z0-9_]+"), "La clé naturelle ne peut être vide. vérifier le nom des colonnes.");
-        Preconditions.checkState(keyComponent.matches("[a-z0-9_]+"), keyComponent + " n'est pas un élément valide pour une clé naturelle");
+        Preconditions.checkState(keyComponent.length() <= 256, "Un label dans un ltree ne peut pas être plus long que 256 caractères à cause de PG");
+        Preconditions.checkState(!keyComponent.isEmpty(), "La clé naturelle ne peut être vide. vérifier le nom des colonnes.");
+        Preconditions.checkState(keyComponent.matches("[a-zA-Z0-9_]+"), keyComponent + " n'est pas un élément valide pour une clé naturelle");
+    }
+
+    private static String escapeSymbolFromKeyComponent(Character aChar) {
+        String escapedChar;
+        if (characterCanBeUsedInLtreeLabel(aChar)) {
+            escapedChar = String.valueOf(aChar);
+        } else {
+            escapedChar = RegExUtils.replaceAll(
+                    Character.getName(aChar),
+                    "[^a-zA-Z0-9_]",
+                    ""
+            );
+        }
+        return escapedChar;
+    }
+
+    /**
+     * D'après la documentation PostgreSQL sur ltree
+     *
+     * <blockquote>
+     *     A label is a sequence of alphanumeric characters and underscores (for example, in C locale the characters A-Za-z0-9_ are allowed). Labels must be less than 256 characters long.
+     * </blockquote>
+     */
+    private static boolean characterCanBeUsedInLtreeLabel(Character aChar) {
+        return Character.isAlphabetic(aChar) || Character.isDigit(aChar) || '_' == aChar;
     }
 
     private void checkHierarchicalKeySyntax(String compositeKey) {
-- 
GitLab


From cf75f22ddaf741b168ac95bd2ea64f9826f32271 Mon Sep 17 00:00:00 2001
From: Brendan Le Ny <bleny@codelutin.com>
Date: Fri, 4 Feb 2022 20:05:23 +0100
Subject: [PATCH 2/3] Introduit une classe Ltree

---
 .../fr/inra/oresing/persistence/Ltree.java    | 94 +++++++++++++++++++
 .../fr/inra/oresing/rest/OreSiService.java    | 79 ++++------------
 .../inra/oresing/persistence/LtreeTest.java   | 15 +++
 3 files changed, 125 insertions(+), 63 deletions(-)
 create mode 100644 src/main/java/fr/inra/oresing/persistence/Ltree.java
 create mode 100644 src/test/java/fr/inra/oresing/persistence/LtreeTest.java

diff --git a/src/main/java/fr/inra/oresing/persistence/Ltree.java b/src/main/java/fr/inra/oresing/persistence/Ltree.java
new file mode 100644
index 000000000..8caaf2ebb
--- /dev/null
+++ b/src/main/java/fr/inra/oresing/persistence/Ltree.java
@@ -0,0 +1,94 @@
+package fr.inra.oresing.persistence;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import lombok.Value;
+import org.apache.commons.lang3.CharUtils;
+import org.apache.commons.lang3.RegExUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.stream.Collectors;
+
+@Value
+public class Ltree {
+
+    /**
+     * Déliminateur entre les différents niveaux d'un ltree postgresql.
+     * <p>
+     * https://www.postgresql.org/docs/current/ltree.html
+     */
+    public static final String SEPARATOR = ".";
+
+    String sql;
+
+    private Ltree(String sql) {
+        this.sql = sql;
+    }
+
+    public static Ltree fromSql(String sql) {
+        checkSyntax(sql);
+        return new Ltree(sql);
+    }
+
+    public static String escapeLabel(String key) {
+        String lowerCased = key.toLowerCase();
+        String withAccentsStripped = StringUtils.stripAccents(lowerCased);
+        String toEscape = StringUtils.replace(withAccentsStripped, " ", "_");
+        String escaped = toEscape.chars()
+                .mapToObj(x -> (char) x)
+                .map(Ltree::escapeSymbolFromKeyComponent)
+                .collect(Collectors.joining());
+        checkLabelSyntax(escaped);
+        return escaped;
+    }
+
+    public static void checkLabelSyntax(String keyComponent) {
+        Preconditions.checkState(keyComponent.length() <= 256, "Un label dans un ltree ne peut pas être plus long que 256 caractères à cause de PG");
+        Preconditions.checkState(!keyComponent.isEmpty(), "La clé naturelle ne peut être vide. vérifier le nom des colonnes.");
+        Preconditions.checkState(keyComponent.matches("[a-zA-Z0-9_]+"), keyComponent + " n'est pas un élément valide pour une clé naturelle");
+    }
+
+    private static String escapeSymbolFromKeyComponent(Character aChar) {
+        String escapedChar;
+        if (characterCanBeUsedInLabel(aChar)) {
+            escapedChar = CharUtils.toString(aChar);
+        } else {
+            escapedChar = RegExUtils.replaceAll(
+                    Character.getName(aChar),
+                    "[^a-zA-Z0-9_]",
+                    ""
+            );
+        }
+        return escapedChar;
+    }
+
+    /**
+     * D'après la documentation PostgreSQL sur ltree
+     *
+     * <blockquote>
+     *     A label is a sequence of alphanumeric characters and underscores (for example, in C locale the characters A-Za-z0-9_ are allowed). Labels must be less than 256 characters long.
+     * </blockquote>
+     */
+    private static boolean characterCanBeUsedInLabel(Character aChar) {
+        return CharUtils.isAsciiAlphanumeric(aChar) || '_' == aChar;
+    }
+
+    public static void checkSyntax(String sql) {
+        Splitter.on(SEPARATOR).split(sql).forEach(Ltree::checkLabelSyntax);
+    }
+
+    public static Ltree join(String prefix, String suffix) {
+        checkSyntax(prefix);
+        checkSyntax(suffix);
+        return fromSql(prefix + SEPARATOR + suffix);
+    }
+
+    public static Ltree join(Ltree prefix, Ltree suffix) {
+        return join(prefix.getSql(), suffix.getSql());
+    }
+
+    public static Ltree parseLabel(String labelToEscape) {
+        String escaped = escapeLabel(labelToEscape);
+        return fromSql(escaped);
+    }
+}
diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java
index e2301459c..8c7f0e439 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiService.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java
@@ -3,7 +3,6 @@ package fr.inra.oresing.rest;
 import com.google.common.base.Charsets;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
-import com.google.common.base.Splitter;
 import com.google.common.collect.*;
 import com.google.common.primitives.Ints;
 import fr.inra.oresing.OreSiTechnicalException;
@@ -25,7 +24,6 @@ import org.apache.commons.csv.CSVFormat;
 import org.apache.commons.csv.CSVParser;
 import org.apache.commons.csv.CSVPrinter;
 import org.apache.commons.csv.CSVRecord;
-import org.apache.commons.lang3.RegExUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.assertj.core.util.Streams;
 import org.assertj.core.util.Strings;
@@ -66,12 +64,6 @@ public class OreSiService {
 
     public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss").withZone(ZoneOffset.UTC);
     public static final DateTimeFormatter DATE_FORMATTER_DDMMYYYY = DateTimeFormatter.ofPattern("dd/MM/yyyy");
-    /**
-     * Déliminateur entre les différents niveaux d'un ltree postgresql.
-     * <p>
-     * https://www.postgresql.org/docs/current/ltree.html
-     */
-    private static final String LTREE_SEPARATOR = ".";
     private static final String KEYCOLUMN_SEPARATOR = "__";
     @Autowired
     private OreSiRepository repo;
@@ -97,51 +89,12 @@ public class OreSiService {
     @Autowired
     private RelationalService relationalService;
 
-    public static String escapeKeyComponent(String key) {
-        String lowerCased = key.toLowerCase();
-        String withAccentsStripped = StringUtils.stripAccents(lowerCased);
-        String toEscape = StringUtils.replace(withAccentsStripped, " ", "_");
-        String escaped = toEscape.chars()
-                .mapToObj(x -> (char) x)
-                .map(OreSiService::escapeSymbolFromKeyComponent)
-                .collect(Collectors.joining());
-        checkNaturalKeySyntax(escaped);
-        return escaped;
-    }
-
-    public static void checkNaturalKeySyntax(String keyComponent) {
-        Preconditions.checkState(keyComponent.length() <= 256, "Un label dans un ltree ne peut pas être plus long que 256 caractères à cause de PG");
-        Preconditions.checkState(!keyComponent.isEmpty(), "La clé naturelle ne peut être vide. vérifier le nom des colonnes.");
-        Preconditions.checkState(keyComponent.matches("[a-zA-Z0-9_]+"), keyComponent + " n'est pas un élément valide pour une clé naturelle");
-    }
-
-    private static String escapeSymbolFromKeyComponent(Character aChar) {
-        String escapedChar;
-        if (characterCanBeUsedInLtreeLabel(aChar)) {
-            escapedChar = String.valueOf(aChar);
-        } else {
-            escapedChar = RegExUtils.replaceAll(
-                    Character.getName(aChar),
-                    "[^a-zA-Z0-9_]",
-                    ""
-            );
-        }
-        return escapedChar;
-    }
-
     /**
-     * D'après la documentation PostgreSQL sur ltree
-     *
-     * <blockquote>
-     *     A label is a sequence of alphanumeric characters and underscores (for example, in C locale the characters A-Za-z0-9_ are allowed). Labels must be less than 256 characters long.
-     * </blockquote>
+     * @deprecated utiliser directement {@link Ltree#escapeLabel(String)}
      */
-    private static boolean characterCanBeUsedInLtreeLabel(Character aChar) {
-        return Character.isAlphabetic(aChar) || Character.isDigit(aChar) || '_' == aChar;
-    }
-
-    private void checkHierarchicalKeySyntax(String compositeKey) {
-        Splitter.on(LTREE_SEPARATOR).split(compositeKey).forEach(OreSiService::checkNaturalKeySyntax);
+    @Deprecated
+    public static String escapeKeyComponent(String key) {
+        return Ltree.escapeLabel(key);
     }
 
     protected UUID storeFile(Application app, MultipartFile file) throws IOException {
@@ -380,10 +333,10 @@ public class OreSiService {
                 parentHierarchicalKeyColumn = referenceComponentDescription.getParentKeyColumn();
                 parentHierarchicalParentReference = compositeReferenceDescription.getComponents().get(compositeReferenceDescription.getComponents().indexOf(referenceComponentDescription) - 1).getReference();
                 getHierarchicalKeyFn = (naturalKey, referenceValues) -> {
-                    String parentHierarchicalKey = escapeKeyComponent(referenceValues.get(parentHierarchicalKeyColumn));
-                    return parentHierarchicalKey + LTREE_SEPARATOR + naturalKey;
+                    String parentHierarchicalKey = Ltree.escapeLabel(referenceValues.get(parentHierarchicalKeyColumn));
+                    return parentHierarchicalKey + Ltree.SEPARATOR + naturalKey;
                 };
-                getHierarchicalReferenceFn = (reference) -> parentHierarchicalParentReference + LTREE_SEPARATOR + reference;
+                getHierarchicalReferenceFn = (reference) -> parentHierarchicalParentReference + Ltree.SEPARATOR + reference;
             }
         } else {
             getHierarchicalKeyFn = (naturalKey, referenceValues) -> naturalKey;
@@ -439,7 +392,7 @@ public class OreSiService {
                                     UUID referenceId = referenceValidationCheckResult.getReferenceId();
                                     refValues.put((String) referenceValidationCheckResult.getTarget().getTarget(), (String) referenceValidationCheckResult.getValue());
                                     refsLinkedTo
-                                            .computeIfAbsent(escapeKeyComponent(reference), k -> new LinkedHashSet<>())
+                                            .computeIfAbsent(Ltree.escapeLabel(reference), k -> new LinkedHashSet<>())
                                             .add(referenceId);
                                 }
                             } else {
@@ -450,15 +403,15 @@ public class OreSiService {
                         String naturalKey;
                         String technicalId = e.getId().toString();
                         if (ref.getKeyColumns().isEmpty()) {
-                            naturalKey = escapeKeyComponent(technicalId);
+                            naturalKey = Ltree.escapeLabel(technicalId);
                         } else {
                             naturalKey = ref.getKeyColumns().stream()
                                     .map(kc -> refValues.get(kc))
                                     .filter(key -> !Strings.isNullOrEmpty(key))
-                                    .map(key -> escapeKeyComponent(key))
+                                    .map(key -> Ltree.escapeLabel(key))
                                     .collect(Collectors.joining(KEYCOLUMN_SEPARATOR));
                         }
-                        OreSiService.checkNaturalKeySyntax(naturalKey);
+                        Ltree.checkLabelSyntax(naturalKey);
                         String recursiveNaturalKey = naturalKey;
                         if (isRecursive) {
                             selfLineChecker
@@ -468,7 +421,7 @@ public class OreSiService {
                                     .ifPresent(key -> e.setId(key));
                             String parentKey = parentreferenceMap.getOrDefault(recursiveNaturalKey, null);
                             while (!Strings.isNullOrEmpty(parentKey)) {
-                                recursiveNaturalKey = parentKey + LTREE_SEPARATOR + recursiveNaturalKey;
+                                recursiveNaturalKey = parentKey + Ltree.SEPARATOR + recursiveNaturalKey;
                                 parentKey = parentreferenceMap.getOrDefault(parentKey, null);
                             }
                         }
@@ -483,7 +436,7 @@ public class OreSiService {
                                 getHierarchicalReferenceFn.apply(selfHierarchicalReference);
                         refValues.putAll(InternationalizationDisplay.getDisplays(displayPattern, displayColumns, refValues));
                         buildedHierarchicalKeys.put(naturalKey, hierarchicalKey);
-                        checkHierarchicalKeySyntax(hierarchicalKey);
+                        Ltree.checkSyntax(hierarchicalKey);
                         e.setBinaryFile(fileId);
                         e.setReferenceType(refType);
                         e.setHierarchicalKey(hierarchicalKey);
@@ -533,11 +486,11 @@ public class OreSiService {
                     if (!Strings.isNullOrEmpty(s)) {
                         String naturalKey;
                         try {
-                            s = OreSiService.escapeKeyComponent(s);
+                            s = Ltree.escapeLabel(s);
                             naturalKey = ref.getKeyColumns()
                                     .stream()
                                     .map(kc -> columns.indexOf(kc))
-                                    .map(k -> OreSiService.escapeKeyComponent(csvrecord.get(k)))
+                                    .map(k -> Ltree.escapeLabel(csvrecord.get(k)))
                                     .collect(Collectors.joining("__"));
                         } catch (IllegalArgumentException e) {
                             return;
@@ -827,7 +780,7 @@ public class OreSiService {
             Map<String, String> requiredAuthorizations = new LinkedHashMap<>();
             dataTypeDescription.getAuthorization().getAuthorizationScopes().forEach((authorizationScope, variableComponentKey) -> {
                 String requiredAuthorization = values.get(variableComponentKey);
-                checkHierarchicalKeySyntax(requiredAuthorization);
+                Ltree.checkSyntax(requiredAuthorization);
                 requiredAuthorizations.put(authorizationScope, requiredAuthorization);
             });
             checkTimescopRangeInDatasetRange(timeScope, errors, binaryFileDataset, rowWithData.getLineNumber());
diff --git a/src/test/java/fr/inra/oresing/persistence/LtreeTest.java b/src/test/java/fr/inra/oresing/persistence/LtreeTest.java
new file mode 100644
index 000000000..0e1479f09
--- /dev/null
+++ b/src/test/java/fr/inra/oresing/persistence/LtreeTest.java
@@ -0,0 +1,15 @@
+package fr.inra.oresing.persistence;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class LtreeTest {
+
+    @Test
+    public void parseLabel() {
+        String sql = Ltree.parseLabel("composition <5%/µg").getSql();
+        Assert.assertEquals("composition_LESSTHANSIGN5PERCENTSIGNSOLIDUSMICROSIGNg", sql);
+    }
+}
\ No newline at end of file
-- 
GitLab


From a7d7538546bebaeb679119f5a2e4877b2ffbc1b3 Mon Sep 17 00:00:00 2001
From: Brendan Le Ny <bleny@codelutin.com>
Date: Mon, 7 Feb 2022 15:01:52 +0100
Subject: [PATCH 3/3] =?UTF-8?q?Utilise=20Ltree=20dans=20les=20entit=C3=A9s?=
 =?UTF-8?q?=20et=20dans=20la=20couche=20service=20pour=20le=20calcul=20des?=
 =?UTF-8?q?=20cl=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../fr/inra/oresing/model/ReferenceValue.java |  7 +-
 .../oresing/persistence/JsonRowMapper.java    | 12 ++++
 .../JsonTableRepositoryTemplate.java          |  1 +
 .../fr/inra/oresing/persistence/Ltree.java    |  6 ++
 .../persistence/ReferenceValueRepository.java |  4 +-
 .../oresing/rest/AuthorizationService.java    |  2 +-
 .../fr/inra/oresing/rest/OreSiResources.java  |  6 +-
 .../fr/inra/oresing/rest/OreSiService.java    | 65 ++++++++++---------
 .../migration/application/V1__init_schema.sql |  4 +-
 .../data/monsore/monsore-with-repository.yaml |  6 +-
 src/test/resources/data/monsore/monsore.yaml  |  6 +-
 11 files changed, 71 insertions(+), 48 deletions(-)

diff --git a/src/main/java/fr/inra/oresing/model/ReferenceValue.java b/src/main/java/fr/inra/oresing/model/ReferenceValue.java
index 88ea349bd..9a0b18b54 100644
--- a/src/main/java/fr/inra/oresing/model/ReferenceValue.java
+++ b/src/main/java/fr/inra/oresing/model/ReferenceValue.java
@@ -1,5 +1,6 @@
 package fr.inra.oresing.model;
 
+import fr.inra.oresing.persistence.Ltree;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.ToString;
@@ -14,9 +15,9 @@ import java.util.UUID;
 public class ReferenceValue extends OreSiEntity {
     private UUID application;
     private String referenceType;
-    private String hierarchicalKey;
-    private String hierarchicalReference;
-    private String naturalKey;
+    private Ltree hierarchicalKey;
+    private Ltree hierarchicalReference;
+    private Ltree naturalKey;
     private Map<String, String> refValues;
     private Map<String, Set<UUID>> refsLinkedTo;
     private UUID binaryFile;
diff --git a/src/main/java/fr/inra/oresing/persistence/JsonRowMapper.java b/src/main/java/fr/inra/oresing/persistence/JsonRowMapper.java
index 75c99bd50..10a0fc062 100644
--- a/src/main/java/fr/inra/oresing/persistence/JsonRowMapper.java
+++ b/src/main/java/fr/inra/oresing/persistence/JsonRowMapper.java
@@ -55,6 +55,18 @@ public class JsonRowMapper<T> implements RowMapper<T> {
                         return LocalDateTimeRange.parseSql(p.getText());
                     }
                 })
+                .addSerializer(Ltree.class, new JsonSerializer<>() {
+                    @Override
+                    public void serialize(Ltree value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+                        gen.writeString(value.getSql());
+                    }
+                })
+                .addDeserializer(Ltree.class, new JsonDeserializer<>() {
+                    @Override
+                    public Ltree deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+                        return Ltree.fromSql(p.getText());
+                    }
+                })
                 ;
         jsonMapper.registerModule(module);
     }
diff --git a/src/main/java/fr/inra/oresing/persistence/JsonTableRepositoryTemplate.java b/src/main/java/fr/inra/oresing/persistence/JsonTableRepositoryTemplate.java
index fcd7c5902..e74a926d3 100644
--- a/src/main/java/fr/inra/oresing/persistence/JsonTableRepositoryTemplate.java
+++ b/src/main/java/fr/inra/oresing/persistence/JsonTableRepositoryTemplate.java
@@ -55,6 +55,7 @@ abstract class JsonTableRepositoryTemplate<T extends OreSiEntity> implements Ini
                 }
             });
             String json = getJsonRowMapper().toJson(entities);
+            System.out.println("machin " + json);
             List<UUID> result = namedParameterJdbcTemplate.queryForList(
                     query, new MapSqlParameterSource("json", json), UUID.class);
         });
diff --git a/src/main/java/fr/inra/oresing/persistence/Ltree.java b/src/main/java/fr/inra/oresing/persistence/Ltree.java
index 8caaf2ebb..a13148ef3 100644
--- a/src/main/java/fr/inra/oresing/persistence/Ltree.java
+++ b/src/main/java/fr/inra/oresing/persistence/Ltree.java
@@ -7,6 +7,7 @@ import org.apache.commons.lang3.CharUtils;
 import org.apache.commons.lang3.RegExUtils;
 import org.apache.commons.lang3.StringUtils;
 
+import java.util.UUID;
 import java.util.stream.Collectors;
 
 @Value
@@ -91,4 +92,9 @@ public class Ltree {
         String escaped = escapeLabel(labelToEscape);
         return fromSql(escaped);
     }
+
+    public static Ltree toLabel(UUID uuid) {
+        String escaped = escapeLabel(StringUtils.remove(uuid.toString(), "-"));
+        return fromSql(escaped);
+    }
 }
diff --git a/src/main/java/fr/inra/oresing/persistence/ReferenceValueRepository.java b/src/main/java/fr/inra/oresing/persistence/ReferenceValueRepository.java
index f97c428e2..7b24cf31c 100644
--- a/src/main/java/fr/inra/oresing/persistence/ReferenceValueRepository.java
+++ b/src/main/java/fr/inra/oresing/persistence/ReferenceValueRepository.java
@@ -115,11 +115,11 @@ public class ReferenceValueRepository extends JsonTableInApplicationSchemaReposi
 
     public ImmutableMap<String, ApplicationResult.Reference.ReferenceUUIDAndDisplay> getReferenceIdAndDisplayPerKeys(String referenceType, String locale) {
         return findAllByReferenceType(referenceType).stream()
-                .collect(ImmutableMap.toImmutableMap(ReferenceValue::getHierarchicalKey,result->new ApplicationResult.Reference.ReferenceUUIDAndDisplay(result.getRefValues().get("__display_"+locale), result.getId(), result.getRefValues())));
+                .collect(ImmutableMap.toImmutableMap(referenceValue -> referenceValue.getHierarchicalKey().getSql(),result->new ApplicationResult.Reference.ReferenceUUIDAndDisplay(result.getRefValues().get("__display_"+locale), result.getId(), result.getRefValues())));
     }
 
     public ImmutableMap<String, UUID> getReferenceIdPerKeys(String referenceType) {
         return findAllByReferenceType(referenceType).stream()
-                .collect(ImmutableMap.toImmutableMap(ReferenceValue::getHierarchicalKey, ReferenceValue::getId));
+                .collect(ImmutableMap.toImmutableMap(referenceValue -> referenceValue.getHierarchicalKey().getSql(), ReferenceValue::getId));
     }
 }
\ No newline at end of file
diff --git a/src/main/java/fr/inra/oresing/rest/AuthorizationService.java b/src/main/java/fr/inra/oresing/rest/AuthorizationService.java
index a667a59a9..7223f5651 100644
--- a/src/main/java/fr/inra/oresing/rest/AuthorizationService.java
+++ b/src/main/java/fr/inra/oresing/rest/AuthorizationService.java
@@ -300,6 +300,6 @@ public class AuthorizationService {
         ImmutableSortedSet<GetGrantableResult.AuthorizationScope.Option> options = tree.getChildren(referenceValue).stream()
                 .map(child -> toOption(tree, child))
                 .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.AuthorizationScope.Option::getId)));
-        return new GetGrantableResult.AuthorizationScope.Option(referenceValue.getHierarchicalKey(), referenceValue.getHierarchicalKey(), options);
+        return new GetGrantableResult.AuthorizationScope.Option(referenceValue.getHierarchicalKey().getSql(), referenceValue.getHierarchicalKey().getSql(), options);
     }
 }
\ 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 d59af7505..e8cb0de81 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiResources.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiResources.java
@@ -197,9 +197,9 @@ public class OreSiResources {
         ImmutableSet<GetReferenceResult.ReferenceValue> referenceValues = list.stream()
                 .map(referenceValue ->
                         new GetReferenceResult.ReferenceValue(
-                                referenceValue.getHierarchicalKey(),
-                                referenceValue.getHierarchicalReference(),
-                                referenceValue.getNaturalKey(),
+                                referenceValue.getHierarchicalKey().getSql(),
+                                referenceValue.getHierarchicalReference().getSql(),
+                                referenceValue.getNaturalKey().getSql(),
                                 referenceValue.getRefValues()
                         )
                 )
diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java
index 8c7f0e439..60d074e34 100644
--- a/src/main/java/fr/inra/oresing/rest/OreSiService.java
+++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java
@@ -316,10 +316,10 @@ public class OreSiService {
         String parentHierarchicalKeyColumn, parentHierarchicalParentReference;
         Optional<Configuration.CompositeReferenceComponentDescription> recursiveComponentDescription = getRecursiveComponent(conf.getCompositeReferences(), refType);
         boolean isRecursive = recursiveComponentDescription.isPresent();
-        BiFunction<String, Map<String, String>, String> getHierarchicalKeyFn;
-        Function<String, String> getHierarchicalReferenceFn;
-        Map<String, String> buildedHierarchicalKeys = new HashMap<>();
-        Map<String, String> parentreferenceMap = new HashMap<>();
+        BiFunction<Ltree, Map<String, String>, Ltree> getHierarchicalKeyFn;
+        Function<Ltree, Ltree> getHierarchicalReferenceFn;
+        Map<Ltree, Ltree> buildedHierarchicalKeys = new HashMap<>();
+        Map<Ltree, Ltree> parentreferenceMap = new HashMap<>();
         if (toUpdateCompositeReference.isPresent()) {
             Configuration.CompositeReferenceDescription compositeReferenceDescription = toUpdateCompositeReference.get();
             boolean root = Iterables.get(compositeReferenceDescription.getComponents(), 0).getReference().equals(refType);
@@ -333,10 +333,10 @@ public class OreSiService {
                 parentHierarchicalKeyColumn = referenceComponentDescription.getParentKeyColumn();
                 parentHierarchicalParentReference = compositeReferenceDescription.getComponents().get(compositeReferenceDescription.getComponents().indexOf(referenceComponentDescription) - 1).getReference();
                 getHierarchicalKeyFn = (naturalKey, referenceValues) -> {
-                    String parentHierarchicalKey = Ltree.escapeLabel(referenceValues.get(parentHierarchicalKeyColumn));
-                    return parentHierarchicalKey + Ltree.SEPARATOR + naturalKey;
+                    Ltree parentHierarchicalKey = Ltree.parseLabel(referenceValues.get(parentHierarchicalKeyColumn));
+                    return Ltree.join(parentHierarchicalKey, naturalKey);
                 };
-                getHierarchicalReferenceFn = (reference) -> parentHierarchicalParentReference + Ltree.SEPARATOR + reference;
+                getHierarchicalReferenceFn = (reference) -> Ltree.join(Ltree.parseLabel(parentHierarchicalParentReference), reference);
             }
         } else {
             getHierarchicalKeyFn = (naturalKey, referenceValues) -> naturalKey;
@@ -368,7 +368,7 @@ public class OreSiService {
             if (isRecursive) {
                 recordStream = addMissingReferences(recordStream, selfLineChecker, recursiveComponentDescription, columns, ref, parentreferenceMap);
             }
-            List<String> hierarchicalKeys = new LinkedList<>();
+            List<Ltree> hierarchicalKeys = new LinkedList<>();
             Optional<InternationalizationReferenceMap> internationalizationReferenceMap = Optional.ofNullable(conf)
                     .map(configuration -> conf.getInternationalization())
                     .map(inter -> inter.getReferences())
@@ -400,43 +400,46 @@ public class OreSiService {
                             }
                         });
                         ReferenceValue e = new ReferenceValue();
-                        String naturalKey;
-                        String technicalId = e.getId().toString();
+                        Ltree naturalKey;
                         if (ref.getKeyColumns().isEmpty()) {
-                            naturalKey = Ltree.escapeLabel(technicalId);
+                            UUID technicalId = e.getId();
+                            naturalKey = Ltree.toLabel(technicalId);
                         } else {
-                            naturalKey = ref.getKeyColumns().stream()
+                            naturalKey = Ltree.parseLabel(ref.getKeyColumns().stream()
                                     .map(kc -> refValues.get(kc))
                                     .filter(key -> !Strings.isNullOrEmpty(key))
                                     .map(key -> Ltree.escapeLabel(key))
-                                    .collect(Collectors.joining(KEYCOLUMN_SEPARATOR));
+                                    .collect(Collectors.joining(KEYCOLUMN_SEPARATOR)));
                         }
-                        Ltree.checkLabelSyntax(naturalKey);
-                        String recursiveNaturalKey = naturalKey;
+                        Ltree recursiveNaturalKey = naturalKey;
+                        final Ltree refTypeAsLabel = Ltree.parseLabel(refType);
                         if (isRecursive) {
                             selfLineChecker
                                     .map(referenceLineChecker -> referenceLineChecker.getReferenceValues())
-                                    .map(values -> values.get(naturalKey))
+                                    .map(values -> values.get(naturalKey.getSql()))
                                     .filter(key -> key != null)
                                     .ifPresent(key -> e.setId(key));
-                            String parentKey = parentreferenceMap.getOrDefault(recursiveNaturalKey, null);
-                            while (!Strings.isNullOrEmpty(parentKey)) {
-                                recursiveNaturalKey = parentKey + Ltree.SEPARATOR + recursiveNaturalKey;
+                            Ltree parentKey = parentreferenceMap.getOrDefault(recursiveNaturalKey, null);
+                            while (parentKey != null) {
+                                recursiveNaturalKey = Ltree.join(parentKey, recursiveNaturalKey);
                                 parentKey = parentreferenceMap.getOrDefault(parentKey, null);
+                                //selfHierarchicalReference = Ltree.join(selfHierarchicalReference, refTypeAsLabel);
                             }
+//                            int x = StringUtils.countMatches(selfHierarchicalReference.getSql(), ".");
+//                            int y = StringUtils.countMatches(recursiveNaturalKey.getSql(), ".");
+//                            Preconditions.checkState(x == y);
                         }
-                        String hierarchicalKey = getHierarchicalKeyFn.apply(isRecursive ? recursiveNaturalKey : naturalKey, refValues);
-                        String selfHierarchicalReference = refType;
+                        Ltree hierarchicalKey = getHierarchicalKeyFn.apply(isRecursive ? recursiveNaturalKey : naturalKey, refValues);
+                        Ltree selfHierarchicalReference = refTypeAsLabel;
                         if (isRecursive) {
-                            for (int i = 1; i < recursiveNaturalKey.split("\\.").length; i++) {
-                                selfHierarchicalReference += ".".concat(refType);
+                            for (int i = 1; i < recursiveNaturalKey.getSql().split("\\.").length; i++) {
+                                selfHierarchicalReference = Ltree.fromSql(selfHierarchicalReference.getSql() + ".".concat(refType));
                             }
                         }
-                        String hierarchicalReference =
+                        Ltree hierarchicalReference =
                                 getHierarchicalReferenceFn.apply(selfHierarchicalReference);
                         refValues.putAll(InternationalizationDisplay.getDisplays(displayPattern, displayColumns, refValues));
                         buildedHierarchicalKeys.put(naturalKey, hierarchicalKey);
-                        Ltree.checkSyntax(hierarchicalKey);
                         e.setBinaryFile(fileId);
                         e.setReferenceType(refType);
                         e.setHierarchicalKey(hierarchicalKey);
@@ -447,7 +450,7 @@ public class OreSiService {
                         e.setRefValues(refValues);
                         return e;
                     })
-                    .sorted((a, b) -> a.getHierarchicalKey().compareTo(b.getHierarchicalKey()))
+                    .sorted(Comparator.comparing(a -> a.getHierarchicalKey().getSql()))
                     .map(e -> {
                         if (hierarchicalKeys.contains(e.getHierarchicalKey())) {
                             /*envoyer un message de warning : le refType avec la clef e.getNaturalKey existe en plusieurs exemplaires
@@ -468,7 +471,7 @@ public class OreSiService {
         return fileId;
     }
 
-    private Stream<CSVRecord> addMissingReferences(Stream<CSVRecord> recordStream, Optional<ReferenceLineChecker> selfLineChecker, Optional<Configuration.CompositeReferenceComponentDescription> recursiveComponentDescription, ImmutableList<String> columns, Configuration.ReferenceDescription ref, Map<String, String> referenceMap) {
+    private Stream<CSVRecord> addMissingReferences(Stream<CSVRecord> recordStream, Optional<ReferenceLineChecker> selfLineChecker, Optional<Configuration.CompositeReferenceComponentDescription> recursiveComponentDescription, ImmutableList<String> columns, Configuration.ReferenceDescription ref, Map<Ltree, Ltree> referenceMap) {
         Integer parentRecursiveIndex = recursiveComponentDescription
                 .map(rcd -> rcd.getParentRecursiveKey())
                 .map(rck -> columns.indexOf(rck))
@@ -495,7 +498,7 @@ public class OreSiService {
                         } catch (IllegalArgumentException e) {
                             return;
                         }
-                        referenceMap.put(naturalKey, s);
+                        referenceMap.put(Ltree.parseLabel(naturalKey), Ltree.parseLabel(s));
                         if (!referenceUUIDs.containsKey(s)) {
                             referenceUUIDs.put(s, UUID.randomUUID());
                         }
@@ -524,8 +527,8 @@ public class OreSiService {
                 .getConfiguration()
                 .getCompositeReferencesUsing(lowestLevelReference)
                 .orElseThrow();
-        BiMap<String, ReferenceValue> indexedByHierarchicalKeyReferenceValues = HashBiMap.create();
-        Map<ReferenceValue, String> parentHierarchicalKeys = new LinkedHashMap<>();
+        BiMap<Ltree, ReferenceValue> indexedByHierarchicalKeyReferenceValues = HashBiMap.create();
+        Map<ReferenceValue, Ltree> parentHierarchicalKeys = new LinkedHashMap<>();
         ImmutableList<String> referenceTypes = compositeReferenceDescription.getComponents().stream()
                 .map(Configuration.CompositeReferenceComponentDescription::getReference)
                 .collect(ImmutableList.toImmutableList());
@@ -539,7 +542,7 @@ public class OreSiService {
                     referenceValueRepository.findAllByReferenceType(reference).forEach(referenceValue -> {
                         indexedByHierarchicalKeyReferenceValues.put(referenceValue.getHierarchicalKey(), referenceValue);
                         if (parentKeyColumn != null) {
-                            String parentHierarchicalKey = referenceValue.getRefValues().get(parentKeyColumn);
+                            Ltree parentHierarchicalKey = Ltree.fromSql(referenceValue.getRefValues().get(parentKeyColumn));
                             parentHierarchicalKeys.put(referenceValue, parentHierarchicalKey);
                         }
                     });
diff --git a/src/main/resources/migration/application/V1__init_schema.sql b/src/main/resources/migration/application/V1__init_schema.sql
index 9a0db35f8..c493aa71c 100644
--- a/src/main/resources/migration/application/V1__init_schema.sql
+++ b/src/main/resources/migration/application/V1__init_schema.sql
@@ -19,8 +19,8 @@ create table ReferenceValue
     application           EntityRef REFERENCES Application (id),
     referenceType         TEXT CHECK (name_check(application, 'referenceType', referenceType)),
     hierarchicalKey       ltree NOT NULL,
-    hierarchicalReference TEXT  NOT NULL,
-    naturalKey            TEXT  NOT NULL,
+    hierarchicalReference ltree NOT NULL,
+    naturalKey            ltree NOT NULL,
     refsLinkedTo          jsonb check (refs_check_for_reference('${applicationSchema}', application, refsLinkedTo)),
     refValues             jsonb,
     binaryFile            EntityRef REFERENCES BinaryFile (id),
diff --git a/src/test/resources/data/monsore/monsore-with-repository.yaml b/src/test/resources/data/monsore/monsore-with-repository.yaml
index d6d755471..351da6cca 100644
--- a/src/test/resources/data/monsore/monsore-with-repository.yaml
+++ b/src/test/resources/data/monsore/monsore-with-repository.yaml
@@ -352,8 +352,8 @@ dataTypes:
               replace: true
             defaultValue: >
               return references.get("sites")
-              .find{it.getNaturalKey().equals(datumByVariableAndComponent.get("site").get("bassin"))}
-              .getHierarchicalKey();
+              .find{it.getNaturalKey().getSql().equals(datumByVariableAndComponent.get("site").get("bassin"))}
+              .getHierarchicalKey().getSql();
             checker:
               name: Reference
               params:
@@ -365,7 +365,7 @@ dataTypes:
             defaultValue: >
               return references.get("sites")
               .find{it.getRefValues().get("zet_chemin_parent").equals(datumByVariableAndComponent.get("site").get("bassin")) && it.getRefValues().get("zet_nom_key").equals(datumByVariableAndComponent.get("site").get("plateforme"))}
-              .getHierarchicalKey();
+              .getHierarchicalKey().getSql();
             checker:
               name: Reference
               params:
diff --git a/src/test/resources/data/monsore/monsore.yaml b/src/test/resources/data/monsore/monsore.yaml
index f11cc867b..fec3bc303 100644
--- a/src/test/resources/data/monsore/monsore.yaml
+++ b/src/test/resources/data/monsore/monsore.yaml
@@ -350,8 +350,8 @@ dataTypes:
               replace: true
             defaultValue: >
               return references.get("sites")
-              .find{it.getNaturalKey().equals(datumByVariableAndComponent.get("site").get("bassin"))}
-              .getHierarchicalKey();
+              .find{it.getNaturalKey().getSql().equals(datumByVariableAndComponent.get("site").get("bassin"))}
+              .getHierarchicalKey().getSql();
             checker:
               name: Reference
               params:
@@ -363,7 +363,7 @@ dataTypes:
             defaultValue: >
               return references.get("sites")
               .find{it.getRefValues().get("zet_chemin_parent").equals(datumByVariableAndComponent.get("site").get("bassin")) && it.getRefValues().get("zet_nom_key").equals(datumByVariableAndComponent.get("site").get("plateforme"))}
-              .getHierarchicalKey();
+              .getHierarchicalKey().getSql();
             checker:
               name: Reference
               params:
-- 
GitLab