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