diff --git a/Documentation_fichier_Yaml.md b/Documentation_fichier_Yaml.md index 733ed2fe46622e626bd199356fd7d50615d6be1a..78b69412372c6688d187ae6bc28fbe2c7dc9c212 100644 --- a/Documentation_fichier_Yaml.md +++ b/Documentation_fichier_Yaml.md @@ -13,18 +13,29 @@ Il y a 5 parties (<span style="color: orange">sans indentation</span>) attendues <span style="color: orange">l'indentation du fichier yaml est très importante.</span> -### on commence par mettre la version du parser de yaml. + +### Description du fichier + +Informations sur le fichier lui même + +#### on renseigne la version du parser de yaml. Soit version actuelle du site qui est 0 actuellement. + ``` yaml version: 0 ``` +``` + D'une version à l'autre le format du yaml peut changer +``` + <span style="color: orange">*version* n'est pas indenté.</span> -### on présente l'application avec son nom et sa la version du fichier yaml : +#### on présente l'application avec son nom et sa la version du fichier yaml : (on commence par la version 1) -S'il y a déjà une application du même nom mais que l'on a fait des modifications dans le fichier on change de version. +S'il y a déjà une application du même nom mais que l'on a fait des modifications dans le fichier on incrémente la version. + ``` yaml application: name: Aplication_Nom @@ -32,8 +43,18 @@ application: ``` <span style="color: orange">*application* n'est pas indenté. *nom* et *version* sont indentés de 1.</span> -### on décrit les donnés de référence dans la partie *references*, on y liste les noms des colonnes souhaitées (dans *columns*); on précisant la liste de colonnes qui forment la clef naturelle (dans *keyColumn*): -par exemple pour les fichiers : + +### Description référentiels +on décrit les référentiels dans la partie *references*, on y liste les noms des colonnes souhaitées (dans *columns*); on précisant la liste de colonnes qui forment la clef naturelle (dans *keyColumn*): + +pour le modèle de référentiels + +```mermaid + classDiagram + sites *-- parcelles:site +``` + +et pour les fichiers : - sites @@ -73,20 +94,33 @@ references: nom de la parcelle: ``` -Le nom des colonnes des references doivent être courte pour ne pas être tronqué lors de la création de l'application. +``` +Le nom du référentiel est libre. Cependant pour ceux réutilisés ailleurs dans l'application, préférer utiliser minuscules et underscores sous peine de générer des erreurss: + +exampl : mon_nom_de_referentiel +``` + +Le nom des colonnes des references doivent être courte pour ne pas être tronqué lors de la création des vues de l'application. + + +``` + les noms des colonnes dans la base de données est limité à 63 caractères. Dans les vues, ce nom est une concaténation du nom du référentiel et du nom de la colonne +``` + Penser à mettre le même nom de colonnes dans le fichier *.csv* que dans la partie *columns* du fichier yaml. <span style="color: orange">*references* n'est pas indenté. *sites* et *parcelles* sont indentés de 1. *keyColumns* et *columns* sont indentés de 2. Le contenue de *columns* seront indenté de 3.</span> -#### On rajoute contraintes sur les données de référence +#### On peut poser des contraintes sur les données de référence Les contraintes se définissent pour chacune des données de référence dans la section validations. Chaque règle de validation peut porter sur plusieurs colonnes de la donnée de référence. Elle comporte une description et un checker (Reference, Integer, Float, RegularExpression, Date). + ``` yaml types_de_donnees_par_themes_de_sites_et_projet: @@ -118,12 +152,30 @@ Elle comporte une description et un checker (Reference, Integer, Float, RegularE checker: name: GroovyExpression # utilisation d'un script groovy de validation params: - expression: > - String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); - return application.getDataType().contains(datatype); + groovy: + expression: > + String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); + return application.getDataType().contains(datatype); ``` +| name| References | Integer | Float | GroovyExpression | RegularExpression | * +| --- | :-: | :-: | :-: | :-: | :-: | --- | +| columns | X | X | X | | X | La colonne dans laquelle on prend la valeur | +| refTypes | X | | | | | Le référentiels de jointure | +| pattern | | | | | X | Le pattern pour une expression régulière | +| codify | X | X | X | | X | Le contenu de la colonne est codifié | +| required | X | X | X | | X | Le contenu de la colonne ne peut être vide | +| groovy | | | | X | | une section pour le traitement d'une expression groovy | +| replace | | | | X | | l'expression groovy renvoie un résultat au lieu d'un booléen | + + +La section groovy accepte trois paramètres +expression : une expression groovy (pour le checker GroovyExpression doit renvoyer true. Ou remplace la valeur actuelle pour les autres checkers) +references : une liste de référentiels pour lesquels on veut disposer des valeurs dans l'expression +datatypes : une liste de datatypes pour lesquels on veut disposer des valeurs dans l'expression + + Pour les checkers GroovyExpression, on récupère dans le script des informations : datum : les valeurs de la ligne courante. @@ -138,7 +190,8 @@ Pour les checkers GroovyExpression, on récupère dans le script des information -> datatypes.get("nom du datatype").getValues().get("nom de la colonne") datatypesValues : idem que datatypes -> datatypesValues.get("nom du datatype").get("nom de la colonne") - params : la section params dans laquelle on peut rajouter des information que l'on souhaite utiliser dans le script.. + params : la section params + ### il est possible de définir des clefs composite entre différentes références Une clef composite permet de définir une hiérarchie entre différentes données de référence. @@ -157,17 +210,6 @@ Pour les checkers GroovyExpression, on récupère dans le script des information seront supprimés. Pour créer une clef à partir d'une chaîne, on peut utiliser un checker et enrenseignant la section codify de params. -rajouter le params required si cette clef est obligatoire -```yaml - - checker: - name: RegularExpression - params: - pattern: .* - required: true - codify: true - columns: nom du taxon déterminé -``` ```mermaid classDiagram @@ -196,17 +238,19 @@ compositeReferences: - parentRecursiveKey: nom du taxon superieur reference: taxon ``` -### on met les infos des *dataTypes* +### on renseigne la description des *dataTypes* + Pour enregistrer un type de données, il faut déclarer - le data : ce qui sera enregistré en base de données (*section data*) - le format du fichier (*section format*) - les authorisations (*section authorisations*) - les validations de chaque ligne -#### Nous regrouperons les données par nom du fichier csv qu'on souhaite importer (*nomDonnéeCSV*).</h4> + +#### Nous regrouperons les données par nom du fichier csv qu'on souhaite importer (*nomDonnéeCSV*.csv)</h4> ``` yaml dataTypes: - nomDonnéeCSV: + nom_donnees_csv: ``` <span style="color : orange">*dataTypes* n'est pas indenté. *nomDonnée* est indenté de 1.</span> @@ -321,10 +365,15 @@ Les *variables/components* sont passés dans la map *datum*. On récupère la va checker: name: GroovyExpression params: - expression: > - Set.of("", "0", "1", "2").contains(datum.get("SWC").get("qualité")) + groovy: + expression: > + Set.of("", "0", "1", "2").contains(datum.get("SWC").get("qualité")) ``` +Cette formulation vérifie que la valeur du component qualité de la variable SWC est vide ou égale à 0,1 ou 2 +L'expression doit renvoyer true + + Pour les checkers GroovyExpression, on récupère dans le script des informations : datum : les valeurs de la ligne courante. @@ -339,7 +388,6 @@ Pour les checkers GroovyExpression, on récupère dans le script des information -> datatypes.get("nom du datatype").getValues().get("nom de la variable").get("nom du composant") datatypesValues : idem que datatypes -> datatypesValues.get("nom du datatype").get("nom de la variable").get("nom du composant") - params : la section params dans laquelle on peut rajouter des information que l'on souhaite utiliser dans le script.. ``` yaml @@ -348,21 +396,20 @@ Pour les checkers GroovyExpression, on récupère dans le script des information checker: name: GroovyExpression params: + groovy: expression: > - return referencesValues.get("variables_et_unites_par_types_de_donnees") - .findAll{it.get("nom du type de données").equals(params.get("datatype"))} - .find{it.get("nom de la variable").equals(params.get("codeVariable"))} - .get("nom de l'unité").equals(datum.get(params.get("variable")).get(params.get("component"))); - references: variables_et_unites_par_types_de_donnees - datatype: "piegeage_en_montee" - variable: "Nombre d'individus" - codeVariable: nombre_d_individus - component: unit - - + String datatype= "piegeage_en_montee" + String variable= "Nombre d'individus" + String codeVariable= "nombre_d_individus" + String component= "unit" + return referencesValues.get("variables_et_unites_par_types_de_donnees") + .findAll{it.get("nom du type de données").equals(datatype)} + .find{it.get("nom de la variable").equals(codeVariable)} + .get("nom de l'unité").equals(datum.variable.component); + references: + - variables_et_unites_par_types_de_donnees ``` -Cette formulation vérifie que la valeur du component qualité de la variable SWC est vide ou égale à 0,1 ou 2 -L'expression doit renvoyer true +Des valeurs peuvent être définies dans l'expression. La partie validation peut être utilisée pour vérifier le contenu d'une colonne d'un fichier de référence @@ -685,8 +732,8 @@ multiyaml.zip ├── compositeReferences.yaml ├── configuration.yaml ├── dataTypes -│  ├── smp_infraj.yaml -│  └── ts_infraj.yaml +│ ├── smp_infraj.yaml +│ └── ts_infraj.yaml └── references ├── types_de_zones_etudes.yaml @@ -719,4 +766,4 @@ multiyaml.zip ## lors de l'importation de fichier csv dans l'application: -* ouvrer la console avec F12 dans votre navigateur pour voir l'erreur de téléversement (erreur serveur) plus en détail. \ No newline at end of file +* ouvrer la console avec F12 dans votre navigateur pour voir l'erreur de téléversement (erreur serveur) plus en détail. diff --git a/Documentation_fichier_Yaml.md.backup b/Documentation_fichier_Yaml.md.backup new file mode 100644 index 0000000000000000000000000000000000000000..bb4a923462668280d13730e9417bc574e8d9c7b7 --- /dev/null +++ b/Documentation_fichier_Yaml.md.backup @@ -0,0 +1,768 @@ +# Aide fichier Yaml +## La création : +Vous trouverez ci-dessous un exemple de fichier Yaml fictif qui décrit les parties attendues dans celui-ci pour qu'il +soit valide. **Attention le format Yaml est sensible** il faut donc respecter l'indentation. + +Il y a 5 parties (<span style="color: orange">sans indentation</span>) attendues dans le fichier : + + * version, + * application, + * references, + * compositeReferences, + * dataTypes + +<span style="color: orange">l'indentation du fichier yaml est très importante.</span> + + +### Description du fichier + +Informations sur le fichier lui même + +#### on renseigne la version du parser de yaml. +Soit version actuelle du site qui est 0 actuellement. + + +``` yaml +version: 0 +``` + +``` + D'une version à l'autre le format du yaml peut changer +``` + +<span style="color: orange">*version* n'est pas indenté.</span> +#### on présente l'application avec son nom et sa la version du fichier yaml : +(on commence par la version 1) + +S'il y a déjà une application du même nom mais que l'on a fait des modifications dans le fichier on incrémente la version. + +``` yaml +application: + name: Aplication_Nom + version: 1 +``` + +<span style="color: orange">*application* n'est pas indenté. *nom* et *version* sont indentés de 1.</span> + +### Description référentiels +on décrit les référentiels dans la partie *references*, on y liste les noms des colonnes souhaitées (dans *columns*); on précisant la liste de colonnes qui forment la clef naturelle (dans *keyColumn*): + +pour le modèle de référentiels + +```mermaid + classDiagram + sites *-- parcelles:site +``` + +et pour les fichiers : + + +- sites + +| nom du site | +| ------ | +| site1 | +| site2 | + +- parcelles + +| site | nom de la parcelle | +| ------ | ------ | +| site1 | 1 | +| site2 | 1 | + +on aura le yaml suivant + +``` yaml +references: + agroécosystème: + keyColumns: [nom] + columns: + nom: + nom + sites: + #donnée de référence avec une clef sur une colonne + keyColumns: [nom du site] + columns: + Agroécosystème: + nom du site: + parcelles: + #donnée de référence avec une clef sur deux colonnes + keyColumns: [site,nom de la parcelle] + columns: + site: + nom de la parcelle: +``` + +``` +Le nom du référentiel est libre. Cependant pour ceux réutilisés ailleurs dans l'application, préférer utiliser minuscules et underscores sous peine de générer des erreurss: + +exampl : mon_nom_de_referentiel +``` + +Le nom des colonnes des references doivent être courte pour ne pas être tronqué lors de la création des vues de l'application. + + +``` + les noms des colonnes dans la base de données est limité à 63 caractères. Dans les vues, ce nom est une concaténation du nom du référentiel et du nom de la colonne +``` + +Penser à mettre le même nom de colonnes dans le fichier *.csv* que dans la partie *columns* du fichier yaml. + +<span style="color: orange">*references* n'est pas indenté. *sites* et *parcelles* sont indentés de 1. *keyColumns* et +*columns* sont indentés de 2. Le contenue de *columns* seront indenté de 3.</span> + + +#### On peut poser des contraintes sur les données de référence + +Les contraintes se définissent pour chacune des données de référence dans la section validations. +Chaque règle de validation peut porter sur plusieurs colonnes de la donnée de référence. +Elle comporte une description et un checker (Reference, Integer, Float, RegularExpression, Date). + + + +``` yaml + + types_de_donnees_par_themes_de_sites_et_projet: + validations: + projetRef: # la clef d'une validation + description: "référence au projet" # la description + checker: # le checker de validation + name: Reference #Le checker à utiliser + params: #liste de paramètres (dépend du checker choisi) + refType: projet #pour le checker référence la donnée référencée + columns: nom du projet #liste des colonnes sur lequel s'applique le checker + sitesRef: + description: "référence au site" + checker: + name: Reference + params: + refType: sites + columns: nom du site + themesRef: + description: "référence au theme" + checker: + name: Reference + params: + refType: themes + columns: nom du thème + + checkDatatype: + description: "test" + checker: + name: GroovyExpression # utilisation d'un script groovy de validation + params: + groovy: + expression: > + String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); + return application.getDataType().contains(datatype); + +``` + +| name| References | Integer | Float | GroovyExpression | RegularExpression | * +| --- | :-: | :-: | :-: | :-: | :-: | --- | +| columns | X | X | X | | X | La colonne dans laquelle on prend la valeur | +| refTypes | X | | | | | Le référentiels de jointure | +| pattern | | | | | X | Le pattern pour une expression régulière | +| codify | X | X | X | | X | Le contenu de la colonne est codifié | +| required | X | X | X | | X | Le contenu de la colonne ne peut être vide | +| groovy | | | | X | | une section pour le traitement d'une expression groovy | +| replace | | | | X | | l'expression groovy renvoie un résultat au lieu d'un booléen | + + +La section groovy accepte trois paramètres +expression : une expression groovy (pour le checker GroovyExpression doit renvoyer true. Ou remplace la valeur actuelle pour les autres checkers) +references : une liste de référentiels pour lesquels on veut disposer des valeurs dans l'expression +datatypes : une liste de datatypes pour lesquels on veut disposer des valeurs dans l'expression + + +Pour les checkers GroovyExpression, on récupère dans le script des informations : + + datum : les valeurs de la ligne courante. + On récupère la valeur d'un variable-component -> datum.get("nom de la variable").get("nom du composant") + application : le yaml de l'application + references: les valeurs d'une donnée de référence spécifique; + Il faut renseigner dans params la clef "references" qui définit les données de références accessibles dans references. + -> references.get("nom de la reference").getRefValues().get("nom de la colonne") + referencesValues : idem que references; + -> referencesValues.get("nom de la reference").get("nom de la colonne") + datatypes : idem que references pour les datatypes. Il faut renseigner le param datatypes + -> datatypes.get("nom du datatype").getValues().get("nom de la colonne") + datatypesValues : idem que datatypes + -> datatypesValues.get("nom du datatype").get("nom de la colonne") + params : la section params + +### il est possible de définir des clefs composite entre différentes références + + Une clef composite permet de définir une hiérarchie entre différentes données de référence. + Dans l'example ci-dessous il y a une relation oneToMany entre les deux données de référence nomDeLaReferences et + nomDeLaReferences2. + + La clef naturelle permet de distinguer deux lignes distinctes + La clef composite rajoute une hiérarchie entre les données de référence. Dans l'exemple ci-dessous pour référencer + une ligne nomDeLaReferences2, il faudra utiliser comme valeur la clef technique crée : site1.site1__1 + + La clef crée sera en minuscule, ne comportera pas d'accents; les espaces sont remplacés par des underscores; les + traits d'union sont supprimés. + "Ma clé qui-sert-de-référence" -> "ma_cle_quisertdereference" + + Elle ne doit alors comporter que des lettres minuscules de chiffres et des underscores tous les autres caractères + seront supprimés. + +Pour créer une clef à partir d'une chaîne, on peut utiliser un checker et enrenseignant la section codify de params. + +```mermaid + classDiagram + sites *-- parcelles:site +``` + +``` yaml +compositeReferences: + localizations: + components: + - reference: sites + - reference: parcelles + parentKeyColumn: "site" +``` + +<span style="color: orange">*compositeReferences* n'est pas indenté. *localizations* est indenté de 1. *components* est +indenté de 2. *- reference* et *- parentKeyColumn* sont indentés de 3. Le *reference* qui est sous parentKeyColumn est +indenté de 4.</span> + +Il est possible de définir une composite référence récursive dans le cas de données de références dui font référence à elle même. En ce cas on utilisera la clef `parentRecursiveKey` pour faire référence à la colonne parent du même fichier. +``` yaml + +compositeReferences: + taxon: + components: + - parentRecursiveKey: nom du taxon superieur + reference: taxon +``` +### on renseigne la description des *dataTypes* + + Pour enregistrer un type de données, il faut déclarer + - le data : ce qui sera enregistré en base de données (*section data*) + - le format du fichier (*section format*) + - les authorisations (*section authorisations*) + - les validations de chaque ligne + +#### Nous regrouperons les données par nom du fichier csv qu'on souhaite importer (*nomDonnéeCSV*.csv)</h4> + +``` yaml +dataTypes: + nom_donnees_csv: +``` + +<span style="color : orange">*dataTypes* n'est pas indenté. *nomDonnée* est indenté de 1.</span> + +##### *data* +La section data permet de décrire le schéma des données enregistrées en base. Les données sont enregistrées comme une +liste de *variables* pouvant avoir plusieurs *components*. +Les *variables/components* peuvent être des constantes ou des valeurs calculées, provenir d'un en-tête, ou provenir des +colonnes. + +*date*, *localization* et *prélèvement* sont des exemples de nom de variable qui regrouperont plusieurs components. + On fait la liste de *components* pour chaque variable. + +Par exemple *day* et *time* sont les *components* de la variable *date*. + +On vérifie leurs formats grace aux *checker* -> *name* est le nom du checker et *params* permet de définir les +paramètres du format via le *pattern*. +Voici quelque possibilité de *pattern* possible pour les dates et heures : + +|pattern | exemple 1 | exemple 2 | +| -------- | --------- | --------- | +|dd/MM/yy |31/01/21 | 31/12/21 | +|dd/MM/yyyy|31/01/2021 |31/12/2021 | +|MM/yyyy |01/2021 |12/2021 | +|M/yyyy |1/2021 |12/2021 | +|HH:mm |13:00 |01:00 | +|hh:mm:ss |13:00:00 |01:00:00 | +|dd/MM/yy hh:mm:ss|31/01/21 13:00:00|31/12/21 01:00:00| + +<span style="color : orange">Pour les dates anglaises inverser le "dd" avec le "MM" (exemple : MM/dd/yy -> 01/31/21) et +pour l'heure anglaise il suffit d'ajouter am/pm (exemple "hh:mm am/pm"-> "01:00 am" ou "hh:mm:ss AM/PM" -> "01:00:00 AM"). +Le *pattern* doit correspondre avec le format de la date dans le fichier CSV.</span> + +pour les données : + +| date | heure | nom de la parcelle | point | volume | qualité | +| ------ | ------ | ------ | ------ | ------ | ------ | +| 12/01/2010 | 10:00:00 | site1.site1__1 | 2 | 240.7 | 2 | +| 12/01/2010 | 15:30:00 | site2.site2__1 | 1 | 105.25 | 1 | + +On décrit un format pour stocker les données sous la forment + +``` json + { + date:{ + datetime: "12/01/2010 10:00:00", + day: "12/01/2010", + time: "10:00:00" + }, + localization:{ + parcelle:"site1.site1__1", + point:"2" + }, + prélèvement:{ + volume:240.7, + qualité:2 + } + } +``` + +``` yaml + data: + date: + components: + datetime: + #calcul d'une valeur par défaut date+time avec une expression groovy + defaultValue: > + return datumByVariableAndComponent.get("date").get("day") +" " +datumByVariableAndComponent.get("date").get("time")+ ":00" + day: + checker: + name: Date + params: + pattern: dd/MM/yyyy + time: + checker: + name: Date + params: + pattern: hh:mm:ss + localization: + components: + parcelle: + checker: + name: Reference + params: + refType: parcelles + point: + checker: + name: Integer + prélèvement: + components: + volume: + checker: + name: Float + qualité: + checker: + name: Integer +``` + +<span style="color: red">*/!\ *refType* doit forcément être identique aux noms des références déclarées dans la partie +*references* /!\ </span> + +<span style="color: orange">*data* est indenté de 2. Les variables sont indentés de 3 et les components le sont de 4.</span> + +##### la validation est utilisé pour valider une ligne. + +Les *variables/components* sont passés dans la map *datum*. On récupère la valeur du component qualité de la variable SWC + +``` yaml + validations: + swcQualityEnumeration: + description: "Si renseignée, la qualité du taux d'humidité vaut 1, 2 ou 3" + checker: + name: GroovyExpression + params: + groovy: + expression: > + Set.of("", "0", "1", "2").contains(datum.get("SWC").get("qualité")) +``` + +Cette formulation vérifie que la valeur du component qualité de la variable SWC est vide ou égale à 0,1 ou 2 +L'expression doit renvoyer true + + +Pour les checkers GroovyExpression, on récupère dans le script des informations : + + datum : les valeurs de la ligne courante. + On récupère la valeur d'un variable-component -> datum.get("nom de la variable").get("nom du composant") + application : le yaml de l'application + references: les valeurs d'une donnée de référence spécifique; + Il faut renseigner dans params la clef "references" qui définit les données de références accessibles dans references. + -> references.get("nom de la reference").getRefValues().get("nom de la variable").get("nom du composant") + referencesValues : idem que references; + -> referencesValues.get("nom de la reference").get("nom de la variable").get("nom du composant") + datatypes : idem que references pour les datatypes. Il faut renseigner le param datatypes + -> datatypes.get("nom du datatype").getValues().get("nom de la variable").get("nom du composant") + datatypesValues : idem que datatypes + -> datatypesValues.get("nom du datatype").get("nom de la variable").get("nom du composant") + + +``` yaml + unitOfIndividus: + description: "vérifie l'unité du nombre d'individus" + checker: + name: GroovyExpression + params: + groovy: + expression: > + String datatype= "piegeage_en_montee" + String variable= "Nombre d'individus" + String codeVariable= "nombre_d_individus" + String component= "unit" + return referencesValues.get("variables_et_unites_par_types_de_donnees") + .findAll{it.get("nom du type de données").equals(datatype)} + .find{it.get("nom de la variable").equals(codeVariable)} + .get("nom de l'unité").equals(datum.variable.component); + references: variables_et_unites_par_types_de_donnees +``` +Des valeurs peuvent être définies dans l'expression. + +La partie validation peut être utilisée pour vérifier le contenu d'une colonne d'un fichier de référence + +<span style="color: orange">*validations* est indenté de 2. </span> + +##### *authorization* porte bien son nom c'est là qu'on définira les autorisations d'accès aux données : +Authorization permet de définir des groupes de valeurs. Une ligne du fichier est découpée en autant de ligne que de +*dataGroups* et contient un *authorizationScope* et un *timeScope*. +Les droits sont portés par la ligne. (un dataGroup + un authorizationScope + un timeScope) + +Dans *dataGroups* nous regrouperont les données par type de données. + +-> *authorizationScope* clef naturelle d' une ligne de fichier en combinaison avec. + +-> *timeScope* est la partie qui permet de mettre une autorisation sur une durée. + +``` yaml + authorization: + dataGroups: + typeDonnée1: + label: "Référentiel" + data: + - date + - localization + typeDonnée2: + label: "Données qualitatives" + data: + - prélèvement + authorizationScopes: + localization_ref1: + variable: localization + component: parcelle + localization_ref2: + variable: localization + component: point + timeScope: + variable: date + component: datetime +``` + +Les patterns de timescope valides sont : +- dd/MM/yyyy HH:mm:ss +- dd/MM/yyyy +- MM/yyyy +- yyyy +Vous pouvez préciser la durée du timescope dans le params "duration" au format: +- ([0-9]*) (NANOS|MICROS|MILLIS|SECONDS|MINUTES|HOURS|HALF_DAYS|DAYS|WEEKS|MONTHS|YEARS + + +``` yaml + authorization: + ... + timeScope: + variable: date + component: datetime + + data: + date: + components: + datetime: + checker: + name: Date + params: + pattern: dd/MM/yyyy HH:mm:ss + duration: 30 MINUTES +``` + +<span style="color: orange">*authorization* est indenté de 2. *dataGroups*, *authorizationScopes* et *timeScope* sont +indenté de 3.</span> + +##### ensuite on va décrire le format des données attendues (dans *format*) décrite dans la partie *dataTypes* : + +Si votre fichier à des données mise dans un cartouche vous devrez les décrire dans la partie *constants*. +On précisera le nombre de lignes dans la cartouche dans *rowNumber* et le nombre de colonnes utiliser dans la cartouche +dans *columnNumber*. + +ici le contenu de la première ligne deuxième colonne est lié au varaible/component localization/nomDonnée et apparaîtra +à l'export dans une colonne "type de données" +``` yaml + format: + constants: + - rowNumber: 1 + columnNumber: 2 + boundTo: + variable: localization + component: nomDonnée + exportHeader: "type de données" +``` + +<span style="color: orange">*format* est indenté de 2. </span> + +*headerLine* permet de mettre le nombre de la lignes qui contient le nom des colonnes décrite plus bas dans *columns*. + +``` yaml + headerLine: 1 +``` + +*firstRowLine* sera égale au numéro de la première ligne dans la quelle se trouvera les premières données. +``` yaml + firstRowLine: 2 +``` + +*columns* est la partie dans laquelle nous décrirons toutes les colonnes et leurs types de données que nous attendons +dans chaque colonne du fichier CSV (pour l'exemple utilisé ici c'est pour les données du fichier nomDonnées.csv): + +*header* doit avoir exactement le même nom que le nom de la colonne dans le fichier csv. + +``` yaml + columns: + - header: "nom de la parcelle" + boundTo: + variable: localization + component: parcelle + - header: "point" + boundTo: + variable: localization + component: point + - header: "date" + boundTo: + variable: date + component: day + - header: "heure" + boundTo: + variable: date + component: time + - header: "volume" + boundTo: + variable: prélèvement + component: volume + - header: "qualité" + boundTo: + variable: prélèvement + component: qualité +``` +## lors de l'importation du fichier yaml : + +* mettre le nom de l'application en minuscule, +* sans espace, +* sans accent, +* sans chiffre et +* sans caractères speciaux +## Internationalisation du fichier yaml: +Il est possible de faire un fichier international en ajoutant plusieurs parties Internationalisation en précisant la langue. + +### Internationalisation de l'application: +Dans la partie application ajouter *defaultLanguage* pour préciser la langue par default de l'application. +Ainsi que *internationalization* qui contient les abbreviations des langues de traduction (ex: *fr* ou *en*) +Ce qui premettra de traduire le nom de l'application. + +``` yaml + defaultLanguage: fr + internationalization: + fr: Application_nom_fr + en: Application_nom_en +``` +### Internationalisation des *references*: +Nous pouvons faire en sorte que le nom de la référence s'affiche dans la langue de l'application en y ajoutant +*internationalizationName* ainsi que les langues dans lequel on veux traduire le nom de la référence. +*internationalizedColumns* .... + +``` yaml +references: + especes: + internationalizationName: + fr: Espèces + en: Species + internationalizedColumns: + esp_definition_fr: + fr: esp_definition_fr + en: esp_definition_en +``` + +- Définition d'un affichage d'un référentiel' + +Il est possible de créer un affichage internationalisé d'un référentiel (dans les menus, les types de données). +Pour cela on va rajouter une section internationalizationDisplay. + +``` Yaml + internationalizationDisplay: + pattern: + fr: '{nom_key} ({code_key})' + en: '{nom_key} ({code_key})' + +``` +On définit un pattern pour chaque langue en mettant entre accolades les nom des colonnes. C'est nom de colonnes seront remplacés par la valeur de la colonne ou bien, si la colonne est internationalisée, par la valeur de la colonne internationalisée correspondant à cette colonne. + +Par défaut, c'est le code du référentiel qui est affiché. +### Internationalisation des *dataTypes*: +Nous pouvons aussi faire en sorte que *nomDonnéeCSV* soit traduit. Même chose pour les noms des *dataGroup*. + +``` yaml +dataTypes: + nomDonnéeCSV: + internationalizationName: + fr: Nom Donnée CSV + en: Name Data CSV + authorization: + dataGroups: + referentiel: + internationalizationName: + fr: Référentiel + en: Referential + label: "Référentiel" + data: + - date + - projet + - site + - commentaire +``` + +On peut surcharger l'affichage d'une colonne faisant référence à un référentiel en rajoutant une section internationalizationDisplay dans le dataType. +```Yaml + pem: + internationalizationDisplay: + especes: + pattern: + fr: 'espèce :{esp_nom}' + en: 'espèce :{esp_nom}' +``` +## templating +IL est possible d'utiliser un template lorsque certaines colonnes de datatype on un format commun. +par example avec des colonnes dont le nom répond au pattern variable_profondeur_répétition : SWC_([0-9]*)_([0-9]*) + +``` csv +Date Time SWC_1_10 SWC_2_10 SWC_3_10 SWC_4_10 +01/01/2001 01:00 45 35 37 49 +01/01/2001 02:00 45 35 37 49 + + +``` +Il est possible d'enregistrer toutes les colonnes SWC_([0-9]*)_([0-9]*) dans une variable unique swc. + +On declare cette variable dans la section data + +```yaml + SWC: + components: + variable: + checker: + name: Reference + params: + refType: variables + required: true + codify: true + value: + checker: + name: Float + params: + required: false + unit: + defaultValue: return "percentage" + checker: + name: Reference + params: + refType: unites + required: true + codify: true + profondeur: + checker: + name: Float + params: + required: true + repetition: + checker: + name: Integer + params: + required: true + +``` +Dans la section format on rajoute une section _repeatedColumns_ pour indiquer comment remplir le data à partir du pattern +```yaml + format: + ... + repeatedColumns: + - headerPattern: "(SWC)_([0-9]+)_([0-9]+)" + tokens: + - boundTo: + variable: SWC + component: variable + exportHeader: "variable" + - boundTo: + variable: SWC + component: repetition + exportHeader: "Répétition" + - boundTo: + variable: SWC + component: profondeur + exportHeader: "Profondeur" + boundTo: + variable: SWC + component: valeur + exportHeader: "SWC" + +``` +On note la présence de la section token contenant un tableau de boundTo dans lequel le résultat des capture de l'expression régulière seront utilisés comme une colonne. +token d'indice 0 -> $1 +token d'indice 1 -> $2 + + etc... + +Dans l'exemple le variable-component SWC-variable aura pour valeur SWC résultat de la première parenthèse. + + +## Zip de YAML +Il est possible au lieu de fournir un yaml, de fournir un fichier zip. Cela permet de découper les YAML long en plusieurs fichiers. + +Dans le zip le contenu de la section <section><sous_section><sous_sous_section> sera placé dans un fichier sous_sous_section.yaml que l'on placera dans le dossier sous_section du dossier section. + +Au premier niveau il est possible de placer un fichier configuration.yaml qui servira de base à la génération du yaml. +A défaut de se fichier on utilisera comme base +```yaml +version: 0 +``` + +voici un example du contenu du zip : + +``` html +multiyaml.zip +├── application.yaml +├── compositeReferences.yaml +├── configuration.yaml +├── dataTypes +│ ├── smp_infraj.yaml +│ └── ts_infraj.yaml +└── references + ├── types_de_zones_etudes.yaml + +``` +## lors de l'importation du fichier yaml : + +* mettre le nom de l'application en minuscule, +* sans espace, +* sans accent, +* sans chiffre et +* sans caractères speciaux +# Aide fichier .csv + +## lors de l'ouverture du fichier csv via libre office: + +<span style="color: red">* sélectionner le séparateur en ";"</span> + +## lors de la création du fichier csv de Référence et de donnée : + +* cocher lors de l'enregistrement du fichier + * Éditer les paramètre du filtre + * Sélectionner le point virgule +* dans les données qui se trouvent dans les colonnes contenant des clés naturelles on attend : + * pas d'accents + * pas de majuscules + * pas de caratères spéciaux () , - : + * autorisé les _ et les . +* le nom des colonnes doive être le plus court possible +* le fichier doit être en UTF8 pour que les colonnes soient lisible (les caractères spéciaux ne passe pas sinon. ex : é, è, ç) + +## lors de l'importation de fichier csv dans l'application: + +* ouvrer la console avec F12 dans votre navigateur pour voir l'erreur de téléversement (erreur serveur) plus en détail. diff --git a/src/main/java/fr/inra/oresing/checker/CheckerFactory.java b/src/main/java/fr/inra/oresing/checker/CheckerFactory.java index ca8617e0d62f7740664d62bac7a01df14c1b4bce..bd62eebbc69656af300dc43c18dad03eb8f327f5 100644 --- a/src/main/java/fr/inra/oresing/checker/CheckerFactory.java +++ b/src/main/java/fr/inra/oresing/checker/CheckerFactory.java @@ -4,18 +4,28 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import fr.inra.oresing.groovy.GroovyContextHelper; import fr.inra.oresing.model.Application; import fr.inra.oresing.model.Configuration; +import fr.inra.oresing.model.ReferenceColumn; import fr.inra.oresing.model.VariableComponentKey; import fr.inra.oresing.model.internationalization.InternationalizationDisplay; +import fr.inra.oresing.persistence.DataRepository; import fr.inra.oresing.persistence.OreSiRepository; import fr.inra.oresing.persistence.ReferenceValueRepository; import fr.inra.oresing.rest.ApplicationResult; +import fr.inra.oresing.transformer.LineTransformer; +import fr.inra.oresing.transformer.TransformerFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -24,12 +34,21 @@ import java.util.stream.Stream; @Slf4j public class CheckerFactory { + @Deprecated public static final String COLUMNS = "columns"; + + @Deprecated public static final String VARIABLE_COMPONENT_KEY = "variableComponentKey"; - public static final String REQUIRED = "required"; + @Autowired private OreSiRepository repository; + @Autowired + private GroovyContextHelper groovyContextHelper; + + @Autowired + private TransformerFactory transformerFactory; + public ImmutableMap<VariableComponentKey, ReferenceLineChecker> getReferenceLineCheckers(Application app, String dataType) { return getLineCheckers(app, dataType).stream() .filter(lineChecker -> lineChecker instanceof ReferenceLineChecker) @@ -83,32 +102,39 @@ public class CheckerFactory { Configuration.CheckerDescription checkerDescription = variableDescription.getComponents().get(component).getChecker(); CheckerOnOneVariableComponentLineChecker variableComponentChecker; CheckerTarget checkerTarget = CheckerTarget.getInstance(variableComponentKey, app, repository.getRepository(app)); + LineTransformer transformer = Optional.ofNullable(checkerDescription.getParams()) + .map(transformationConfiguration -> transformerFactory.newTransformer(transformationConfiguration, app, checkerTarget)) + .orElseGet(transformerFactory::getNullTransformer); if ("Reference".equals(checkerDescription.getName())) { - variableComponentChecker = getCheckerOnReferenceChecker(app, dataType, locale, checkerDescription, checkerTarget); - } else if ("Date".equals(checkerDescription.getName())) { - String pattern = checkerDescription.getParams().get(DateLineChecker.PARAM_PATTERN); - variableComponentChecker = new DateLineChecker(checkerTarget, pattern, checkerDescription.getParams()); - } else if ("Integer".equals(checkerDescription.getName())) { - variableComponentChecker = new IntegerChecker(checkerTarget, checkerDescription.getParams()); - } else if ("Float".equals(checkerDescription.getName())) { - variableComponentChecker = new FloatChecker(checkerTarget, checkerDescription.getParams()); - } else if ("RegularExpression".equals(checkerDescription.getName())) { - String pattern = checkerDescription.getParams().get(RegularExpressionChecker.PARAM_PATTERN); - variableComponentChecker = new RegularExpressionChecker(checkerTarget, pattern, checkerDescription.getParams()); + variableComponentChecker = getCheckerOnReferenceChecker(app, dataType, locale, checkerDescription, checkerTarget, transformer); } else { - throw new IllegalArgumentException("checker inconnu " + checkerDescription.getName()); + final Configuration.CheckerConfigurationDescription configuration = checkerDescription.getParams(); + if ("Date".equals(checkerDescription.getName())) { + String pattern = configuration.getPattern(); + variableComponentChecker = new DateLineChecker(checkerTarget, pattern, configuration, transformer); + } else if ("Integer".equals(checkerDescription.getName())) { + Preconditions.checkState(configuration == null || !configuration.isCodify(), "codify avec checker " + checkerDescription.getName() + " sur le composant " + component + " de la variable " + variable + " du type de données " + dataType + " de l'application " + app.getName()); + variableComponentChecker = new IntegerChecker(checkerTarget, configuration, transformer); + } else if ("Float".equals(checkerDescription.getName())) { + Preconditions.checkState(configuration == null || !configuration.isCodify(), "codify avec checker " + checkerDescription.getName() + " sur le composant " + component + " de la variable " + variable + " du type de données " + dataType + " de l'application " + app.getName()); + variableComponentChecker = new FloatChecker(checkerTarget, configuration, transformer); + } else if ("RegularExpression".equals(checkerDescription.getName())) { + String pattern = configuration.getPattern(); + variableComponentChecker = new RegularExpressionChecker(checkerTarget, pattern, configuration, transformer); + } else { + throw new IllegalArgumentException("checker inconnu " + checkerDescription.getName()); + } } Preconditions.checkState(variableComponentChecker.getTarget().getTarget().equals(variableComponentKey)); checkersBuilder.add(variableComponentChecker); } } - private CheckerOnOneVariableComponentLineChecker getCheckerOnReferenceChecker(Application app, String dataType, String locale, Configuration.CheckerDescription checkerDescription, CheckerTarget checkerTarget) { + private CheckerOnOneVariableComponentLineChecker getCheckerOnReferenceChecker(Application app, String dataType, String locale, Configuration.CheckerDescription checkerDescription, CheckerTarget checkerTarget, LineTransformer transformer) { CheckerOnOneVariableComponentLineChecker variableComponentChecker; - String refType = checkerDescription.getParams().get(ReferenceLineChecker.PARAM_REFTYPE); + String refType = checkerDescription.getParams().getRefType(); ReferenceValueRepository referenceValueRepository = repository.getRepository(app).referenceValue(); ImmutableMap<String, UUID> referenceValues; - ImmutableMap<String, String> display = null; if (locale == null) { referenceValues = referenceValueRepository.getReferenceIdPerKeys(refType); } else { @@ -117,12 +143,8 @@ public class CheckerFactory { referenceIdAndDisplayPerKeys.entrySet().stream() .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().getUuid())) ); - display = ImmutableMap.copyOf( - referenceIdAndDisplayPerKeys.entrySet().stream() - .collect(Collectors.toMap(e -> e.getKey(), e -> getOrBuildDisplay(app, refType, dataType, locale, e))) - ); } - variableComponentChecker = new ReferenceLineChecker(checkerTarget, refType, referenceValues, display, checkerDescription.getParams()); + variableComponentChecker = new ReferenceLineChecker(checkerTarget, refType, referenceValues, checkerDescription.getParams(), transformer); return variableComponentChecker; } @@ -147,20 +169,30 @@ public class CheckerFactory { } private void addCheckersFromLineValidationDescriptions(Application app, LinkedHashMap<String, Configuration.LineValidationRuleDescription> lineValidationDescriptions, ImmutableSet.Builder<LineChecker> checkersBuilder, String param) { + ReferenceValueRepository referenceValueRepository = repository.getRepository(app).referenceValue(); + DataRepository dataRepository = repository.getRepository(app).data(); for (Map.Entry<String, Configuration.LineValidationRuleDescription> validationEntry : lineValidationDescriptions.entrySet()) { Configuration.LineValidationRuleDescription lineValidationRuleDescription = validationEntry.getValue(); Configuration.CheckerDescription checkerDescription = lineValidationRuleDescription.getChecker(); LineChecker lineChecker; - Map<String, String> params = checkerDescription.getParams(); - String pattern; + Configuration.CheckerConfigurationDescription configurationDescription = checkerDescription.getParams(); if (GroovyLineChecker.NAME.equals(checkerDescription.getName())) { - String expression = params.get(GroovyLineChecker.PARAM_EXPRESSION); - lineChecker = GroovyLineChecker.forExpression(expression, app, repository.getRepository(app), params); + String expression = configurationDescription.getGroovy().getExpression(); + Set<String> references = configurationDescription.getGroovy().getReferences(); + Set<String> dataTypes = configurationDescription.getGroovy().getDatatypes(); + ImmutableMap<String, Object> groovyContextForReferences = groovyContextHelper.getGroovyContextForReferences(referenceValueRepository, references); + ImmutableMap<String, Object> groovyContextForDataTypes = groovyContextHelper.getGroovyContextForDataTypes(dataRepository, dataTypes, app); + ImmutableMap<String, Object> context = ImmutableMap.<String, Object>builder() + .putAll(groovyContextForReferences) + .putAll(groovyContextForDataTypes) + .put("application", app) + .build(); + lineChecker = GroovyLineChecker.forExpression(expression, context, configurationDescription); checkersBuilder.add(lineChecker); } else { - List<CheckerTarget> checkerTargets = buildCheckerTarget(params, app); + List<CheckerTarget> checkerTargets = buildCheckerTarget(configurationDescription, app); if (checkerTargets != null) { - checkerTargets.forEach(checkerTarget -> buildCheckers(app, checkerDescription, params, checkerTarget, checkersBuilder)); + checkerTargets.forEach(checkerTarget -> buildCheckers(app, checkerDescription, checkerTarget, checkersBuilder)); } else { throw new IllegalArgumentException(String.format("Pour le checker de ligne %s, le paramètre %s doit être fourni.", checkerDescription.getName(), param)); } @@ -169,40 +201,40 @@ public class CheckerFactory { } } - private void buildCheckers(Application app, Configuration.CheckerDescription checkerDescription, Map<String, String> params, CheckerTarget target, ImmutableSet.Builder<LineChecker> checkersBuilder) { - String pattern; + private void buildCheckers(Application app, Configuration.CheckerDescription checkerDescription, CheckerTarget target, ImmutableSet.Builder<LineChecker> checkersBuilder) { + LineTransformer transformer = transformerFactory.newTransformer(checkerDescription.getParams(), app, target); + Configuration.CheckerConfigurationDescription checkerConfigurationDescription = checkerDescription.getParams(); switch (checkerDescription.getName()) { case "Date": - pattern = params.get(DateLineChecker.PARAM_PATTERN); - checkersBuilder.add(new DateLineChecker(target, pattern, params)); + checkersBuilder.add(new DateLineChecker(target, checkerConfigurationDescription.getPattern(), checkerConfigurationDescription, transformer)); break; case "Integer": - checkersBuilder.add(new IntegerChecker(target, params)); + checkersBuilder.add(new IntegerChecker(target, checkerConfigurationDescription, transformer)); break; case "Float": - checkersBuilder.add(new FloatChecker(target, params)); + checkersBuilder.add(new FloatChecker(target, checkerConfigurationDescription, transformer)); break; case "RegularExpression": - pattern = params.get(RegularExpressionChecker.PARAM_PATTERN); - checkersBuilder.add(new RegularExpressionChecker(target, pattern, params)); + checkersBuilder.add(new RegularExpressionChecker(target, checkerConfigurationDescription.getPattern(), checkerConfigurationDescription, transformer)); break; case "Reference": - String refType = checkerDescription.getParams().get(ReferenceLineChecker.PARAM_REFTYPE); + String refType = checkerConfigurationDescription.getRefType(); ReferenceValueRepository referenceValueRepository = repository.getRepository(app).referenceValue(); ImmutableMap<String, UUID> referenceValues = referenceValueRepository.getReferenceIdPerKeys(refType); - checkersBuilder.add(new ReferenceLineChecker(target, refType, referenceValues, null, params)); + checkersBuilder.add(new ReferenceLineChecker(target, refType, referenceValues, checkerConfigurationDescription, transformer)); break; default: throw new IllegalArgumentException("checker inconnu " + checkerDescription.getName()); } } - private List<CheckerTarget> buildCheckerTarget(Map<String, String> params, Application application) { - String columnsString = params.getOrDefault(COLUMNS, null); - String variableComponentKeyParam = params.getOrDefault(VARIABLE_COMPONENT_KEY, null); + private List<CheckerTarget> buildCheckerTarget(Configuration.CheckerConfigurationDescription params, Application application) { + String columnsString = params.getColumns(); + String variableComponentKeyParam = params.getVariableComponentKey(); if (!Strings.isNullOrEmpty(columnsString)) { return Stream.of(columnsString.split(",")) - .map(column -> CheckerTarget.getInstance(column, application, repository.getRepository(application))) + .map(ReferenceColumn::new) + .map(referenceColumn -> CheckerTarget.getInstance(referenceColumn, application, repository.getRepository(application))) .collect(Collectors.toList()); } else if (!Strings.isNullOrEmpty(variableComponentKeyParam) || !variableComponentKeyParam.matches("_")) { String[] split = variableComponentKeyParam.split("_"); diff --git a/src/main/java/fr/inra/oresing/checker/CheckerOnOneVariableComponentLineChecker.java b/src/main/java/fr/inra/oresing/checker/CheckerOnOneVariableComponentLineChecker.java index 329f4d3d7361aada3dfbd502198261524839eb1a..336f62251e1902ed307665c1aaa7097362a27e30 100644 --- a/src/main/java/fr/inra/oresing/checker/CheckerOnOneVariableComponentLineChecker.java +++ b/src/main/java/fr/inra/oresing/checker/CheckerOnOneVariableComponentLineChecker.java @@ -1,51 +1,56 @@ package fr.inra.oresing.checker; -import fr.inra.oresing.ValidationLevel; -import fr.inra.oresing.checker.decorators.CheckerDecorator; -import fr.inra.oresing.checker.decorators.DecoratorException; +import com.google.common.collect.ImmutableMap; +import fr.inra.oresing.model.Datum; +import fr.inra.oresing.model.ReferenceColumn; +import fr.inra.oresing.model.ReferenceDatum; import fr.inra.oresing.model.VariableComponentKey; +import fr.inra.oresing.rest.DefaultValidationCheckResult; import fr.inra.oresing.rest.ValidationCheckResult; +import fr.inra.oresing.transformer.LineTransformer; +import org.assertj.core.util.Strings; -import java.util.Map; - -public interface CheckerOnOneVariableComponentLineChecker extends LineChecker { +public interface CheckerOnOneVariableComponentLineChecker<C extends LineCheckerConfiguration> extends LineChecker<C> { CheckerTarget getTarget(); - default ValidationCheckResult check(Map<VariableComponentKey, String> values) { + LineTransformer getTransformer(); + + default ValidationCheckResult check(Datum datum) { + Datum transformedDatum = getTransformer().transform(datum); VariableComponentKey variableComponentKey = (VariableComponentKey) getTarget().getTarget(); - String value = values.get(variableComponentKey); - try { - ValidationCheckResult check = CheckerDecorator.check(values, value, getParams(), getTarget()); - if(ValidationLevel.WARN.equals(check.getLevel())){ - value = check.getMessage(); - }else{ - return check; + String value = transformedDatum.get(variableComponentKey); + ValidationCheckResult validationCheckResult; + if (Strings.isNullOrEmpty(value)) { + if (getConfiguration().isRequired()) { + CheckerTarget target = getTarget(); + validationCheckResult = DefaultValidationCheckResult.error(target.getInternationalizedKey("requiredValue"), ImmutableMap.of("target", target.getTarget())); + } else { + validationCheckResult = DefaultValidationCheckResult.success(); } - } catch (DecoratorException e) { - return e.getValidationCheckResult(); + } else { + validationCheckResult = check(value); } - return check(value); + return validationCheckResult; } @Override - default ValidationCheckResult checkReference(Map<String, String> values) { - String value = values.get(getTarget().getTarget()); - try { - ValidationCheckResult check = CheckerDecorator.check(values, value, getParams(), getTarget()); - if(ValidationLevel.WARN.equals(check.getLevel())){ - value = check.getMessage(); - }else{ - return check; + default ValidationCheckResult checkReference(ReferenceDatum referenceDatum) { + ReferenceDatum transformedReferenceDatum = getTransformer().transform(referenceDatum); + final ReferenceColumn column = (ReferenceColumn) getTarget().getTarget(); + String value = transformedReferenceDatum.get(column); + ValidationCheckResult validationCheckResult; + if (Strings.isNullOrEmpty(value)) { + if (getConfiguration().isRequired()) { + CheckerTarget target = getTarget(); + validationCheckResult = DefaultValidationCheckResult.error(target.getInternationalizedKey("requiredValue"), ImmutableMap.of("target", target.getTarget())); + } else { + validationCheckResult = DefaultValidationCheckResult.success(); } - } catch (DecoratorException e) { - return e.getValidationCheckResult(); + } else { + validationCheckResult = check(value); } - return check(value); - } - - default Map<String, String> getParams(){ - return Map.of(); + return validationCheckResult; } ValidationCheckResult check(String value); diff --git a/src/main/java/fr/inra/oresing/checker/CheckerTarget.java b/src/main/java/fr/inra/oresing/checker/CheckerTarget.java index fc35a2f7da60959fb8a5e8a69039febbf0775278..7950d40c0bdbfe680037d80caeb40396c4a4e5b7 100644 --- a/src/main/java/fr/inra/oresing/checker/CheckerTarget.java +++ b/src/main/java/fr/inra/oresing/checker/CheckerTarget.java @@ -2,6 +2,7 @@ package fr.inra.oresing.checker; import com.fasterxml.jackson.annotation.JsonIgnore; import fr.inra.oresing.model.Application; +import fr.inra.oresing.model.ReferenceColumn; import fr.inra.oresing.model.VariableComponentKey; import fr.inra.oresing.persistence.OreSiRepository; import lombok.Getter; @@ -18,26 +19,23 @@ abstract public class CheckerTarget<T>{ @JsonIgnore private OreSiRepository.RepositoryForApplication repository; private CheckerTargetType type; - public static CheckerTarget getInstance(Object target, Application application, OreSiRepository.RepositoryForApplication repository){ - CheckerTarget checkerTarget = null; - if(target instanceof VariableComponentKey){ - checkerTarget = new VariableComponentKeyCheckerTarget((VariableComponentKey) target); - } else if (target instanceof String){ - checkerTarget = new ColumnCheckerTarget((String) target); - } - if(checkerTarget!=null){ - checkerTarget.application = application; - checkerTarget.repository = repository; - } + + public static CheckerTarget getInstance(VariableComponentKey target, Application application, OreSiRepository.RepositoryForApplication repository){ + CheckerTarget checkerTarget = new VariableComponentKeyCheckerTarget(target); + checkerTarget.application = application; + checkerTarget.repository = repository; return checkerTarget; } - public String getInternationalizedKey(String key){ - if(CheckerTargetType.PARAM_COLUMN.equals(type)){ - return key+"WithColumn"; - } - return key; + + public static CheckerTarget getInstance(ReferenceColumn target, Application application, OreSiRepository.RepositoryForApplication repository){ + CheckerTarget checkerTarget = new ColumnCheckerTarget(target); + checkerTarget.application = application; + checkerTarget.repository = repository; + return checkerTarget; } + public abstract String getInternationalizedKey(String key); + public CheckerTarget(CheckerTargetType type, T target) { this.type = type; this.target = target; @@ -61,17 +59,28 @@ abstract public class CheckerTarget<T>{ return type; } } - public static class ColumnCheckerTarget extends CheckerTarget<String>{ - private ColumnCheckerTarget(String column) { + public static class ColumnCheckerTarget extends CheckerTarget<ReferenceColumn> { + + private ColumnCheckerTarget(ReferenceColumn column) { super(CheckerTargetType.PARAM_COLUMN, column); } + + @Override + public String getInternationalizedKey(String key){ + return key+"WithColumn"; + } } - public static class VariableComponentKeyCheckerTarget extends CheckerTarget<VariableComponentKey>{ + public static class VariableComponentKeyCheckerTarget extends CheckerTarget<VariableComponentKey> { private VariableComponentKeyCheckerTarget(VariableComponentKey variableComponentKey) { super(CheckerTargetType.PARAM_VARIABLE_COMPONENT_KEY, variableComponentKey); } + + @Override + public String getInternationalizedKey(String key){ + return key; + } } @Override diff --git a/src/main/java/fr/inra/oresing/checker/DateLineChecker.java b/src/main/java/fr/inra/oresing/checker/DateLineChecker.java index 8160a0a3c8a8fd97489c4dc29de8992719f92f23..95134a330a73a026b2f64a866643c4e05b4cd099 100644 --- a/src/main/java/fr/inra/oresing/checker/DateLineChecker.java +++ b/src/main/java/fr/inra/oresing/checker/DateLineChecker.java @@ -1,22 +1,25 @@ package fr.inra.oresing.checker; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.collect.ImmutableMap; import fr.inra.oresing.rest.ValidationCheckResult; +import fr.inra.oresing.transformer.LineTransformer; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.TemporalAccessor; import java.util.Map; -public class DateLineChecker implements CheckerOnOneVariableComponentLineChecker { +public class DateLineChecker implements CheckerOnOneVariableComponentLineChecker<DateLineCheckerConfiguration> { public static final String PARAM_PATTERN = "pattern"; - public static final String PARAM_DURATION = "duration"; public static final String PARAM_DATE_TIME_FORMATTER = "dateTimeFormatter"; public static final String PARAM_DATE = "date"; public static final String PATTERN_DATE_REGEXP = "^date:.{19}:"; private final CheckerTarget target; - private final Map<String, String> params; + private final DateLineCheckerConfiguration configuration; + @JsonIgnore + private final LineTransformer transformer; public CheckerTarget getTarget(){ return this.target; @@ -29,11 +32,12 @@ public class DateLineChecker implements CheckerOnOneVariableComponentLineChecker return formattedDate.replaceAll(PATTERN_DATE_REGEXP, ""); } - public DateLineChecker(CheckerTarget target, String pattern, Map<String, String> params) { - this.params = params; + public DateLineChecker(CheckerTarget target, String pattern, DateLineCheckerConfiguration configuration, LineTransformer transformer) { + this.configuration = configuration; this.target = target; this.dateTimeFormatter = DateTimeFormatter.ofPattern(pattern); this.pattern = pattern; + this.transformer = transformer; } public String getPattern() { @@ -64,7 +68,12 @@ public class DateLineChecker implements CheckerOnOneVariableComponentLineChecker } @Override - public Map<String, String> getParams() { - return params; + public DateLineCheckerConfiguration getConfiguration() { + return configuration; + } + + @Override + public LineTransformer getTransformer() { + return transformer; } } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/DateLineCheckerConfiguration.java b/src/main/java/fr/inra/oresing/checker/DateLineCheckerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..32d38cfe954a91f2b7807c69266e391f4b8088d4 --- /dev/null +++ b/src/main/java/fr/inra/oresing/checker/DateLineCheckerConfiguration.java @@ -0,0 +1,17 @@ +package fr.inra.oresing.checker; + +/** + * Configuration pour un checker de type "Date" + */ +public interface DateLineCheckerConfiguration extends LineCheckerConfiguration { + + /** + * Le format dans lequel doit être la la date qui sera validée (par exemple, "dd/MM/yyyy") + */ + String getPattern(); + + /** + * La {@link fr.inra.oresing.model.Duration} pour cette donnée. + */ + String getDuration(); +} diff --git a/src/main/java/fr/inra/oresing/checker/FloatChecker.java b/src/main/java/fr/inra/oresing/checker/FloatChecker.java index 07d6ee26ab0ae6a317f83576041ba6992feaded3..16aed69b93ce1a31013fcffe21bf92777fc51348 100644 --- a/src/main/java/fr/inra/oresing/checker/FloatChecker.java +++ b/src/main/java/fr/inra/oresing/checker/FloatChecker.java @@ -1,21 +1,26 @@ package fr.inra.oresing.checker; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.collect.ImmutableMap; import fr.inra.oresing.rest.DefaultValidationCheckResult; import fr.inra.oresing.rest.ValidationCheckResult; +import fr.inra.oresing.transformer.LineTransformer; -import java.util.Map; - -public class FloatChecker implements CheckerOnOneVariableComponentLineChecker { +public class FloatChecker implements CheckerOnOneVariableComponentLineChecker<FloatCheckerConfiguration> { private final CheckerTarget target; - private final Map<String, String> params; + private final FloatCheckerConfiguration configuration; + + @JsonIgnore + private final LineTransformer transformer; public CheckerTarget getTarget(){ return this.target; } - public FloatChecker(CheckerTarget target, Map<String, String> params) { - this.params = params;this.target = target; + public FloatChecker(CheckerTarget target, FloatCheckerConfiguration configuration, LineTransformer transformer) { + this.target = target; + this.configuration = configuration; + this.transformer = transformer; } @Override @@ -34,7 +39,12 @@ public class FloatChecker implements CheckerOnOneVariableComponentLineChecker { } @Override - public Map<String, String> getParams() { - return params; + public FloatCheckerConfiguration getConfiguration() { + return configuration; + } + + @Override + public LineTransformer getTransformer() { + return transformer; } } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/FloatCheckerConfiguration.java b/src/main/java/fr/inra/oresing/checker/FloatCheckerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..34bfa14f7c520d317d5153ccca5e905c9f9709fb --- /dev/null +++ b/src/main/java/fr/inra/oresing/checker/FloatCheckerConfiguration.java @@ -0,0 +1,7 @@ +package fr.inra.oresing.checker; + +/** + * Configuration pour un checker de type nombre à virgule + */ +public interface FloatCheckerConfiguration extends LineCheckerConfiguration { +} diff --git a/src/main/java/fr/inra/oresing/checker/GroovyConfiguration.java b/src/main/java/fr/inra/oresing/checker/GroovyConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..2d3a15a73b2672afccd5cd88397733e27a1929b8 --- /dev/null +++ b/src/main/java/fr/inra/oresing/checker/GroovyConfiguration.java @@ -0,0 +1,8 @@ +package fr.inra.oresing.checker; + +import fr.inra.oresing.model.GroovyDataInjectionConfiguration; + +public interface GroovyConfiguration extends GroovyDataInjectionConfiguration { + + String getExpression(); +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/GroovyLineChecker.java b/src/main/java/fr/inra/oresing/checker/GroovyLineChecker.java index 6b551b48be1deeb107d0c680aaeb8236565b9093..fb29c552e884e62e8096407007e6bc74b20290e6 100644 --- a/src/main/java/fr/inra/oresing/checker/GroovyLineChecker.java +++ b/src/main/java/fr/inra/oresing/checker/GroovyLineChecker.java @@ -3,117 +3,58 @@ package fr.inra.oresing.checker; import com.google.common.collect.ImmutableMap; import fr.inra.oresing.groovy.BooleanGroovyExpression; import fr.inra.oresing.groovy.GroovyExpression; -import fr.inra.oresing.model.Application; -import fr.inra.oresing.model.ReferenceValue; -import fr.inra.oresing.model.VariableComponentKey; -import fr.inra.oresing.persistence.DataRow; -import fr.inra.oresing.persistence.OreSiRepository; +import fr.inra.oresing.model.Datum; +import fr.inra.oresing.model.ReferenceDatum; +import fr.inra.oresing.model.SomethingThatCanProvideEvaluationContext; import fr.inra.oresing.rest.DefaultValidationCheckResult; -import fr.inra.oresing.rest.DownloadDatasetQuery; import fr.inra.oresing.rest.ValidationCheckResult; -import java.util.*; +import java.util.Optional; -public class GroovyLineChecker implements LineChecker { +public class GroovyLineChecker implements LineChecker<GroovyLineCheckerConfiguration> { public static final String NAME = "GroovyExpression"; - public static final String PARAM_EXPRESSION = "expression"; - - public static final String PARAM_REFERENCES = "references"; + private final BooleanGroovyExpression expression; - public static final String PARAM_DATATYPES = "datatypes"; + private final ImmutableMap<String, Object> context; - private final BooleanGroovyExpression expression; - private final Application application; - private final OreSiRepository.RepositoryForApplication repository; - private Map<String, String> params = new HashMap<>(); + private final GroovyLineCheckerConfiguration configuration; - private GroovyLineChecker(BooleanGroovyExpression expression, Application app, OreSiRepository.RepositoryForApplication repository, Map<String, String> params) { + private GroovyLineChecker(BooleanGroovyExpression expression, ImmutableMap<String, Object> context, GroovyLineCheckerConfiguration configuration) { this.expression = expression; - this.application = app; - this.repository = repository; - this.params = params; + this.context = context; + this.configuration = configuration; } - public static GroovyLineChecker forExpression(String expression, Application app, OreSiRepository.RepositoryForApplication repository, Map<String, String> params) { - BooleanGroovyExpression groovyExpression = BooleanGroovyExpression.forExpression(expression); - return new GroovyLineChecker(groovyExpression, app, repository, params); + public static GroovyLineChecker forExpression(String expression, ImmutableMap<String, Object> context, GroovyLineCheckerConfiguration configuration) { + return new GroovyLineChecker(BooleanGroovyExpression.forExpression(expression), context, configuration); } public static Optional<GroovyExpression.CompilationError> validateExpression(String expression) { return GroovyExpression.validateExpression(expression); } - public static ImmutableMap<String, Object> buildContext(Object datum, Application application, Map<String, String> params, OreSiRepository.RepositoryForApplication repository) { - Map<String, List<ReferenceValue>> references = new HashMap<>(); - Map<String, List<DataRow>> datatypes = new HashMap<>(); - Map<String, List<Map<String, String>>> referencesValues = new HashMap<>(); - Map<String, List<Map<String, Map<String, String>>>> datatypesValues = new HashMap<>(); - Optional.ofNullable(params) - .map(p -> p.get(PARAM_REFERENCES)) - .ifPresent(refs -> { - Arrays.stream(refs.split(",")) - .forEach(ref -> { - List<ReferenceValue> allByReferenceType = repository.referenceValue().findAllByReferenceType(ref); - references.put(ref, allByReferenceType); - allByReferenceType.stream() - .map(referenceValue -> referenceValue.getRefValues()) - .forEach(values -> referencesValues.computeIfAbsent(ref, k->new LinkedList<>()).add(values)); - }); - }); - - Optional.ofNullable(params) - .map(p -> p.get(PARAM_DATATYPES)) - .ifPresent(datas -> { - Arrays.stream(datas.split(",")) - .forEach(dataType -> { - List<DataRow> allByDataType = repository.data().findAllByDataType(DownloadDatasetQuery.buildDownloadDatasetQuery(null, null, dataType, application)); - datatypes.put(dataType, allByDataType); - allByDataType.stream() - .map(datatValues -> datatValues.getValues()) - .forEach(dv -> datatypesValues.computeIfAbsent(dataType, k -> new LinkedList<>()).add(dv)); - }); - }); - ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder(); - builder.put("datum", datum); - if (datum != null && datum instanceof Map && ((Map<?, ?>) datum).keySet().stream().anyMatch(e -> e instanceof VariableComponentKey)) { - Map<String, Map<String, String>> datumByVariableAndComponent = new HashMap<>(); - for (Map.Entry<VariableComponentKey, String> entry : ((Map<VariableComponentKey, String>) datum).entrySet()) { - datumByVariableAndComponent - .computeIfAbsent(entry.getKey().getVariable(), k -> new HashMap<String, String>()) - .put(entry.getKey().getComponent(), entry.getValue()); - } - builder.put("datumByVariableAndComponent", datumByVariableAndComponent); - } - builder.put("application", application); - builder.put("references", references); - builder.put("referencesValues", referencesValues); - builder.put("datatypes", datatypes); - builder.put("datatypesValues", datatypesValues); - builder.put("params", Optional.ofNullable(params).orElseGet(HashMap::new)); - return builder.build(); + @Override + public GroovyLineCheckerConfiguration getConfiguration() { + return configuration; } - public ValidationCheckResult check(Map<VariableComponentKey, String> datum) { - Map<String, Map<String, String>> datumAsMap = new LinkedHashMap<>(); - for (Map.Entry<VariableComponentKey, String> entry2 : datum.entrySet()) { - String variable = entry2.getKey().getVariable(); - String component = entry2.getKey().getComponent(); - String value = entry2.getValue(); - datumAsMap.computeIfAbsent(variable, k -> new LinkedHashMap<>()).put(component, value); - } - Map<String, Object> context = buildContext(datumAsMap, application, params, repository); - return evaluate(context); + @Override + public ValidationCheckResult check(Datum datum) { + return doCheck(datum); } @Override - public ValidationCheckResult checkReference(Map<String, String> datum) { - Map<String, Object> context = buildContext(datum, application, params, repository); - return evaluate(context); + public ValidationCheckResult checkReference(ReferenceDatum referenceDatum) { + return doCheck(referenceDatum); } - private ValidationCheckResult evaluate(Map<String, Object> context) { + private ValidationCheckResult doCheck(SomethingThatCanProvideEvaluationContext somethingThatCanProvideEvaluationContext) { + ImmutableMap<String, Object> context = ImmutableMap.<String, Object>builder() + .putAll(this.context) + .putAll(somethingThatCanProvideEvaluationContext.getEvaluationContext()) + .build(); Boolean evaluation = expression.evaluate(context); if (evaluation) { return DefaultValidationCheckResult.success(); diff --git a/src/main/java/fr/inra/oresing/checker/GroovyLineCheckerConfiguration.java b/src/main/java/fr/inra/oresing/checker/GroovyLineCheckerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..b18a6e0bd4f498d860191806b613a97624c04c55 --- /dev/null +++ b/src/main/java/fr/inra/oresing/checker/GroovyLineCheckerConfiguration.java @@ -0,0 +1,16 @@ +package fr.inra.oresing.checker; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang3.StringUtils; +import org.assertj.core.util.Streams; + +import java.util.Set; + +/** + * Configuration pour un checker de type "Expression Groovy" + */ +public interface GroovyLineCheckerConfiguration extends LineCheckerConfiguration { + + GroovyConfiguration getGroovy(); +} diff --git a/src/main/java/fr/inra/oresing/checker/IntegerChecker.java b/src/main/java/fr/inra/oresing/checker/IntegerChecker.java index 32b7ed5330309aea759a2c66ae582c02466649db..48c54f84d99326035b03c61438c2e1d3198f0fd9 100644 --- a/src/main/java/fr/inra/oresing/checker/IntegerChecker.java +++ b/src/main/java/fr/inra/oresing/checker/IntegerChecker.java @@ -1,22 +1,25 @@ package fr.inra.oresing.checker; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.collect.ImmutableMap; import fr.inra.oresing.rest.DefaultValidationCheckResult; import fr.inra.oresing.rest.ValidationCheckResult; +import fr.inra.oresing.transformer.LineTransformer; -import java.util.Map; - -public class IntegerChecker implements CheckerOnOneVariableComponentLineChecker { +public class IntegerChecker implements CheckerOnOneVariableComponentLineChecker<IntegerCheckerConfiguration> { private final CheckerTarget target; - private final Map<String, String> params; + private final IntegerCheckerConfiguration configuration; + @JsonIgnore + private final LineTransformer transformer; public CheckerTarget getTarget(){ return this.target; } - public IntegerChecker(CheckerTarget target, Map<String, String> params) { - this.params = params; + public IntegerChecker(CheckerTarget target, IntegerCheckerConfiguration configuration, LineTransformer transformer) { + this.configuration = configuration; this.target = target; + this.transformer = transformer; } @Override @@ -34,7 +37,12 @@ public class IntegerChecker implements CheckerOnOneVariableComponentLineChecker } @Override - public Map<String, String> getParams() { - return params; + public IntegerCheckerConfiguration getConfiguration() { + return configuration; + } + + @Override + public LineTransformer getTransformer() { + return transformer; } } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/IntegerCheckerConfiguration.java b/src/main/java/fr/inra/oresing/checker/IntegerCheckerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..38664cb817c0ff587a96828b2ddb50b1c1b479a5 --- /dev/null +++ b/src/main/java/fr/inra/oresing/checker/IntegerCheckerConfiguration.java @@ -0,0 +1,7 @@ +package fr.inra.oresing.checker; + +/** + * Configuration pour un checker de type nombre entier + */ +public interface IntegerCheckerConfiguration extends LineCheckerConfiguration { +} diff --git a/src/main/java/fr/inra/oresing/checker/LineChecker.java b/src/main/java/fr/inra/oresing/checker/LineChecker.java index 06cecbd4ca65a185e2bc7c138c1a161041fe185c..5dd6021c500008351ce881d2e18c2ecac5facaaa 100644 --- a/src/main/java/fr/inra/oresing/checker/LineChecker.java +++ b/src/main/java/fr/inra/oresing/checker/LineChecker.java @@ -1,15 +1,12 @@ package fr.inra.oresing.checker; -import fr.inra.oresing.model.VariableComponentKey; +import fr.inra.oresing.model.Datum; +import fr.inra.oresing.model.ReferenceDatum; import fr.inra.oresing.rest.ValidationCheckResult; -import java.util.Map; +public interface LineChecker<C extends LineCheckerConfiguration> { -public interface LineChecker { - - ValidationCheckResult check(Map<VariableComponentKey, String> values); - ValidationCheckResult checkReference(Map<String, String> values); - default Map<String, String> getParams(){ - return Map.of(); - } + ValidationCheckResult check(Datum values); + ValidationCheckResult checkReference(ReferenceDatum referenceDatum); + C getConfiguration(); } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/LineCheckerConfiguration.java b/src/main/java/fr/inra/oresing/checker/LineCheckerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..3460a2e2bce8a04bdecd4f40e4323471d2d795d8 --- /dev/null +++ b/src/main/java/fr/inra/oresing/checker/LineCheckerConfiguration.java @@ -0,0 +1,14 @@ +package fr.inra.oresing.checker; + +import fr.inra.oresing.transformer.TransformationConfiguration; + +/** + * Indique qu'un objet a vocation à contenir des paramètres de configuration pour configurer un {@link LineChecker} + */ +public interface LineCheckerConfiguration extends TransformationConfiguration { + + /** + * Indique la valeur est obligatoire. + */ + boolean isRequired(); +} diff --git a/src/main/java/fr/inra/oresing/checker/ReferenceLineChecker.java b/src/main/java/fr/inra/oresing/checker/ReferenceLineChecker.java index 617f70054a6250d26474121e3d74bf3b1fdfd682..5d7f30b2ada658c18bea04b988b5a0e1e4d17e66 100644 --- a/src/main/java/fr/inra/oresing/checker/ReferenceLineChecker.java +++ b/src/main/java/fr/inra/oresing/checker/ReferenceLineChecker.java @@ -1,24 +1,27 @@ package fr.inra.oresing.checker; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.collect.ImmutableMap; +import fr.inra.oresing.transformer.LineTransformer; -import java.util.Map; import java.util.UUID; -public class ReferenceLineChecker implements CheckerOnOneVariableComponentLineChecker { +public class ReferenceLineChecker implements CheckerOnOneVariableComponentLineChecker<ReferenceLineCheckerConfiguration> { - public static final String PARAM_REFTYPE = "refType"; private final String reference; public ImmutableMap<String, UUID> referenceValues; - public ImmutableMap<String, String> display; - private final Map<String, String> params; + private final ReferenceLineCheckerConfiguration configuration; private final CheckerTarget target; - public ReferenceLineChecker(CheckerTarget target, String reference, ImmutableMap<String, UUID> referenceValues, ImmutableMap<String, String> display, Map<String, String> params) { - this.params = params; + + @JsonIgnore + private final LineTransformer transformer; + + public ReferenceLineChecker(CheckerTarget target, String reference, ImmutableMap<String, UUID> referenceValues, ReferenceLineCheckerConfiguration configuration, LineTransformer transformer) { + this.configuration = configuration; this.target = target; this.reference = reference; this.referenceValues = referenceValues; - this.display = display; + this.transformer = transformer; } public ImmutableMap<String, UUID> getReferenceValues() { @@ -53,7 +56,12 @@ public class ReferenceLineChecker implements CheckerOnOneVariableComponentLineCh } @Override - public Map<String, String> getParams() { - return params; + public ReferenceLineCheckerConfiguration getConfiguration() { + return configuration; + } + + @Override + public LineTransformer getTransformer() { + return transformer; } } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/ReferenceLineCheckerConfiguration.java b/src/main/java/fr/inra/oresing/checker/ReferenceLineCheckerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..eaa05911d8eb8fcd5814b8c05b4bdd1ede43c810 --- /dev/null +++ b/src/main/java/fr/inra/oresing/checker/ReferenceLineCheckerConfiguration.java @@ -0,0 +1,13 @@ +package fr.inra.oresing.checker; + +/** + * Configuration pour un checker de type "Reference" qui permet de s'assurer qu'une donnée + * est bien une valeur parmi celle du référentiel. + */ +public interface ReferenceLineCheckerConfiguration extends LineCheckerConfiguration { + + /** + * Le référentiel dans lequel la valeur vérifiée devra être contenu + */ + String getRefType(); +} diff --git a/src/main/java/fr/inra/oresing/checker/RegularExpressionChecker.java b/src/main/java/fr/inra/oresing/checker/RegularExpressionChecker.java index 057f2e9c4d5d79317779d39d7f5a63cf30cad658..3ff89361cd55a748e620b3872b571ff773d5f66e 100644 --- a/src/main/java/fr/inra/oresing/checker/RegularExpressionChecker.java +++ b/src/main/java/fr/inra/oresing/checker/RegularExpressionChecker.java @@ -1,32 +1,34 @@ package fr.inra.oresing.checker; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.collect.ImmutableMap; import fr.inra.oresing.rest.DefaultValidationCheckResult; import fr.inra.oresing.rest.ValidationCheckResult; +import fr.inra.oresing.transformer.LineTransformer; -import java.util.Map; import java.util.function.Predicate; import java.util.regex.Pattern; -public class RegularExpressionChecker implements CheckerOnOneVariableComponentLineChecker { - - public static final String PARAM_PATTERN = "pattern"; +public class RegularExpressionChecker implements CheckerOnOneVariableComponentLineChecker<RegularExpressionCheckerConfiguration> { private final String patternString; private final Predicate<String> predicate; + @JsonIgnore + private final LineTransformer transformer; private final CheckerTarget target; - private final Map<String, String> params; + private final RegularExpressionCheckerConfiguration configuration; public CheckerTarget getTarget(){ return this.target; } - public RegularExpressionChecker(CheckerTarget target, String patternString, Map<String, String> params) { - this.params = params; + public RegularExpressionChecker(CheckerTarget target, String patternString, RegularExpressionCheckerConfiguration configuration, LineTransformer transformer) { + this.configuration = configuration; this.target = target; this.patternString = patternString; predicate = Pattern.compile(patternString).asMatchPredicate(); + this.transformer = transformer; } @Override @@ -45,7 +47,12 @@ public class RegularExpressionChecker implements CheckerOnOneVariableComponentLi } @Override - public Map<String, String> getParams() { - return params; + public RegularExpressionCheckerConfiguration getConfiguration() { + return configuration; + } + + @Override + public LineTransformer getTransformer() { + return transformer; } } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/RegularExpressionCheckerConfiguration.java b/src/main/java/fr/inra/oresing/checker/RegularExpressionCheckerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..064c5e2de8263ac5f54d106ad64bff1f03c8e7ef --- /dev/null +++ b/src/main/java/fr/inra/oresing/checker/RegularExpressionCheckerConfiguration.java @@ -0,0 +1,12 @@ +package fr.inra.oresing.checker; + +/** + * Configuration pour un checker de type expression régulière + */ +public interface RegularExpressionCheckerConfiguration extends LineCheckerConfiguration { + + /** + * L'expression régulière à laquelle doit être conforme la valeur qui sera vérifiée. + */ + String getPattern(); +} diff --git a/src/main/java/fr/inra/oresing/checker/decorators/CheckerDecorator.java b/src/main/java/fr/inra/oresing/checker/decorators/CheckerDecorator.java deleted file mode 100644 index c119739de0c58717ae79a2255f51a482ef3ec830..0000000000000000000000000000000000000000 --- a/src/main/java/fr/inra/oresing/checker/decorators/CheckerDecorator.java +++ /dev/null @@ -1,28 +0,0 @@ -package fr.inra.oresing.checker.decorators; - -import fr.inra.oresing.ValidationLevel; -import fr.inra.oresing.checker.CheckerTarget; -import fr.inra.oresing.rest.DefaultValidationCheckResult; -import fr.inra.oresing.rest.ValidationCheckResult; - -import java.util.List; -import java.util.Map; - -public class CheckerDecorator { - public static List<ICheckerDecorator> checkerDecorators = List.of( new CodifyDecorator(),new GroovyDecorator(), new RequiredDecorator()); - - public static <T> ValidationCheckResult check(Map<T, String> values, String value, Map<String, String> params, CheckerTarget target) throws DecoratorException { - if(params == null || params.isEmpty()){ - return DefaultValidationCheckResult.warn(value, null); - } - for (ICheckerDecorator checkerDecorator : checkerDecorators) { - ValidationCheckResult check = checkerDecorator.check(values, value, params, target); - if(ValidationLevel.WARN.equals(check.getLevel())){ - value = check.getMessage(); - }else{ - return check; - } - } - return DefaultValidationCheckResult.warn(value, null); - } -} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/decorators/CodifyDecorator.java b/src/main/java/fr/inra/oresing/checker/decorators/CodifyDecorator.java deleted file mode 100644 index 4957466597a0831243c3e2d4d17c0a28464eb25b..0000000000000000000000000000000000000000 --- a/src/main/java/fr/inra/oresing/checker/decorators/CodifyDecorator.java +++ /dev/null @@ -1,26 +0,0 @@ -package fr.inra.oresing.checker.decorators; - -import fr.inra.oresing.checker.CheckerTarget; -import fr.inra.oresing.rest.DefaultValidationCheckResult; -import fr.inra.oresing.rest.OreSiService; -import fr.inra.oresing.rest.ValidationCheckResult; -import org.assertj.core.util.Strings; - -import java.util.Map; -import java.util.Optional; - -public class CodifyDecorator implements ICheckerDecorator { - public static final String PARAMS_CODIFY = "codify"; - - public ValidationCheckResult check(Map<? extends Object, String> values, String value, Map<String, String> params, CheckerTarget target) throws DecoratorException { - - boolean codify = params.containsKey(PARAMS_CODIFY) && - Optional.ofNullable(params.get(PARAMS_CODIFY)) - .map(req -> req == null || Boolean.parseBoolean(req)) - .orElse(false); - if (codify && !Strings.isNullOrEmpty(value)) { - value = OreSiService.escapeKeyComponent(value); - } - return DefaultValidationCheckResult.warn(value, null); - } -} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/decorators/DecoratorException.java b/src/main/java/fr/inra/oresing/checker/decorators/DecoratorException.java deleted file mode 100644 index 6d80334492670f62c361f660fe4dee800ec10113..0000000000000000000000000000000000000000 --- a/src/main/java/fr/inra/oresing/checker/decorators/DecoratorException.java +++ /dev/null @@ -1,15 +0,0 @@ -package fr.inra.oresing.checker.decorators; - -import fr.inra.oresing.rest.ValidationCheckResult; - -public class DecoratorException extends Throwable { - ValidationCheckResult validationCheckResult; - - public DecoratorException(ValidationCheckResult validationCheckResult) { - this.validationCheckResult = validationCheckResult; - } - - public ValidationCheckResult getValidationCheckResult() { - return validationCheckResult; - } -} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/decorators/GroovyDecorator.java b/src/main/java/fr/inra/oresing/checker/decorators/GroovyDecorator.java deleted file mode 100644 index cdfdbd4f54d9263ee65e500160744ec7eef8451e..0000000000000000000000000000000000000000 --- a/src/main/java/fr/inra/oresing/checker/decorators/GroovyDecorator.java +++ /dev/null @@ -1,28 +0,0 @@ -package fr.inra.oresing.checker.decorators; - -import com.google.common.collect.ImmutableMap; -import fr.inra.oresing.checker.CheckerTarget; -import fr.inra.oresing.checker.GroovyLineChecker; -import fr.inra.oresing.groovy.GroovyExpression; -import fr.inra.oresing.rest.DefaultValidationCheckResult; -import fr.inra.oresing.rest.ValidationCheckResult; - -import java.util.Map; -import java.util.Optional; - -public class GroovyDecorator implements ICheckerDecorator { - public static final String PARAMS_GROOVY = "groovy"; - - public ValidationCheckResult check(Map<? extends Object, String> values, String value, Map<String, String> params, CheckerTarget target) throws DecoratorException { - - GroovyExpression groovyExpression=Optional.ofNullable(params.get(PARAMS_GROOVY)) - .map(req -> GroovyExpression.forExpression(req)) - .orElse((GroovyExpression) null); - if (groovyExpression!=null) { - ImmutableMap<String, Object> context = GroovyLineChecker.buildContext(values, target.getApplication(), params, target.getRepository()); - value = groovyExpression.evaluate(context).toString(); - - } - return DefaultValidationCheckResult.warn(value, null); - } -} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/decorators/ICheckerDecorator.java b/src/main/java/fr/inra/oresing/checker/decorators/ICheckerDecorator.java deleted file mode 100644 index 413b9357adfaa5bffc566e4e3c88fd094a493187..0000000000000000000000000000000000000000 --- a/src/main/java/fr/inra/oresing/checker/decorators/ICheckerDecorator.java +++ /dev/null @@ -1,10 +0,0 @@ -package fr.inra.oresing.checker.decorators; - -import fr.inra.oresing.checker.CheckerTarget; -import fr.inra.oresing.rest.ValidationCheckResult; - -import java.util.Map; - -public interface ICheckerDecorator { - ValidationCheckResult check(Map<? extends Object, String> values, String value, Map<String, String> params, CheckerTarget target) throws DecoratorException; -} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/checker/decorators/RequiredDecorator.java b/src/main/java/fr/inra/oresing/checker/decorators/RequiredDecorator.java deleted file mode 100644 index 3f65ecb18596690755e524d447634e33c04f5a1e..0000000000000000000000000000000000000000 --- a/src/main/java/fr/inra/oresing/checker/decorators/RequiredDecorator.java +++ /dev/null @@ -1,27 +0,0 @@ -package fr.inra.oresing.checker.decorators; - -import com.google.common.collect.ImmutableMap; -import fr.inra.oresing.checker.CheckerTarget; -import fr.inra.oresing.rest.DefaultValidationCheckResult; -import fr.inra.oresing.rest.ValidationCheckResult; -import org.assertj.core.util.Strings; - -import java.util.Map; -import java.util.Optional; - -public class RequiredDecorator implements ICheckerDecorator { - public static final String PARAMS_REQUIRED = "required"; - - public ValidationCheckResult check(Map<? extends Object, String> values, String value, Map<String, String> params, CheckerTarget target) throws DecoratorException { - boolean required = params.containsKey(PARAMS_REQUIRED) && - Optional.ofNullable(params.get(PARAMS_REQUIRED)) - .map(req->req==null || Boolean.parseBoolean(req)) - .orElse(false); - if (required && Strings.isNullOrEmpty(value)) { - throw new DecoratorException(DefaultValidationCheckResult.error(target.getInternationalizedKey("requiredValue"), ImmutableMap.of("target", target.getTarget()))); - }else if (!required && Strings.isNullOrEmpty(value)) { - return DefaultValidationCheckResult.success(); - } - return DefaultValidationCheckResult.warn(value, null); - } -} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/groovy/GroovyContextHelper.java b/src/main/java/fr/inra/oresing/groovy/GroovyContextHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..c0a377639f5b55563603c052a83f7d58abd76172 --- /dev/null +++ b/src/main/java/fr/inra/oresing/groovy/GroovyContextHelper.java @@ -0,0 +1,84 @@ +package fr.inra.oresing.groovy; + +import com.google.common.collect.ImmutableMap; +import fr.inra.oresing.model.Application; +import fr.inra.oresing.model.ReferenceValue; +import fr.inra.oresing.persistence.DataRepository; +import fr.inra.oresing.persistence.DataRow; +import fr.inra.oresing.persistence.ReferenceValueRepository; +import fr.inra.oresing.rest.DownloadDatasetQuery; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Component +@Slf4j +public class GroovyContextHelper { + + public ImmutableMap<String, Object> getGroovyContextForReferences(ReferenceValueRepository referenceValueRepository, Set<String> refs) { + Map<String, List<ReferenceValueDecorator>> references = new HashMap<>(); + Map<String, List<Map<String, String>>> referencesValues = new HashMap<>(); + refs.forEach(ref -> { + List<ReferenceValue> allByReferenceType = referenceValueRepository.findAllByReferenceType(ref); + allByReferenceType.stream() + .map(ReferenceValueDecorator::new) + .forEach(referenceValue -> references.computeIfAbsent(ref, k -> new LinkedList<>()).add(referenceValue)); + allByReferenceType.stream() + .map(ReferenceValue::getRefValues) + .forEach(values -> referencesValues.computeIfAbsent(ref, k -> new LinkedList<>()).add(values)); + }); + return ImmutableMap.<String, Object>builder() + .put("references", references) + .put("referencesValues", referencesValues) + .build(); + } + + public ImmutableMap<String, Object> getGroovyContextForDataTypes(DataRepository dataRepository, Set<String> dataTypes, @Deprecated Application application) { + Map<String, List<DataRow>> datatypes = new HashMap<>(); + Map<String, List<Map<String, Map<String, String>>>> datatypesValues = new HashMap<>(); + dataTypes.forEach(dataType -> { + List<DataRow> allByDataType = dataRepository.findAllByDataType(DownloadDatasetQuery.buildDownloadDatasetQuery(null, null, dataType, application)); + datatypes.put(dataType, allByDataType); + allByDataType.stream() + .map(datatValues -> datatValues.getValues()) + .forEach(dv -> datatypesValues.computeIfAbsent(dataType, k -> new LinkedList<>()).add(dv)); + }); + return ImmutableMap.<String, Object>builder() + .put("datatypes", datatypes) + .put("datatypesValues", datatypesValues) + .build(); + } + + /** + * On expose pas directement les entités dans le contexte Groovy mais on contrôle un peu les types + */ + private static class ReferenceValueDecorator { + + private final ReferenceValue decorated; + + public ReferenceValueDecorator(ReferenceValue decorated) { + this.decorated = decorated; + } + + public String getHierarchicalKey() { + return decorated.getHierarchicalKey().getSql(); + } + + public String getHierarchicalReference() { + return decorated.getHierarchicalReference().getSql(); + } + + public String getNaturalKey() { + return decorated.getNaturalKey().getSql(); + } + + public Map<String, String> getRefValues() { + return decorated.getRefValues(); + } + } +} diff --git a/src/main/java/fr/inra/oresing/model/Configuration.java b/src/main/java/fr/inra/oresing/model/Configuration.java index d5208c786d48cffd8a52974779b22970fbe4adf6..9badf92d753c6b48b02d020478950eb4b57f5f17 100644 --- a/src/main/java/fr/inra/oresing/model/Configuration.java +++ b/src/main/java/fr/inra/oresing/model/Configuration.java @@ -1,16 +1,29 @@ package fr.inra.oresing.model; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.MoreCollectors; -import fr.inra.oresing.checker.ReferenceLineChecker; +import fr.inra.oresing.checker.*; import fr.inra.oresing.model.internationalization.Internationalization; import fr.inra.oresing.model.internationalization.InternationalizationMap; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.assertj.core.util.Streams; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import javax.annotation.Nullable; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; @Getter @Setter @@ -52,9 +65,9 @@ public class Configuration { for (Map.Entry<String, LineValidationRuleDescription> validation : validations.entrySet()) { CheckerDescription checker = validation.getValue().getChecker(); if (checker != null) { - String refType = Optional.ofNullable(checker) - .map(c -> c.getParams()) - .map(param -> param.get(ReferenceLineChecker.PARAM_REFTYPE)) + String refType = Optional.of(checker) + .map(CheckerDescription::getParams) + .map(CheckerConfigurationDescription::getRefType) .orElse(null); if ("Reference".equals(checker.getName()) && refType != null) { DependencyNode node = nodes.computeIfAbsent(refType, k -> new DependencyNode(refType)); @@ -202,7 +215,16 @@ public class Configuration { CheckerDescription checker; @Nullable String defaultValue; - Map<String, String> params; + VariableComponentDescriptionConfiguration params; + } + + @Getter + @Setter + @ToString + public static class VariableComponentDescriptionConfiguration implements GroovyDataInjectionConfiguration { + Set<String> references = new LinkedHashSet<>(); + Set<String> datatypes = new LinkedHashSet<>(); + boolean replace; } @Getter @@ -210,7 +232,43 @@ public class Configuration { @ToString public static class CheckerDescription { String name; - Map<String, String> params = new LinkedHashMap<>(); + CheckerConfigurationDescription params; + } + + @Getter + @Setter + @ToString + public static class CheckerConfigurationDescription implements + RegularExpressionCheckerConfiguration, + FloatCheckerConfiguration, + IntegerCheckerConfiguration, + DateLineCheckerConfiguration, + ReferenceLineCheckerConfiguration, + GroovyLineCheckerConfiguration { + String pattern; + String refType; + GroovyConfiguration groovy; + String columns; + String variableComponentKey; + String duration; + boolean codify; + boolean required; + + public ImmutableSet<String> doGetColumnsAsCollection() { + if (StringUtils.isEmpty(getColumns())) { + return ImmutableSet.of(); + } + return Streams.stream(Splitter.on(",").split(getColumns())).collect(ImmutableSet.toImmutableSet()); + } + } + + @Getter + @Setter + @ToString + public static class GroovyConfiguration implements fr.inra.oresing.checker.GroovyConfiguration { + String expression; + Set<String> references = new LinkedHashSet<>(); + Set<String> datatypes = new LinkedHashSet<>(); } @Getter diff --git a/src/main/java/fr/inra/oresing/model/Datum.java b/src/main/java/fr/inra/oresing/model/Datum.java new file mode 100644 index 0000000000000000000000000000000000000000..e8c2272021a0b473dbdb9f36aa6c9c7bb8ea25b0 --- /dev/null +++ b/src/main/java/fr/inra/oresing/model/Datum.java @@ -0,0 +1,75 @@ +package fr.inra.oresing.model; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class Datum implements SomethingThatCanProvideEvaluationContext{ + + private final Map<VariableComponentKey, String> values; + + public Datum() { + this(new LinkedHashMap<>()); + } + + public Datum(Map<VariableComponentKey, String> values) { + this.values = values; + } + + public static Datum copyOf(Datum datum) { + return new Datum(new LinkedHashMap<>(datum.asMap())); + } + + public static Datum fromMapMap(Map<String, Map<String, String>> line) { + Map<VariableComponentKey, String> valuesPerReference = new LinkedHashMap<>(); + for (Map.Entry<String, Map<String, String>> variableEntry : line.entrySet()) { + String variable = variableEntry.getKey(); + for (Map.Entry<String, String> componentEntry : variableEntry.getValue().entrySet()) { + String component = componentEntry.getKey(); + VariableComponentKey reference = new VariableComponentKey(variable, component); + valuesPerReference.put(reference, componentEntry.getValue()); + } + } + return new Datum(ImmutableMap.copyOf(valuesPerReference)); + } + + public String get(VariableComponentKey variableComponentKey) { + return values.get(variableComponentKey); + } + + public Map<VariableComponentKey, String> asMap() { + return values; + } + + public Map<String, Map<String, String>> asMapMap() { + final Map<String, Map<String, String>> datumAsMapMap = new LinkedHashMap<>(); + for (Map.Entry<VariableComponentKey, String> entry2 : asMap().entrySet()) { + String variable = entry2.getKey().getVariable(); + String component = entry2.getKey().getComponent(); + String value = entry2.getValue(); + datumAsMapMap.computeIfAbsent(variable, k -> new LinkedHashMap<>()).put(component, value); + } + return datumAsMapMap; + } + + public Datum filterOnVariable(Predicate<VariableComponentKey> includeInDataGroupPredicate) { + Map<VariableComponentKey, String> filteredValues = Maps.filterKeys(values, includeInDataGroupPredicate); + return new Datum(filteredValues); + } + + public String put(VariableComponentKey variableComponentKey, String value) { + return values.put(variableComponentKey, value); + } + + public void putAll(Datum rowWithValues) { + values.putAll(rowWithValues.values); + } + + @Override + public ImmutableMap<String, Object> getEvaluationContext() { + return ImmutableMap.of("datum", asMapMap(), "datumByVariableAndComponent", asMapMap()); + } +} diff --git a/src/main/java/fr/inra/oresing/model/GroovyDataInjectionConfiguration.java b/src/main/java/fr/inra/oresing/model/GroovyDataInjectionConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..171d481c75cbc5062a4343d76e545c9e812ab5c5 --- /dev/null +++ b/src/main/java/fr/inra/oresing/model/GroovyDataInjectionConfiguration.java @@ -0,0 +1,8 @@ +package fr.inra.oresing.model; + +import java.util.Set; + +public interface GroovyDataInjectionConfiguration { + Set<String> getReferences(); + Set<String> getDatatypes(); +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/model/LocalDateTimeRange.java b/src/main/java/fr/inra/oresing/model/LocalDateTimeRange.java index 21d607e7c11c1781deee29865d285795f1844adc..870a4431708f241a1778d64d4f2c63626f67c7e7 100644 --- a/src/main/java/fr/inra/oresing/model/LocalDateTimeRange.java +++ b/src/main/java/fr/inra/oresing/model/LocalDateTimeRange.java @@ -31,8 +31,8 @@ public class LocalDateTimeRange { public LocalDateTimeRange toLocalDateTimeRange(String str, DateTimeFormatter dateTimeFormatter, DateLineChecker dateLineChecker) { String pattern = "01/" + str; final LocalDate date = LocalDate.parse(pattern, DateTimeFormatter.ofPattern("dd/MM/yyyy")); - if (dateLineChecker.getParams() != null && dateLineChecker.getParams().containsKey(DateLineChecker.PARAM_DURATION)) { - return new Duration(dateLineChecker.getParams().get(DateLineChecker.PARAM_DURATION)).getLocalDateTimeRange(date); + if (dateLineChecker.getConfiguration() != null && dateLineChecker.getConfiguration().getDuration() != null) { + return new Duration(dateLineChecker.getConfiguration().getDuration()).getLocalDateTimeRange(date); } return LocalDateTimeRange.between(LocalDate.from(date.atStartOfDay()), date.plus(1L, ChronoUnit.MONTHS)); } @@ -45,10 +45,10 @@ public class LocalDateTimeRange { @Override public LocalDateTimeRange toLocalDateTimeRange(String str, DateTimeFormatter dateTimeFormatter, DateLineChecker dateLineChecker) { - if (dateLineChecker.getParams() != null && dateLineChecker.getParams().containsKey(DateLineChecker.PARAM_DURATION)) { + if (dateLineChecker.getConfiguration() != null && dateLineChecker.getConfiguration().getDuration() != null) { String pattern = "01/01/" + str; final LocalDate date = LocalDate.parse(pattern, DateTimeFormatter.ofPattern("dd/MM/yyyy")); - return new Duration(dateLineChecker.getParams().get(DateLineChecker.PARAM_DURATION)).getLocalDateTimeRange(date); + return new Duration(dateLineChecker.getConfiguration().getDuration()).getLocalDateTimeRange(date); } return LocalDateTimeRange.forYear(Year.parse(str, dateTimeFormatter)); } @@ -61,9 +61,9 @@ public class LocalDateTimeRange { @Override public LocalDateTimeRange toLocalDateTimeRange(String str, DateTimeFormatter dateTimeFormatter, DateLineChecker dateLineChecker) { - if (dateLineChecker.getParams() != null && dateLineChecker.getParams().containsKey(DateLineChecker.PARAM_DURATION)) { + if (dateLineChecker.getConfiguration() != null && dateLineChecker.getConfiguration().getDuration() != null) { final LocalDate date = LocalDate.parse(str, DateTimeFormatter.ofPattern("dd/MM/yyyy")); - return new Duration(dateLineChecker.getParams().get(DateLineChecker.PARAM_DURATION)).getLocalDateTimeRange(date); + return new Duration(dateLineChecker.getConfiguration().getDuration()).getLocalDateTimeRange(date); } return LocalDateTimeRange.forDay(LocalDate.parse(str, dateTimeFormatter)); } @@ -77,9 +77,9 @@ public class LocalDateTimeRange { @Override public LocalDateTimeRange toLocalDateTimeRange(String str, DateTimeFormatter dateTimeFormatter, DateLineChecker dateLineChecker) { final LocalDate startDate = LocalDate.parse(str, dateTimeFormatter); - if(dateLineChecker.getParams() != null && dateLineChecker.getParams().containsKey(DateLineChecker.PARAM_DURATION)){ + if(dateLineChecker.getConfiguration() != null && dateLineChecker.getConfiguration().getDuration() != null){ final LocalDateTime date = LocalDateTime.parse(str, DateTimeFormatter.ofPattern(getPattern())); - return new Duration(dateLineChecker.getParams().get(DateLineChecker.PARAM_DURATION)).getLocalDateTimeRange(date); + return new Duration(dateLineChecker.getConfiguration().getDuration()).getLocalDateTimeRange(date); } return LocalDateTimeRange.forDay(startDate); } diff --git a/src/main/java/fr/inra/oresing/model/ReferenceColumn.java b/src/main/java/fr/inra/oresing/model/ReferenceColumn.java new file mode 100644 index 0000000000000000000000000000000000000000..dcce3eb4c6b52d6859b19719afff5cd35f0b92f1 --- /dev/null +++ b/src/main/java/fr/inra/oresing/model/ReferenceColumn.java @@ -0,0 +1,12 @@ +package fr.inra.oresing.model; + +import lombok.Value; + +@Value +public class ReferenceColumn { + String column; + + public String asString() { + return column; + } +} diff --git a/src/main/java/fr/inra/oresing/model/ReferenceDatum.java b/src/main/java/fr/inra/oresing/model/ReferenceDatum.java new file mode 100644 index 0000000000000000000000000000000000000000..8ac944ecee78acc8b0259fbd03b54576f970796e --- /dev/null +++ b/src/main/java/fr/inra/oresing/model/ReferenceDatum.java @@ -0,0 +1,49 @@ +package fr.inra.oresing.model; + +import com.google.common.collect.ImmutableMap; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class ReferenceDatum implements SomethingThatCanProvideEvaluationContext { + + private final Map<ReferenceColumn, String> values; + + public ReferenceDatum() { + this(new LinkedHashMap<>()); + } + + public ReferenceDatum(Map<ReferenceColumn, String> values) { + this.values = values; + } + + public static ReferenceDatum copyOf(ReferenceDatum referenceDatum) { + return new ReferenceDatum(new LinkedHashMap<>(referenceDatum.values)); + } + + public String get(ReferenceColumn column) { + return values.get(column); + } + + public Map<String, String> asMap() { + Map<String, String> map = new LinkedHashMap<>(); + for (Map.Entry<ReferenceColumn, String> entry : values.entrySet()) { + String valueThatMayBeNull = entry.getValue(); + map.put(entry.getKey().asString(), valueThatMayBeNull); + } + return map; + } + + public String put(ReferenceColumn string, String value) { + return values.put(string, value); + } + + public void putAll(ReferenceDatum anotherReferenceDatum) { + values.putAll(anotherReferenceDatum.values); + } + + @Override + public ImmutableMap<String, Object> getEvaluationContext() { + return ImmutableMap.of("datum", asMap()); + } +} diff --git a/src/main/java/fr/inra/oresing/model/ReferenceValue.java b/src/main/java/fr/inra/oresing/model/ReferenceValue.java index 88ea349bd3c437cc432df7d6be65c71da3035b4c..9a0b18b5408b3eff70590ab2e6e033b9eb9b831e 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/model/SomethingThatCanProvideEvaluationContext.java b/src/main/java/fr/inra/oresing/model/SomethingThatCanProvideEvaluationContext.java new file mode 100644 index 0000000000000000000000000000000000000000..bf3292179d784e987e49d4fdbab78549676a88cd --- /dev/null +++ b/src/main/java/fr/inra/oresing/model/SomethingThatCanProvideEvaluationContext.java @@ -0,0 +1,7 @@ +package fr.inra.oresing.model; + +import com.google.common.collect.ImmutableMap; + +public interface SomethingThatCanProvideEvaluationContext { + ImmutableMap<String, Object> getEvaluationContext(); +} diff --git a/src/main/java/fr/inra/oresing/model/internationalization/InternationalizationDisplay.java b/src/main/java/fr/inra/oresing/model/internationalization/InternationalizationDisplay.java index 97bfecef68233c62871222b259b165959697719d..9a48f51b6654f014a379dbdd0a2efb386f27dd92 100644 --- a/src/main/java/fr/inra/oresing/model/internationalization/InternationalizationDisplay.java +++ b/src/main/java/fr/inra/oresing/model/internationalization/InternationalizationDisplay.java @@ -1,10 +1,11 @@ package fr.inra.oresing.model.internationalization; +import fr.inra.oresing.model.ReferenceColumn; +import fr.inra.oresing.model.ReferenceDatum; import lombok.Getter; import lombok.Setter; import org.assertj.core.util.Strings; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -16,13 +17,13 @@ import java.util.stream.Stream; public class InternationalizationDisplay { Map<String, String> pattern; - public static Map<String, String> getDisplays(Optional<Map<String, String>> displayPattern, Map<String, Internationalization> displayColumns, Map<String, String> refValues) { - Map<String, String> displays = new HashMap<>(); + public static ReferenceDatum getDisplays(Optional<Map<String, String>> displayPattern, Map<String, Internationalization> displayColumns, ReferenceDatum refValues) { + ReferenceDatum displays = new ReferenceDatum(); displayPattern .ifPresent(patterns -> { patterns.entrySet().stream() .forEach(stringEntry -> { - displays.put("__display_" + stringEntry.getKey(), + displays.put(new ReferenceColumn("__display_" + stringEntry.getKey()), parsePattern(stringEntry.getValue()).stream() .map(patternSection -> { String internationalizedPattern = patternSection.text; @@ -31,7 +32,7 @@ public class InternationalizationDisplay { if (displayColumns.containsKey(referencedColumn)) { referencedColumn = displayColumns.get(referencedColumn).getOrDefault(stringEntry.getKey(), referencedColumn); } - internationalizedPattern += refValues.get(referencedColumn); + internationalizedPattern += refValues.get(new ReferenceColumn(referencedColumn)); } return internationalizedPattern; } diff --git a/src/main/java/fr/inra/oresing/persistence/JsonRowMapper.java b/src/main/java/fr/inra/oresing/persistence/JsonRowMapper.java index 75c99bd50a6eb4b08a4b6e01499e25a86c21cc06..10a0fc0623d09fa9d0df07f4242274f398f5634f 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/Ltree.java b/src/main/java/fr/inra/oresing/persistence/Ltree.java new file mode 100644 index 0000000000000000000000000000000000000000..24fc547ca479a2cfbf5e058be853403ac6a277a1 --- /dev/null +++ b/src/main/java/fr/inra/oresing/persistence/Ltree.java @@ -0,0 +1,117 @@ +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.UUID; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Représente une donnée correspondant à une valeur de type <code>ltree</code>. + * + * Un ltree correspond à une séquence de labels séparés par des points. Les labels sont + * contraingnants en terme de syntaxe et cette classe gère l'échappement. + * + * https://www.postgresql.org/docs/current/ltree.html + */ +@Value +public class Ltree { + + /** + * Déliminateur entre les différents niveaux d'un ltree postgresql. + */ + public static final String SEPARATOR = "."; + + private static final Pattern LABEL_INVALID_CHARACTERS_REGEX = Pattern.compile("[^a-zA-Z0-9_]"); + + private static final Pattern VALID_LABEL_REGEX = Pattern.compile("[a-zA-Z0-9_]+"); + + String sql; + + private Ltree(String sql) { + this.sql = sql; + } + + /** + * Construire à partir d'un ltree tel qu'il a pu existé en base (donc déjà échappé et syntaxiquement correct) + */ + public static Ltree fromSql(String sql) { + checkSyntax(sql); + return new Ltree(sql); + } + + /** + * Constuire un label à partir d'un UUID + */ + public static Ltree fromUuid(UUID uuid) { + String escaped = escapeToLabel(StringUtils.remove(uuid.toString(), "-")); + return fromSql(escaped); + } + + /** + * Constuire en concaténant deux ltree pour en former un + */ + public static Ltree join(Ltree prefix, Ltree suffix) { + return fromSql(prefix.getSql() + SEPARATOR + suffix.getSql()); + } + + public static Ltree fromUnescapedString(String labelToEscape) { + String escaped = escapeToLabel(labelToEscape); + return fromSql(escaped); + } + + /** + * Échapper une chaîne pour former un label. + */ + public static String escapeToLabel(String key) { + String lowerCased = key.toLowerCase(); + String withAccentsStripped = StringUtils.stripAccents(lowerCased); + String withoutSpace = StringUtils.replace(withAccentsStripped, " ", "_"); + String toEscape = StringUtils.remove(withoutSpace, "-"); + String escaped = toEscape.chars() + .mapToObj(x -> (char) x) + .map(Ltree::escapeSymbolFromKeyComponent) + .collect(Collectors.joining()); + checkLabelSyntax(escaped); + return escaped; + } + + public static void checkLabelSyntax(String label) { + Preconditions.checkState(label.length() <= 256, "Un label dans un ltree ne peut pas être plus long que 256 caractères"); + Preconditions.checkState(!label.isEmpty(), "Un label ne peut être vide"); + Preconditions.checkState(VALID_LABEL_REGEX.matcher(label).matches(), label + " contient des caractères invalides"); + } + + private static String escapeSymbolFromKeyComponent(Character aChar) { + String escapedChar; + if (characterCanBeUsedInLabel(aChar)) { + escapedChar = CharUtils.toString(aChar); + } else { + escapedChar = RegExUtils.removeAll( + Character.getName(aChar), + LABEL_INVALID_CHARACTERS_REGEX + ); + } + 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). + * </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); + } +} diff --git a/src/main/java/fr/inra/oresing/persistence/ReferenceValueRepository.java b/src/main/java/fr/inra/oresing/persistence/ReferenceValueRepository.java index f97c428e212307784a5db136419cf92b5cd4b50e..7b24cf31cf405daa794ec2f184fbef8286a0d3ae 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/ApplicationConfigurationService.java b/src/main/java/fr/inra/oresing/rest/ApplicationConfigurationService.java index 10f23f93a886b97254204acec9cc1c3037b91167..6d02b79f04591b0bd99a41f6bec2b4042c61091f 100644 --- a/src/main/java/fr/inra/oresing/rest/ApplicationConfigurationService.java +++ b/src/main/java/fr/inra/oresing/rest/ApplicationConfigurationService.java @@ -6,16 +6,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multiset; import com.google.common.collect.Sets; import com.google.common.collect.TreeMultiset; import fr.inra.oresing.OreSiTechnicalException; -import fr.inra.oresing.checker.CheckerFactory; -import fr.inra.oresing.checker.DateLineChecker; +import fr.inra.oresing.checker.GroovyConfiguration; import fr.inra.oresing.checker.GroovyLineChecker; -import fr.inra.oresing.checker.ReferenceLineChecker; -import fr.inra.oresing.checker.decorators.GroovyDecorator; import fr.inra.oresing.groovy.GroovyExpression; import fr.inra.oresing.model.Configuration; import fr.inra.oresing.model.LocalDateTimeRange; @@ -33,7 +31,17 @@ import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -330,11 +338,11 @@ public class ApplicationConfigurationService { builder.recordAuthorizationScopeVariableComponentWrongChecker(authorizationScopeVariableComponentKey, "Date"); } String refType = null; - Map<String, String> params = authorizationScopeVariableComponentChecker.getParams(); - if (params == null) { + Configuration.CheckerConfigurationDescription checkerConfigurationDescription = authorizationScopeVariableComponentChecker.getParams(); + if (checkerConfigurationDescription == null) { builder.recordAuthorizationScopeVariableComponentReftypeNull(authorizationScopeVariableComponentKey, configuration.getReferences().keySet()); } else { - refType = params.getOrDefault(ReferenceLineChecker.PARAM_REFTYPE, null); + refType = checkerConfigurationDescription.getRefType(); if (refType == null || !configuration.getReferences().containsKey(refType)) { builder.recordAuthorizationScopeVariableComponentReftypeUnknown(authorizationScopeVariableComponentKey, refType, configuration.getReferences().keySet()); } else { @@ -379,7 +387,7 @@ public class ApplicationConfigurationService { } Optional.ofNullable(timeScopeVariableComponentChecker) .map(checkerDescription -> checkerDescription.getParams()) - .map(params -> params.getOrDefault(DateLineChecker.PARAM_PATTERN, null)) + .map(Configuration.CheckerConfigurationDescription::getPattern) .ifPresent(pattern -> { if (!LocalDateTimeRange.getKnownPatterns().contains(pattern)) { builder.recordTimeScopeVariableComponentPatternUnknown(timeScopeVariableComponentKey, pattern, LocalDateTimeRange.getKnownPatterns()); @@ -421,7 +429,11 @@ public class ApplicationConfigurationService { String lineValidationRuleKey = validationEntry.getKey(); Configuration.CheckerDescription checker = lineValidationRuleDescription.getChecker(); if (GroovyLineChecker.NAME.equals(checker.getName())) { - String expression = checker.getParams().get(GroovyLineChecker.PARAM_EXPRESSION); + String expression = Optional.of(checker) + .map(Configuration.CheckerDescription::getParams) + .map(Configuration.CheckerConfigurationDescription::getGroovy) + .map(GroovyConfiguration::getExpression) + .orElse(null); if (StringUtils.isBlank(expression)) { builder.recordMissingRequiredExpression(lineValidationRuleKey); } else { @@ -444,8 +456,8 @@ public class ApplicationConfigurationService { if (variableComponentDescription != null) { Configuration.CheckerDescription checkerDescription = variableComponentDescription.getChecker(); if ("Reference".equals(checkerDescription.getName())) { - if (checkerDescription.getParams() != null && checkerDescription.getParams().containsKey(ReferenceLineChecker.PARAM_REFTYPE)) { - String refType = checkerDescription.getParams().get(ReferenceLineChecker.PARAM_REFTYPE); + if (checkerDescription.getParams() != null && checkerDescription.getParams().getRefType() != null) { + String refType = checkerDescription.getParams().getRefType(); if (!references.contains(refType)) { builder.unknownReferenceForChecker(dataType, datum, component, refType, references); } @@ -599,17 +611,23 @@ public class ApplicationConfigurationService { continue; } ImmutableSet<String> variableComponentCheckers = ImmutableSet.of("Date", "Float", "Integer", "RegularExpression", "Reference"); - String columns = checker.getParams().get(CheckerFactory.COLUMNS); + String columns = checker.getParams().getColumns(); Set<String> groovyColumn = Optional.ofNullable(checker) .map(check->check.getParams()) - .filter(params->params.containsKey(GroovyDecorator.PARAMS_GROOVY)) - .map(params-> params.getOrDefault(CheckerFactory.COLUMNS, "")) + .filter(params->params.getGroovy() != null) + .map(params-> MoreObjects.firstNonNull(params.getColumns(), "")) + + // autant mettre une collection dans le YAML directement .map(values-> values.split(",")) .map(values-> Arrays.stream(values).collect(Collectors.toSet())) .orElse(Set.of()); if (GroovyLineChecker.NAME.equals(checker.getName())) { - String expression = checker.getParams().get(GroovyLineChecker.PARAM_EXPRESSION); + String expression =Optional.of(checker) + .map(Configuration.CheckerDescription::getParams) + .map(Configuration.CheckerConfigurationDescription::getGroovy) + .map(GroovyConfiguration::getExpression) + .orElse(null); if (StringUtils.isBlank(expression)) { builder.recordMissingRequiredExpression(validationRuleDescriptionEntryKey); } else { @@ -636,8 +654,8 @@ public class ApplicationConfigurationService { } } if ("Reference".equals(checker.getName())) { - if (checker.getParams() != null && checker.getParams().containsKey(ReferenceLineChecker.PARAM_REFTYPE)) { - String refType = checker.getParams().get(ReferenceLineChecker.PARAM_REFTYPE); + if (checker.getParams() != null && checker.getParams().getRefType() != null) { + String refType = checker.getParams().getRefType(); if (!references.contains(refType)) { builder.unknownReferenceForCheckerInReference(validationRuleDescriptionEntryKey, reference, refType, references); } diff --git a/src/main/java/fr/inra/oresing/rest/AuthorizationService.java b/src/main/java/fr/inra/oresing/rest/AuthorizationService.java index a667a59a9f69c7a5ef8776b796392ab72f9ab92b..7223f5651212088e7c0509e265b308b2186dc8af 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/DownloadDatasetQuery.java b/src/main/java/fr/inra/oresing/rest/DownloadDatasetQuery.java index 2b8d425a712514147eac5fe28eed2f28b73b0380..81fb6934279e5930b2b53ef90a52c44dd63e91e5 100644 --- a/src/main/java/fr/inra/oresing/rest/DownloadDatasetQuery.java +++ b/src/main/java/fr/inra/oresing/rest/DownloadDatasetQuery.java @@ -158,7 +158,7 @@ public class DownloadDatasetQuery { if (!Strings.isNullOrEmpty(vck.intervalValues.from) || !Strings.isNullOrEmpty(vck.intervalValues.to)) { DateLineChecker dateLineChecker = new DateLineChecker( CheckerTarget.getInstance(vck.variableComponentKey, null, null), - vck.format, null); + vck.format, null, null); filters.add( String.format( "datavalues #> '{\"%1$s\",\"%2$s\"}'@@ ('$ >= \"date:'||%3$s||'\" && $ <= \"date:'||%4$s||'Z\"')::jsonpath", diff --git a/src/main/java/fr/inra/oresing/rest/OreSiResources.java b/src/main/java/fr/inra/oresing/rest/OreSiResources.java index d59af7505e474d2efccc7bf38bdbb98b9d317fbb..e8cb0de81f4007d373437bbc53912056c470c2b7 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 461bc6666d1ae91d6ccda979ca3e46969d943f77..e58048116243a8fe20e6efb202b3ecc85eaed8b9 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiService.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java @@ -3,29 +3,73 @@ 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.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Maps; +import com.google.common.collect.MoreCollectors; +import com.google.common.collect.Ordering; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import fr.inra.oresing.OreSiTechnicalException; -import fr.inra.oresing.checker.*; -import fr.inra.oresing.checker.decorators.GroovyDecorator; +import fr.inra.oresing.checker.CheckerFactory; +import fr.inra.oresing.checker.DateLineChecker; +import fr.inra.oresing.checker.DateValidationCheckResult; +import fr.inra.oresing.checker.FloatChecker; +import fr.inra.oresing.checker.IntegerChecker; +import fr.inra.oresing.checker.InvalidDatasetContentException; +import fr.inra.oresing.checker.LineChecker; +import fr.inra.oresing.checker.ReferenceLineChecker; +import fr.inra.oresing.checker.ReferenceLineCheckerConfiguration; +import fr.inra.oresing.checker.ReferenceValidationCheckResult; import fr.inra.oresing.groovy.CommonExpression; import fr.inra.oresing.groovy.Expression; +import fr.inra.oresing.groovy.GroovyContextHelper; import fr.inra.oresing.groovy.StringGroovyExpression; -import fr.inra.oresing.model.*; +import fr.inra.oresing.model.Application; +import fr.inra.oresing.model.Authorization; +import fr.inra.oresing.model.BinaryFile; +import fr.inra.oresing.model.BinaryFileDataset; +import fr.inra.oresing.model.Configuration; +import fr.inra.oresing.model.Data; +import fr.inra.oresing.model.Datum; +import fr.inra.oresing.model.LocalDateTimeRange; +import fr.inra.oresing.model.ReferenceColumn; +import fr.inra.oresing.model.ReferenceDatum; +import fr.inra.oresing.model.ReferenceValue; +import fr.inra.oresing.model.VariableComponentKey; import fr.inra.oresing.model.internationalization.Internationalization; import fr.inra.oresing.model.internationalization.InternationalizationDisplay; import fr.inra.oresing.model.internationalization.InternationalizationReferenceMap; -import fr.inra.oresing.persistence.*; +import fr.inra.oresing.persistence.AuthenticationService; +import fr.inra.oresing.persistence.BinaryFileInfos; +import fr.inra.oresing.persistence.DataRepository; +import fr.inra.oresing.persistence.DataRow; +import fr.inra.oresing.persistence.Ltree; +import fr.inra.oresing.persistence.OreSiRepository; +import fr.inra.oresing.persistence.ReferenceValueRepository; +import fr.inra.oresing.persistence.SqlPolicy; +import fr.inra.oresing.persistence.SqlSchema; +import fr.inra.oresing.persistence.SqlSchemaForApplication; +import fr.inra.oresing.persistence.SqlService; import fr.inra.oresing.persistence.roles.OreSiRightOnApplicationRole; import fr.inra.oresing.persistence.roles.OreSiUserRole; +import fr.inra.oresing.transformer.TransformerFactory; import lombok.Value; import lombok.extern.slf4j.Slf4j; 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; @@ -50,7 +94,20 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -66,12 +123,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; @@ -79,6 +130,9 @@ public class OreSiService { @Autowired private AuthenticationService authenticationService; + @Autowired + private TransformerFactory transformerFactory; + @Autowired private CheckerFactory checkerFactory; @@ -97,27 +151,15 @@ public class OreSiService { @Autowired 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_]", - "" - ), "-" - ); - 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"); - } + @Autowired + private GroovyContextHelper groovyContextHelper; - private void checkHierarchicalKeySyntax(String compositeKey) { - Splitter.on(LTREE_SEPARATOR).split(compositeKey).forEach(OreSiService::checkNaturalKeySyntax); + /** + * @deprecated utiliser directement {@link Ltree#escapeToLabel(String)} + */ + @Deprecated + public static String escapeKeyComponent(String key) { + return Ltree.escapeToLabel(key); } protected UUID storeFile(Application app, MultipartFile file) throws IOException { @@ -291,14 +333,15 @@ public class OreSiService { Application application = downloadDatasetQuery.getApplication(); String dataType = downloadDatasetQuery.getDataType(); ImmutableSet<LineChecker> lineCheckers = checkerFactory.getLineCheckers(application, dataType); - Consumer<ImmutableMap<VariableComponentKey, String>> validateRow = line -> { + Consumer<Datum> validateRow = line -> { lineCheckers.forEach(lineChecker -> { ValidationCheckResult validationCheckResult = lineChecker.check(line); Preconditions.checkState(validationCheckResult.isSuccess(), "erreur de validation d'une donnée stockée " + validationCheckResult); }); }; repo.getRepository(application).data().findAllByDataType(downloadDatasetQuery).stream() - .map(this::valuesToIndexedPerReferenceMap) + .map(DataRow::getValues) + .map(Datum::fromMapMap) .forEach(validateRow); } @@ -336,13 +379,14 @@ public class OreSiService { .map(lineChecker -> ((ReferenceLineChecker) lineChecker)) .findFirst(); Optional<Configuration.CompositeReferenceDescription> toUpdateCompositeReference = conf.getCompositeReferencesUsing(refType); - String parentHierarchicalKeyColumn, parentHierarchicalParentReference; + ReferenceColumn parentHierarchicalKeyColumn; + String 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, ReferenceDatum, 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); @@ -353,13 +397,13 @@ public class OreSiService { Configuration.CompositeReferenceComponentDescription referenceComponentDescription = compositeReferenceDescription.getComponents().stream() .filter(compositeReferenceComponentDescription -> compositeReferenceComponentDescription.getReference().equals(refType)) .collect(MoreCollectors.onlyElement()); - parentHierarchicalKeyColumn = referenceComponentDescription.getParentKeyColumn(); + parentHierarchicalKeyColumn = new ReferenceColumn(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; + Ltree parentHierarchicalKey = Ltree.fromUnescapedString(referenceValues.get(parentHierarchicalKeyColumn)); + return Ltree.join(parentHierarchicalKey, naturalKey); }; - getHierarchicalReferenceFn = (reference) -> parentHierarchicalParentReference + LTREE_SEPARATOR + reference; + getHierarchicalReferenceFn = (reference) -> Ltree.join(Ltree.fromUnescapedString(parentHierarchicalParentReference), reference); } } else { getHierarchicalKeyFn = (naturalKey, referenceValues) -> naturalKey; @@ -376,14 +420,15 @@ public class OreSiService { Iterator<CSVRecord> linesIterator = csvParser.iterator(); CSVRecord headerRow = linesIterator.next(); ImmutableList<String> columns = Streams.stream(headerRow).collect(ImmutableList.toImmutableList()); - Function<CSVRecord, Map<String, String>> csvRecordToLineAsMapFn = line -> { + Function<CSVRecord, ReferenceDatum> csvRecordToLineAsMapFn = line -> { Iterator<String> currentHeader = columns.iterator(); - Map<String, String> recordAsMap = new LinkedHashMap<>(); + ReferenceDatum referenceDatum = new ReferenceDatum(); line.forEach(value -> { String header = currentHeader.next(); - recordAsMap.put(header, value); + ReferenceColumn referenceColumn = new ReferenceColumn(header); + referenceDatum.put(referenceColumn, value); }); - return recordAsMap; + return referenceDatum; }; List<CsvRowValidationCheckResult> rowErrors = new LinkedList<>(); @@ -391,7 +436,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()) @@ -404,18 +449,18 @@ public class OreSiService { .map(internationalizationDisplay -> internationalizationDisplay.getPattern()); Stream<ReferenceValue> referenceValuesStream = recordStream .map(csvRecordToLineAsMapFn) - .map(refValues -> { + .map(referenceDatum -> { Map<String, Set<UUID>> refsLinkedTo = new LinkedHashMap<>(); lineCheckers.forEach(lineChecker -> { - ValidationCheckResult validationCheckResult = lineChecker.checkReference(refValues); + ValidationCheckResult validationCheckResult = lineChecker.checkReference(referenceDatum); if (validationCheckResult.isSuccess()) { if (validationCheckResult instanceof ReferenceValidationCheckResult) { ReferenceValidationCheckResult referenceValidationCheckResult = (ReferenceValidationCheckResult) validationCheckResult; String reference = ((ReferenceLineChecker) lineChecker).getRefType(); UUID referenceId = referenceValidationCheckResult.getReferenceId(); - refValues.put((String) referenceValidationCheckResult.getTarget().getTarget(), (String) referenceValidationCheckResult.getValue()); + referenceDatum.put((ReferenceColumn) referenceValidationCheckResult.getTarget().getTarget(), (String) referenceValidationCheckResult.getValue()); refsLinkedTo - .computeIfAbsent(escapeKeyComponent(reference), k -> new LinkedHashSet<>()) + .computeIfAbsent(Ltree.escapeToLabel(reference), k -> new LinkedHashSet<>()) .add(referenceId); } } else { @@ -423,43 +468,48 @@ public class OreSiService { } }); ReferenceValue e = new ReferenceValue(); - String naturalKey; - String technicalId = e.getId().toString(); + Ltree naturalKey; if (ref.getKeyColumns().isEmpty()) { - naturalKey = escapeKeyComponent(technicalId); + UUID technicalId = e.getId(); + naturalKey = Ltree.fromUuid(technicalId); } else { - naturalKey = ref.getKeyColumns().stream() - .map(kc -> refValues.get(kc)) - .filter(key -> !Strings.isNullOrEmpty(key)) - .map(key -> escapeKeyComponent(key)) + String naturalKeyAsString = ref.getKeyColumns().stream() + .map(ReferenceColumn::new) + .map(referenceDatum::get) + .filter(StringUtils::isNotEmpty) + .map(Ltree::escapeToLabel) .collect(Collectors.joining(KEYCOLUMN_SEPARATOR)); + naturalKey = Ltree.fromSql(naturalKeyAsString); } - OreSiService.checkNaturalKeySyntax(naturalKey); - String recursiveNaturalKey = naturalKey; + Ltree recursiveNaturalKey = naturalKey; + final Ltree refTypeAsLabel = Ltree.fromUnescapedString(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, referenceDatum); + 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)); + referenceDatum.putAll(InternationalizationDisplay.getDisplays(displayPattern, displayColumns, referenceDatum)); buildedHierarchicalKeys.put(naturalKey, hierarchicalKey); - checkHierarchicalKeySyntax(hierarchicalKey); e.setBinaryFile(fileId); e.setReferenceType(refType); e.setHierarchicalKey(hierarchicalKey); @@ -467,10 +517,10 @@ public class OreSiService { e.setRefsLinkedTo(refsLinkedTo); e.setNaturalKey(naturalKey); e.setApplication(app.getId()); - e.setRefValues(refValues); + e.setRefValues(referenceDatum.asMap()); 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 @@ -491,7 +541,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)) @@ -509,16 +559,16 @@ public class OreSiService { if (!Strings.isNullOrEmpty(s)) { String naturalKey; try { - s = OreSiService.escapeKeyComponent(s); + s = Ltree.escapeToLabel(s); naturalKey = ref.getKeyColumns() .stream() .map(kc -> columns.indexOf(kc)) - .map(k -> OreSiService.escapeKeyComponent(csvrecord.get(k))) + .map(k -> Ltree.escapeToLabel(csvrecord.get(k))) .collect(Collectors.joining("__")); } catch (IllegalArgumentException e) { return; } - referenceMap.put(naturalKey, s); + referenceMap.put(Ltree.fromSql(naturalKey), Ltree.fromUnescapedString(s)); if (!referenceUUIDs.containsKey(s)) { referenceUUIDs.put(s, UUID.randomUUID()); } @@ -547,8 +597,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()); @@ -562,7 +612,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); } }); @@ -651,9 +701,7 @@ public class OreSiService { CSVParser csvParser = CSVParser.parse(csv, Charsets.UTF_8, csvFormat); Iterator<CSVRecord> linesIterator = csvParser.iterator(); - Map<VariableComponentKey, String> constantValues = new LinkedHashMap<>(); - ImmutableMap<VariableComponentKey, Expression<String>> defaultValueExpressions = getDefaultValueExpressions(dataTypeDescription, binaryFileDataset == null ? null : binaryFileDataset.getRequiredauthorizations()); - + Datum constantValues = new Datum(); readPreHeader(formatDescription, constantValues, linesIterator); ImmutableList<String> columns = readHeaderRow(linesIterator); @@ -663,7 +711,7 @@ public class OreSiService { .map(buildCsvRecordToLineAsMapFn(columns)) .flatMap(lineAsMap -> buildLineAsMapToRecordsFn(formatDescription).apply(lineAsMap).stream()) .map(buildMergeLineValuesAndConstantValuesFn(constantValues)) - .map(buildReplaceMissingValuesByDefaultValuesFn(defaultValueExpressions, app.getConfiguration().getDataTypes().get(dataType).getData(), app, repo.getRepository(app))) + .map(buildReplaceMissingValuesByDefaultValuesFn(app, dataType, binaryFileDataset == null ? null : binaryFileDataset.getRequiredauthorizations())) .flatMap(buildLineValuesToEntityStreamFn(app, dataType, storedFile.getId(), errors, binaryFileDataset)); repo.getRepository(app).data().storeAll(dataStream); @@ -765,21 +813,22 @@ public class OreSiService { return rowWithData -> { - Map<VariableComponentKey, String> values = new HashMap<>(rowWithData.getDatum()); + Datum datum = Datum.copyOf(rowWithData.getDatum()); Map<VariableComponentKey, UUID> refsLinkedTo = new LinkedHashMap<>(); Map<VariableComponentKey, DateValidationCheckResult> dateValidationCheckResultImmutableMap = new HashMap<>(); List<CsvRowValidationCheckResult> rowErrors = new LinkedList<>(); lineCheckers.forEach(lineChecker -> { - ValidationCheckResult validationCheckResult = lineChecker.check(values); + ValidationCheckResult validationCheckResult = lineChecker.check(datum); if (validationCheckResult.isSuccess()) { if (validationCheckResult instanceof DateValidationCheckResult) { VariableComponentKey variableComponentKey = (VariableComponentKey) ((DateValidationCheckResult) validationCheckResult).getTarget(); dateValidationCheckResultImmutableMap.put(variableComponentKey, (DateValidationCheckResult) validationCheckResult); } if (validationCheckResult instanceof ReferenceValidationCheckResult) { - if (!lineChecker.getParams().isEmpty() && lineChecker.getParams().containsKey(GroovyDecorator.PARAMS_GROOVY)) { - values.put((VariableComponentKey) ((ReferenceValidationCheckResult) validationCheckResult).getTarget().getTarget(), ((ReferenceValidationCheckResult) validationCheckResult).getValue().toString()); + ReferenceLineCheckerConfiguration configuration = (ReferenceLineCheckerConfiguration) lineChecker.getConfiguration(); + if (configuration.getGroovy() != null) { + datum.put((VariableComponentKey) ((ReferenceValidationCheckResult) validationCheckResult).getTarget().getTarget(), ((ReferenceValidationCheckResult) validationCheckResult).getValue().toString()); } ReferenceValidationCheckResult referenceValidationCheckResult = (ReferenceValidationCheckResult) validationCheckResult; VariableComponentKey variableComponentKey = (VariableComponentKey) referenceValidationCheckResult.getTarget().getTarget(); @@ -797,13 +846,13 @@ public class OreSiService { return Stream.empty(); } - String timeScopeValue = values.get(dataTypeDescription.getAuthorization().getTimeScope()); + String timeScopeValue = datum.get(dataTypeDescription.getAuthorization().getTimeScope()); LocalDateTimeRange timeScope = LocalDateTimeRange.parse(timeScopeValue, timeScopeDateLineChecker); Map<String, String> requiredAuthorizations = new LinkedHashMap<>(); dataTypeDescription.getAuthorization().getAuthorizationScopes().forEach((authorizationScope, variableComponentKey) -> { - String requiredAuthorization = values.get(variableComponentKey); - checkHierarchicalKeySyntax(requiredAuthorization); + String requiredAuthorization = datum.get(variableComponentKey); + Ltree.checkSyntax(requiredAuthorization); requiredAuthorizations.put(authorizationScope, requiredAuthorization); }); checkTimescopRangeInDatasetRange(timeScope, errors, binaryFileDataset, rowWithData.getLineNumber()); @@ -816,11 +865,11 @@ public class OreSiService { Configuration.DataGroupDescription dataGroupDescription = entry.getValue(); Predicate<VariableComponentKey> includeInDataGroupPredicate = variableComponentKey -> dataGroupDescription.getData().contains(variableComponentKey.getVariable()); - Map<VariableComponentKey, String> dataGroupValues = Maps.filterKeys(values, includeInDataGroupPredicate); + Datum dataGroupValues = datum.filterOnVariable(includeInDataGroupPredicate); Map<String, Map<String, String>> toStore = new LinkedHashMap<>(); Map<String, Map<String, UUID>> refsLinkedToToStore = new LinkedHashMap<>(); - for (Map.Entry<VariableComponentKey, String> entry2 : dataGroupValues.entrySet()) { + for (Map.Entry<VariableComponentKey, String> entry2 : dataGroupValues.asMap().entrySet()) { VariableComponentKey variableComponentKey = entry2.getKey(); String variable = variableComponentKey.getVariable(); String component = variableComponentKey.getComponent(); @@ -906,37 +955,56 @@ public class OreSiService { * <p> * Si des valeurs par défaut ont été définies dans le YAML, la donnée doit les avoir. */ - private Function<RowWithData, RowWithData> buildReplaceMissingValuesByDefaultValuesFn(ImmutableMap<VariableComponentKey, Expression<String>> defaultValueExpressions, LinkedHashMap<String, Configuration.ColumnDescription> data, Application application, OreSiRepository.RepositoryForApplication repository) { - return rowWithData -> { - Map<String, Map<String, String>> datumByVariableAndComponent = new HashMap<>(); - Map<String, Map<String, Map<String, String>>> paramsByVariableAndComponent = new HashMap<>(); - for (Map.Entry<VariableComponentKey, String> entry : rowWithData.getDatum().entrySet()) { - datumByVariableAndComponent - .computeIfAbsent(entry.getKey().getVariable(), k -> new HashMap<String, String>()) - .put(entry.getKey().getComponent(), entry.getValue()); + private Function<RowWithData, RowWithData> buildReplaceMissingValuesByDefaultValuesFn(Application app, String dataType, Map<String, String> requiredAuthorizations) { + ReferenceValueRepository referenceValueRepository = repo.getRepository(app).referenceValue(); + Configuration.DataTypeDescription dataTypeDescription = app.getConfiguration().getDataTypes().get(dataType); + ImmutableMap<VariableComponentKey, Expression<String>> defaultValueExpressions = getDefaultValueExpressions(dataTypeDescription, requiredAuthorizations); + Map<String, Configuration.ColumnDescription> data = dataTypeDescription.getData(); + Map<VariableComponentKey, Function<Datum, String>> defaultValueFns = new LinkedHashMap<>(); + Set<VariableComponentKey> replaceEnabled = new LinkedHashSet<>(); + for (Map.Entry<VariableComponentKey, Expression<String>> entry : defaultValueExpressions.entrySet()) { + VariableComponentKey variableComponentKey = entry.getKey(); + Expression<String> expression = entry.getValue(); + Configuration.VariableComponentDescriptionConfiguration params = Optional.ofNullable(data) + .map(columnDescriptionLinkedHashMap -> columnDescriptionLinkedHashMap.get(variableComponentKey.getVariable())) + .map(columnDescription -> columnDescription.getComponents()) + .map(variableComponentDescriptionLinkedHashMap -> variableComponentDescriptionLinkedHashMap.get(variableComponentKey.getComponent())) + .map(variableComponentDescription -> variableComponentDescription.getParams()) + .orElseGet(Configuration.VariableComponentDescriptionConfiguration::new); + Set<String> configurationReferences = params.getReferences(); + ImmutableMap<String, Object> contextForExpression = groovyContextHelper.getGroovyContextForReferences(referenceValueRepository, configurationReferences); + Preconditions.checkState(params.getDatatypes().isEmpty(), "à ce stade, on ne gère pas la chargement de données"); + Function<Datum, String> computeDefaultValueFn = datum -> { + ImmutableMap<String, Object> evaluationContext = ImmutableMap.<String, Object>builder() + .putAll(contextForExpression) + .putAll(datum.getEvaluationContext()) + .build(); + String evaluate = expression.evaluate(evaluationContext); + return evaluate; + }; + defaultValueFns.put(variableComponentKey, computeDefaultValueFn); + if (params.isReplace()) { + replaceEnabled.add(variableComponentKey); } - Map<VariableComponentKey, String> rowWithDefaults = new LinkedHashMap(); - Map<VariableComponentKey, String> rowWithValues = new LinkedHashMap(rowWithData.datum); - defaultValueExpressions.entrySet().stream() + } + return rowWithData -> { + Map<VariableComponentKey, String> rowWithDefaults = new LinkedHashMap<>(); + Map<VariableComponentKey, String> rowWithValues = new LinkedHashMap<>(rowWithData.getDatum().asMap()); + defaultValueFns.entrySet().stream() .forEach(variableComponentKeyExpressionEntry -> { - Map<String, String> params = Optional.ofNullable(data) - .map(columnDescriptionLinkedHashMap -> columnDescriptionLinkedHashMap.get(variableComponentKeyExpressionEntry.getKey().getVariable())) - .map(columnDescription -> columnDescription.getComponents()) - .map(variableComponentDescriptionLinkedHashMap -> variableComponentDescriptionLinkedHashMap.get(variableComponentKeyExpressionEntry.getKey().getComponent())) - .map(variableComponentDescription -> variableComponentDescription.getParams()) - .orElseGet(HashMap::new); - ImmutableMap<String, Object> evaluationContext = GroovyLineChecker.buildContext(rowWithData.getDatum(), application, params, repository); - String evaluate = variableComponentKeyExpressionEntry.getValue().evaluate(evaluationContext); + VariableComponentKey variableComponentKey = variableComponentKeyExpressionEntry.getKey(); + Function<Datum, String> computeDefaultValueFn = variableComponentKeyExpressionEntry.getValue(); + String evaluate = computeDefaultValueFn.apply(rowWithData.getDatum()); if (StringUtils.isNotBlank(evaluate)) { - if (params != null && Boolean.parseBoolean(params.get("replace"))) { - rowWithValues.put(variableComponentKeyExpressionEntry.getKey(), evaluate); + if (replaceEnabled.contains(variableComponentKey)) { + rowWithValues.put(variableComponentKey, evaluate); } else { - rowWithDefaults.put(variableComponentKeyExpressionEntry.getKey(), evaluate); + rowWithDefaults.put(variableComponentKey, evaluate); } } }); rowWithDefaults.putAll(rowWithValues); - return new RowWithData(rowWithData.getLineNumber(), ImmutableMap.copyOf(rowWithDefaults)); + return new RowWithData(rowWithData.getLineNumber(), new Datum(ImmutableMap.copyOf(rowWithDefaults))); }; } @@ -947,12 +1015,13 @@ public class OreSiService { * d'un fichier de données qu'on importe. Ce sont les données qu'on trouve dans l'entête * du fichier. */ - private Function<RowWithData, RowWithData> buildMergeLineValuesAndConstantValuesFn(Map<VariableComponentKey, String> constantValues) { + private Function<RowWithData, RowWithData> buildMergeLineValuesAndConstantValuesFn(Datum constantValues) { return rowWithData -> { - ImmutableMap<VariableComponentKey, String> datum = ImmutableMap.<VariableComponentKey, String>builder() - .putAll(constantValues) - .putAll(rowWithData.getDatum()) + final ImmutableMap<VariableComponentKey, String> values = ImmutableMap.<VariableComponentKey, String>builder() + .putAll(constantValues.asMap()) + .putAll(rowWithData.getDatum().asMap()) .build(); + Datum datum = new Datum(values); return new RowWithData(rowWithData.getLineNumber(), datum); }; } @@ -979,7 +1048,7 @@ public class OreSiService { Configuration.ColumnBindingDescription bindingDescription = bindingPerHeader.get(header); record.put(bindingDescription.getBoundTo(), value); } - return ImmutableSet.of(new RowWithData(parsedCsvRow.getLineNumber(), record)); + return ImmutableSet.of(new RowWithData(parsedCsvRow.getLineNumber(), new Datum(record))); }; return lineAsMapToRecordsFn; } @@ -1061,7 +1130,8 @@ public class OreSiService { .putAll(tokenValues) .putAll(bodyValues) .build(); - records.add(new RowWithData(parsedCsvRow.getLineNumber(), record)); + Datum datum = new Datum(record); + records.add(new RowWithData(parsedCsvRow.getLineNumber(), datum)); // et on passe au groupe de colonnes répétées suivant tokenValues.clear(); @@ -1113,7 +1183,7 @@ public class OreSiService { * @param constantValues * @param linesIterator */ - private void readPreHeader(Configuration.FormatDescription formatDescription, Map<VariableComponentKey, String> constantValues, Iterator<CSVRecord> linesIterator) { + private void readPreHeader(Configuration.FormatDescription formatDescription, Datum constantValues, Iterator<CSVRecord> linesIterator) { ImmutableSetMultimap<Integer, Configuration.HeaderConstantDescription> perRowNumberConstants = formatDescription.getConstants().stream() .collect(ImmutableSetMultimap.toImmutableSetMultimap(Configuration.HeaderConstantDescription::getRowNumber, Function.identity())); @@ -1321,20 +1391,6 @@ public class OreSiService { return repo.application().tryFindApplication(nameOrId); } - private ImmutableMap<VariableComponentKey, String> valuesToIndexedPerReferenceMap(DataRow dataRow) { - Map<String, Map<String, String>> line = dataRow.getValues(); - Map<VariableComponentKey, String> valuesPerReference = new LinkedHashMap<>(); - for (Map.Entry<String, Map<String, String>> variableEntry : line.entrySet()) { - String variable = variableEntry.getKey(); - for (Map.Entry<String, String> componentEntry : variableEntry.getValue().entrySet()) { - String component = componentEntry.getKey(); - VariableComponentKey reference = new VariableComponentKey(variable, component); - valuesPerReference.put(reference, componentEntry.getValue()); - } - } - return ImmutableMap.copyOf(valuesPerReference); - } - /** * @param nameOrId l'id de l'application * @param refType le type du referenciel @@ -1468,7 +1524,7 @@ public class OreSiService { @Value private static class RowWithData { int lineNumber; - Map<VariableComponentKey, String> datum; + Datum datum; } @Value diff --git a/src/main/java/fr/inra/oresing/transformer/ChainTransformersLineTransformer.java b/src/main/java/fr/inra/oresing/transformer/ChainTransformersLineTransformer.java new file mode 100644 index 0000000000000000000000000000000000000000..42890c334a816fac54c0e58305008e29f1e35f71 --- /dev/null +++ b/src/main/java/fr/inra/oresing/transformer/ChainTransformersLineTransformer.java @@ -0,0 +1,46 @@ +package fr.inra.oresing.transformer; + +import com.google.common.collect.ImmutableList; +import fr.inra.oresing.model.Datum; +import fr.inra.oresing.model.ReferenceDatum; + +import java.util.Deque; +import java.util.LinkedList; + +/** + * Un transformeur qui est juste le chaînage de plusieurs transformeurs. + */ +public class ChainTransformersLineTransformer implements LineTransformer { + + private final ImmutableList<LineTransformer> transformers; + + public ChainTransformersLineTransformer(ImmutableList<LineTransformer> transformers) { + this.transformers = transformers; + } + + @Override + public ReferenceDatum transform(ReferenceDatum referenceDatumBeforeTransformation) { + Deque<ReferenceDatum> transformations = new LinkedList<>(); + transformations.add(referenceDatumBeforeTransformation); + transformers.forEach(lineTransformer -> { + ReferenceDatum datumAfterLastTransformation = transformations.getLast(); + ReferenceDatum datumAfterOneMoreTransformation = lineTransformer.transform(datumAfterLastTransformation); + transformations.add(datumAfterOneMoreTransformation); + }); + ReferenceDatum datumAfterFullTransformation = transformations.getLast(); + return datumAfterFullTransformation; + } + + @Override + public Datum transform(Datum DatumBeforeTransformation) { + Deque<Datum> transformations = new LinkedList<>(); + transformations.add(DatumBeforeTransformation); + transformers.forEach(lineTransformer -> { + Datum datumAfterLastTransformation = transformations.getLast(); + Datum datumAfterOneMoreTransformation = lineTransformer.transform(datumAfterLastTransformation); + transformations.add(datumAfterOneMoreTransformation); + }); + Datum datumAfterFullTransformation = transformations.getLast(); + return datumAfterFullTransformation; + } +} diff --git a/src/main/java/fr/inra/oresing/transformer/CodifyOneLineElementTransformer.java b/src/main/java/fr/inra/oresing/transformer/CodifyOneLineElementTransformer.java new file mode 100644 index 0000000000000000000000000000000000000000..da5029777ad85d0b564748eb1637700052be7f6d --- /dev/null +++ b/src/main/java/fr/inra/oresing/transformer/CodifyOneLineElementTransformer.java @@ -0,0 +1,39 @@ +package fr.inra.oresing.transformer; + +import fr.inra.oresing.checker.CheckerTarget; +import fr.inra.oresing.model.SomethingThatCanProvideEvaluationContext; +import fr.inra.oresing.persistence.Ltree; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.assertj.core.util.Strings; + +public class CodifyOneLineElementTransformer implements TransformOneLineElementTransformer { + + private final CheckerTarget target; + + public CodifyOneLineElementTransformer(CheckerTarget target) { + this.target = target; + } + + @Override + public CheckerTarget getTarget() { + return target; + } + + @Override + public String transform(SomethingThatCanProvideEvaluationContext somethingThatCanProvideEvaluationContext, String value) { + String valueAfterCodification; + if (Strings.isNullOrEmpty(value)) { + valueAfterCodification = value; + } else { + valueAfterCodification = Ltree.escapeToLabel(value); + } + return valueAfterCodification; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("target", target) + .toString(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/transformer/GroovyExpressionOnOneLineElementTransformer.java b/src/main/java/fr/inra/oresing/transformer/GroovyExpressionOnOneLineElementTransformer.java new file mode 100644 index 0000000000000000000000000000000000000000..d14cae1aef3842ec9d085d94c9f1dd966871e7c0 --- /dev/null +++ b/src/main/java/fr/inra/oresing/transformer/GroovyExpressionOnOneLineElementTransformer.java @@ -0,0 +1,46 @@ +package fr.inra.oresing.transformer; + +import com.google.common.collect.ImmutableMap; +import fr.inra.oresing.checker.CheckerTarget; +import fr.inra.oresing.groovy.StringGroovyExpression; +import fr.inra.oresing.model.SomethingThatCanProvideEvaluationContext; +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class GroovyExpressionOnOneLineElementTransformer implements TransformOneLineElementTransformer { + + private final StringGroovyExpression groovyExpression; + + private final ImmutableMap<String, Object> context; + + private final CheckerTarget target; + + public GroovyExpressionOnOneLineElementTransformer(StringGroovyExpression groovyExpression, ImmutableMap<String, Object> context, CheckerTarget target) { + this.groovyExpression = groovyExpression; + this.context = context; + this.target = target; + } + + @Override + public CheckerTarget getTarget() { + return target; + } + + @Override + public String transform(SomethingThatCanProvideEvaluationContext somethingThatCanProvideEvaluationContext, String value) { + ImmutableMap<String, Object> context = ImmutableMap.<String, Object>builder() + .putAll(this.context) + .putAll(somethingThatCanProvideEvaluationContext.getEvaluationContext()) + .build(); + String transformed = groovyExpression.evaluate(context); + return transformed; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("groovyExpression", groovyExpression) + .append("context", context) + .append("target", target) + .toString(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/transformer/LineTransformer.java b/src/main/java/fr/inra/oresing/transformer/LineTransformer.java new file mode 100644 index 0000000000000000000000000000000000000000..f325fb202cad4e402ef3d0fde53f1a1c5b6ce7b1 --- /dev/null +++ b/src/main/java/fr/inra/oresing/transformer/LineTransformer.java @@ -0,0 +1,12 @@ +package fr.inra.oresing.transformer; + +import fr.inra.oresing.model.Datum; +import fr.inra.oresing.model.ReferenceDatum; + +public interface LineTransformer { + + Datum transform(Datum values); + + ReferenceDatum transform(ReferenceDatum referenceDatum); + +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/transformer/TransformOneLineElementTransformer.java b/src/main/java/fr/inra/oresing/transformer/TransformOneLineElementTransformer.java new file mode 100644 index 0000000000000000000000000000000000000000..f627cabdb8cb42c12ba32e7766a4afb6efdcf402 --- /dev/null +++ b/src/main/java/fr/inra/oresing/transformer/TransformOneLineElementTransformer.java @@ -0,0 +1,35 @@ +package fr.inra.oresing.transformer; + +import fr.inra.oresing.checker.CheckerTarget; +import fr.inra.oresing.model.Datum; +import fr.inra.oresing.model.ReferenceColumn; +import fr.inra.oresing.model.ReferenceDatum; +import fr.inra.oresing.model.SomethingThatCanProvideEvaluationContext; +import fr.inra.oresing.model.VariableComponentKey; + +public interface TransformOneLineElementTransformer extends LineTransformer { + + CheckerTarget getTarget(); + + @Override + default Datum transform(Datum datum) { + VariableComponentKey variableComponentKey = (VariableComponentKey) getTarget().getTarget(); + String value = datum.get(variableComponentKey); + String transformedValue = transform(datum, value); + Datum transformedDatum = Datum.copyOf(datum); + transformedDatum.put(variableComponentKey, transformedValue); + return transformedDatum; + } + + @Override + default ReferenceDatum transform(ReferenceDatum referenceDatum) { + ReferenceColumn referenceColumn = (ReferenceColumn) getTarget().getTarget(); + String value = referenceDatum.get(referenceColumn); + String transformedValue = transform(referenceDatum, value); + ReferenceDatum transformedDatum = ReferenceDatum.copyOf(referenceDatum); + transformedDatum.put(referenceColumn, transformedValue); + return transformedDatum; + } + + String transform(SomethingThatCanProvideEvaluationContext somethingThatCanProvideEvaluationContext, String value); +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/transformer/TransformationConfiguration.java b/src/main/java/fr/inra/oresing/transformer/TransformationConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..331d6fe1d81868b4e562c28651f08197c029c712 --- /dev/null +++ b/src/main/java/fr/inra/oresing/transformer/TransformationConfiguration.java @@ -0,0 +1,20 @@ +package fr.inra.oresing.transformer; + +import fr.inra.oresing.checker.GroovyConfiguration; + +/** + * Indique qu'il faut transformer la donnée (avant de la vérifier) et comment + */ +public interface TransformationConfiguration { + + /** + * Si la valeur doit être transformée en l'échappant pour lui donner la forme d'une clé + */ + boolean isCodify(); + + /** + * Avant d'être vérifiée, la donnée doit être transformée en appliquant cette expression. + */ + GroovyConfiguration getGroovy(); + +} diff --git a/src/main/java/fr/inra/oresing/transformer/TransformerFactory.java b/src/main/java/fr/inra/oresing/transformer/TransformerFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..8afa859ff7fcbfba3d030bc34596654dc9100ab1 --- /dev/null +++ b/src/main/java/fr/inra/oresing/transformer/TransformerFactory.java @@ -0,0 +1,69 @@ +package fr.inra.oresing.transformer; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import fr.inra.oresing.checker.CheckerTarget; +import fr.inra.oresing.checker.GroovyConfiguration; +import fr.inra.oresing.groovy.GroovyContextHelper; +import fr.inra.oresing.groovy.StringGroovyExpression; +import fr.inra.oresing.model.Application; +import fr.inra.oresing.model.Datum; +import fr.inra.oresing.model.ReferenceDatum; +import fr.inra.oresing.persistence.OreSiRepository; +import fr.inra.oresing.persistence.ReferenceValueRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Set; + +@Component +@Slf4j +public class TransformerFactory { + + /** + * Transformeur qui ne fait aucune transformation + */ + private static final LineTransformer NULL_LINE_TRANSFORMER = new LineTransformer() { + @Override + public Datum transform(Datum values) { + return values; + } + + @Override + public ReferenceDatum transform(ReferenceDatum referenceDatum) { + return referenceDatum; + } + }; + + @Autowired + private OreSiRepository repository; + + @Autowired + private GroovyContextHelper groovyContextHelper; + + public LineTransformer newTransformer(TransformationConfiguration configuration, Application app, CheckerTarget target) { + ReferenceValueRepository referenceValueRepository = repository.getRepository(app).referenceValue(); + ImmutableList.Builder<LineTransformer> transformersBuilder = ImmutableList.builder(); + if (configuration.isCodify()) { + transformersBuilder.add(new CodifyOneLineElementTransformer(target)); + } + if (configuration.getGroovy() != null) { + GroovyConfiguration groovyConfiguration = configuration.getGroovy(); + String expression = groovyConfiguration.getExpression(); + StringGroovyExpression groovyExpression = StringGroovyExpression.forExpression(expression); + Set<String> references = groovyConfiguration.getReferences(); + ImmutableMap<String, Object> groovyContext = groovyContextHelper.getGroovyContextForReferences(referenceValueRepository, references); + Preconditions.checkState(groovyConfiguration.getDatatypes().isEmpty(), "à ce stade, on ne gère pas la chargement de données"); + TransformOneLineElementTransformer transformer = new GroovyExpressionOnOneLineElementTransformer(groovyExpression, groovyContext, target); + transformersBuilder.add(transformer); + } + ImmutableList<LineTransformer> transformers = transformersBuilder.build(); + return new ChainTransformersLineTransformer(transformers); + } + + public LineTransformer getNullTransformer() { + return NULL_LINE_TRANSFORMER; + } +} diff --git a/src/main/resources/migration/application/V1__init_schema.sql b/src/main/resources/migration/application/V1__init_schema.sql index 9a0db35f89163d7a0bd5f3b21c2d3bf7d32c7cb2..c493aa71c83ccd7b6ae57dca064cc340df7c04b8 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/java/fr/inra/oresing/checker/CheckerTargetTest.java b/src/test/java/fr/inra/oresing/checker/CheckerTargetTest.java index 2671ab2ce03075f9b020ecafdcd1c2ad28fefe06..e17722fde5df2177fbfd5789163a28414122f612 100644 --- a/src/test/java/fr/inra/oresing/checker/CheckerTargetTest.java +++ b/src/test/java/fr/inra/oresing/checker/CheckerTargetTest.java @@ -1,5 +1,6 @@ package fr.inra.oresing.checker; +import fr.inra.oresing.model.ReferenceColumn; import fr.inra.oresing.model.VariableComponentKey; import org.junit.Assert; import org.junit.Test; @@ -7,9 +8,10 @@ import org.junit.Test; public class CheckerTargetTest { @Test public void testBuildColumnChecker(){ - CheckerTarget checkerTarget= CheckerTarget.getInstance( "bonjour",null, null); + ReferenceColumn referenceColumn = new ReferenceColumn("bonjour"); + CheckerTarget checkerTarget= CheckerTarget.getInstance(referenceColumn,null, null); Assert.assertEquals(CheckerTarget.CheckerTargetType.PARAM_COLUMN, checkerTarget.getType()); - Assert.assertEquals("bonjour", checkerTarget.getTarget()); + Assert.assertEquals(referenceColumn, checkerTarget.getTarget()); String key = checkerTarget.getInternationalizedKey("key"); Assert.assertEquals("keyWithColumn", key); } diff --git a/src/test/java/fr/inra/oresing/checker/DateLineCheckerTest.java b/src/test/java/fr/inra/oresing/checker/DateLineCheckerTest.java index 5df9a2cc1a1fbfb1370fb0d322d4e477c8c60620..014bbc073f426b68e8f932c019de5bf0fb5d4ede 100644 --- a/src/test/java/fr/inra/oresing/checker/DateLineCheckerTest.java +++ b/src/test/java/fr/inra/oresing/checker/DateLineCheckerTest.java @@ -9,7 +9,7 @@ public class DateLineCheckerTest { @Test public void testCheck() { - DateLineChecker dateLineChecker = new DateLineChecker(CheckerTarget.getInstance(new VariableComponentKey("ignored", "ignored"), null, null), "dd/MM/yyyy", null); + DateLineChecker dateLineChecker = new DateLineChecker(CheckerTarget.getInstance(new VariableComponentKey("ignored", "ignored"), null, null), "dd/MM/yyyy", null, null); Assert.assertTrue(dateLineChecker.check("12/01/2021").isSuccess()); Assert.assertFalse(dateLineChecker.check("06/21").isSuccess()); Assert.assertFalse(dateLineChecker.check("04/03/10").isSuccess()); diff --git a/src/test/java/fr/inra/oresing/checker/GroovyLineCheckerTest.java b/src/test/java/fr/inra/oresing/checker/GroovyLineCheckerTest.java index b72dfefb0c474158768f6d040ca67794b80d3b73..5e59a5c86e619e313f3f1a6680f8587ff255aacd 100644 --- a/src/test/java/fr/inra/oresing/checker/GroovyLineCheckerTest.java +++ b/src/test/java/fr/inra/oresing/checker/GroovyLineCheckerTest.java @@ -3,20 +3,22 @@ package fr.inra.oresing.checker; import com.google.common.collect.ImmutableMap; import fr.inra.oresing.OreSiTechnicalException; import fr.inra.oresing.groovy.GroovyExpression; -import fr.inra.oresing.model.Application; +import fr.inra.oresing.model.Datum; import fr.inra.oresing.model.VariableComponentKey; -import fr.inra.oresing.persistence.OreSiRepository; import fr.inra.oresing.rest.ValidationCheckResult; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; -import org.mockito.Mockito; +import java.util.Collections; import java.util.Optional; +import java.util.Set; @Slf4j public class GroovyLineCheckerTest { + private static final ImmutableMap<String, Object> EMPTY_CONTEXT = ImmutableMap.of(); + @Test public void testChecker() { String expression = String.join("\n" @@ -38,9 +40,7 @@ public class GroovyLineCheckerTest { Assert.assertTrue(compilationError.getMessage().contains("Integre")); }); - Application application = Mockito.mock(Application.class); - OreSiRepository.RepositoryForApplication repository = Mockito.mock(OreSiRepository.RepositoryForApplication.class); - GroovyLineChecker groovyLineChecker = GroovyLineChecker.forExpression(expression, application,repository,null); + GroovyLineChecker groovyLineChecker = GroovyLineChecker.forExpression(expression, EMPTY_CONTEXT, getConfiguration(expression)); ImmutableMap<VariableComponentKey, String> validDatum = ImmutableMap.of( new VariableComponentKey("temperature", "valeur"), "-12", @@ -57,10 +57,10 @@ public class GroovyLineCheckerTest { new VariableComponentKey("temperature", "unité"), "degrés" ); - Assert.assertTrue(groovyLineChecker.check(validDatum).isSuccess()); - Assert.assertFalse(groovyLineChecker.check(invalidDatum).isSuccess()); + Assert.assertTrue(groovyLineChecker.check(new Datum(validDatum)).isSuccess()); + Assert.assertFalse(groovyLineChecker.check(new Datum(invalidDatum)).isSuccess()); try { - groovyLineChecker.check(invalidDatum2).isSuccess(); + groovyLineChecker.check(new Datum(invalidDatum2)).isSuccess(); Assert.fail("une exception aurait dû être levée"); } catch (OreSiTechnicalException e) { Assert.assertTrue(e.getCause().getMessage().contains("IllegalArgumentException: unité inconnue, degrés")); @@ -82,20 +82,52 @@ public class GroovyLineCheckerTest { , "}" , "throw new IllegalArgumentException(\"unité inconnue, \" + unité);" ); - Application application = Mockito.mock(Application.class); - OreSiRepository.RepositoryForApplication repository = Mockito.mock(OreSiRepository.RepositoryForApplication.class); - - GroovyLineChecker groovyLineChecker = GroovyLineChecker.forExpression(expression, application,repository,null); + GroovyLineChecker groovyLineChecker = GroovyLineChecker.forExpression(expression, EMPTY_CONTEXT, getConfiguration(expression)); ImmutableMap<VariableComponentKey, String> validDatum = ImmutableMap.of( new VariableComponentKey("temperature", "valeur"), "-12", new VariableComponentKey("temperature", "unité"), "°C" ); try { - ValidationCheckResult validation = groovyLineChecker.check(validDatum); + ValidationCheckResult validation = groovyLineChecker.check(new Datum(validDatum)); Assert.fail("une exception aurait dû être levée"); } catch (OreSiTechnicalException e) { Assert.assertTrue(e.getMessage().contains("L'évaluation de l’expression n'a pas retourné une valeur booléenne mais 261.15.")); } } + + private GroovyLineCheckerConfiguration getConfiguration(String expression) { + return new GroovyLineCheckerConfiguration() { + + @Override + public boolean isCodify() { + throw new UnsupportedOperationException("doublure de test"); + } + + @Override + public boolean isRequired() { + throw new UnsupportedOperationException("doublure de test"); + } + + @Override + public GroovyConfiguration getGroovy() { + return new GroovyConfiguration() { + @Override + public String getExpression() { + return expression; + } + + @Override + public Set<String> getReferences() { + return Collections.emptySet(); + } + + @Override + public Set<String> getDatatypes() { + return Collections.emptySet(); + } + }; + } + }; + } } \ No newline at end of file diff --git a/src/test/java/fr/inra/oresing/model/LocalDateTimeRangeTest.java b/src/test/java/fr/inra/oresing/model/LocalDateTimeRangeTest.java index 24424576c2fe88525abbda0d272c744a4ef7d57e..225b46a8814dd6493d8cdb263fcc9e149a2a5f32 100644 --- a/src/test/java/fr/inra/oresing/model/LocalDateTimeRangeTest.java +++ b/src/test/java/fr/inra/oresing/model/LocalDateTimeRangeTest.java @@ -1,11 +1,12 @@ package fr.inra.oresing.model; import fr.inra.oresing.checker.DateLineChecker; +import fr.inra.oresing.checker.DateLineCheckerConfiguration; +import fr.inra.oresing.checker.GroovyConfiguration; import org.junit.Assert; import org.junit.Test; import java.time.Year; -import java.util.Map; public class LocalDateTimeRangeTest { @@ -28,23 +29,52 @@ public class LocalDateTimeRangeTest { } @Test public void testDayPattern() { - LocalDateTimeRange range = LocalDateTimeRange.parse("01/01/2020", new DateLineChecker(null, "dd/MM/yyyy", Map.of(DateLineChecker.PARAM_DURATION, "2 MONTHS"))); + LocalDateTimeRange range = LocalDateTimeRange.parse("01/01/2020", new DateLineChecker(null, "dd/MM/yyyy", getDateCheckerConfiguration("2 MONTHS"), null)); Assert.assertEquals("[\"2020-01-01 00:00:00\",\"2020-03-01 00:00:00\")", range.toSqlExpression()); - range = LocalDateTimeRange.parse("01/01/2020", new DateLineChecker(null, "dd/MM/yyyy", null)); + range = LocalDateTimeRange.parse("01/01/2020", new DateLineChecker(null, "dd/MM/yyyy", null, null)); Assert.assertEquals("[\"2020-01-01 00:00:00\",\"2020-01-02 00:00:00\")", range.toSqlExpression()); } @Test public void testSemiHourlyPattern() { - LocalDateTimeRange range = LocalDateTimeRange.parse("01/01/2020 01:30:00", new DateLineChecker(null, "dd/MM/yyyy HH:mm:ss", Map.of(DateLineChecker.PARAM_DURATION, "30 MINUTES"))); + LocalDateTimeRange range = LocalDateTimeRange.parse("01/01/2020 01:30:00", new DateLineChecker(null, "dd/MM/yyyy HH:mm:ss", getDateCheckerConfiguration("30 MINUTES"), null)); Assert.assertEquals("[\"2020-01-01 01:30:00\",\"2020-01-01 02:00:00\")", range.toSqlExpression()); - range = LocalDateTimeRange.parse("01/01/2020 01:30:00", new DateLineChecker(null, "dd/MM/yyyy HH:mm:ss", null)); + range = LocalDateTimeRange.parse("01/01/2020 01:30:00", new DateLineChecker(null, "dd/MM/yyyy HH:mm:ss", null, null)); Assert.assertEquals("[\"2020-01-01 00:00:00\",\"2020-01-02 00:00:00\")", range.toSqlExpression()); } @Test public void testMounthPattern() { - LocalDateTimeRange range = LocalDateTimeRange.parse("01/2020", new DateLineChecker(null, "MM/yyyy", Map.of(DateLineChecker.PARAM_DURATION, "2 MONTHS"))); + LocalDateTimeRange range = LocalDateTimeRange.parse("01/2020", new DateLineChecker(null, "MM/yyyy", getDateCheckerConfiguration("2 MONTHS"), null)); Assert.assertEquals("[\"2020-01-01 00:00:00\",\"2020-03-01 00:00:00\")", range.toSqlExpression()); - range = LocalDateTimeRange.parse("01/2020", new DateLineChecker(null, "MM/yyyy", null)); + range = LocalDateTimeRange.parse("01/2020", new DateLineChecker(null, "MM/yyyy", null, null)); Assert.assertEquals("[\"2020-01-01 00:00:00\",\"2020-02-01 00:00:00\")", range.toSqlExpression()); } + + private DateLineCheckerConfiguration getDateCheckerConfiguration(String duration) { + return new DateLineCheckerConfiguration() { + @Override + public String getPattern() { + throw new UnsupportedOperationException("doublure de test"); + } + + @Override + public String getDuration() { + return duration; + } + + @Override + public boolean isCodify() { + throw new UnsupportedOperationException("doublure de test"); + } + + @Override + public boolean isRequired() { + throw new UnsupportedOperationException("doublure de test"); + } + + @Override + public GroovyConfiguration getGroovy() { + throw new UnsupportedOperationException("doublure de test"); + } + }; + } } \ No newline at end of file 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 0000000000000000000000000000000000000000..13312bcd96878c121cfb8fe603cf2bbb6d8a7b45 --- /dev/null +++ b/src/test/java/fr/inra/oresing/persistence/LtreeTest.java @@ -0,0 +1,13 @@ +package fr.inra.oresing.persistence; + +import org.junit.Assert; +import org.junit.Test; + +public class LtreeTest { + + @Test + public void parseLabel() { + String sql = Ltree.fromUnescapedString("composition <5%/µg").getSql(); + Assert.assertEquals("composition_LESSTHANSIGN5PERCENTSIGNSOLIDUSMICROSIGNg", sql); + } +} \ No newline at end of file diff --git a/src/test/resources/data/acbb/acbb.yaml b/src/test/resources/data/acbb/acbb.yaml index 50552a24485ba307b026aed1d2bf5b92afc7a928..7b6cd1195b7031ff640e493d4fbd886791f93617 100644 --- a/src/test/resources/data/acbb/acbb.yaml +++ b/src/test/resources/data/acbb/acbb.yaml @@ -1482,7 +1482,8 @@ dataTypes: checker: name: GroovyExpression params: - expression: "Set.of(\"\", \"0\", \"1\", \"2\").contains(datum.get(\"SWC\").get(\"qualité\"))" + groovy: + expression: "Set.of(\"\", \"0\", \"1\", \"2\").contains(datum.get(\"SWC\").get(\"qualité\"))" format: headerLine: 7 firstRowLine: 10 diff --git a/src/test/resources/data/foret/foret_essai.yaml b/src/test/resources/data/foret/foret_essai.yaml index 6036939a2e50b7c3b0b8183baf7d2eee0efa4216..42dbfb636c12a9a8b04d22acf8a943c5ee768535 100644 --- a/src/test/resources/data/foret/foret_essai.yaml +++ b/src/test/resources/data/foret/foret_essai.yaml @@ -364,16 +364,18 @@ references: params: refType: theme_types_de_donnees_par_zone_etudes columns: theme_types_de_donnees_par_zone_etudes - groovy: > - String[] tab = datum.get("Nom du Thème-Type de - données-Variable").split("-"); String site = tab[0].strip(); - String theme = tab[1].strip(); String datatype = tab[2].strip(); - return references["theme_types_de_donnees_par_zone_etudes"] - .findAll {it.refValues["nom du site"].equals(site)} .findAll - {it.refValues["nom du thème"].equals(theme)} .find - {it.refValues["nom du type de données"].equals(datatype)} - .naturalKey - references: theme_types_de_donnees_par_zone_etudes + groovy: + expression: > + String[] tab = datum.get("Nom du Thème-Type de + données-Variable").split("-"); String site = tab[0].strip(); + String theme = tab[1].strip(); String datatype = tab[2].strip(); + return references["theme_types_de_donnees_par_zone_etudes"] + .findAll {it.refValues["nom du site"].equals(site)} .findAll + {it.refValues["nom du thème"].equals(theme)} .find + {it.refValues["nom du type de données"].equals(datatype)} + .naturalKey + references: + - theme_types_de_donnees_par_zone_etudes checkDataypeVariableUnite: description: référence au DataypeVariableUnite checker: @@ -382,18 +384,20 @@ references: refType: variables_par_types_de_donnees columns: variables_par_types_de_donnees required: true - groovy: > - String[] tab = datum.get("Nom du Thème-Type de - données-Variable").split("-"); String datatype = - fr.inra.oresing.rest.OreSiService.escapeKeyComponent( - tab[2].strip()); String variable = - fr.inra.oresing.rest.OreSiService.escapeKeyComponent( - tab[3].strip()); return - references.find{it.key.equals("variables_par_types_de_donnees")}.value - .findAll {it.refValues["nom de la variable"].equals(variable)} - .find {it.refValues["nom du type de données"].equals(datatype)} - .naturalKey - references: variables_par_types_de_donnees + groovy: + expression: > + String[] tab = datum.get("Nom du Thème-Type de + données-Variable").split("-"); String datatype = + fr.inra.oresing.rest.OreSiService.escapeKeyComponent( + tab[2].strip()); String variable = + fr.inra.oresing.rest.OreSiService.escapeKeyComponent( + tab[3].strip()); return + references.find{it.key.equals("variables_par_types_de_donnees")}.value + .findAll {it.refValues["nom de la variable"].equals(variable)} + .find {it.refValues["nom du type de données"].equals(datatype)} + .naturalKey + references: + - variables_par_types_de_donnees codify: true date début: description: date de début @@ -497,16 +501,18 @@ references: params: refType: theme_types_de_donnees_par_zone_etudes columns: theme_types_de_donnees_par_zone_etudes - groovy: > - String[] tab = datum.get("Nom du Thème-Type de - données-Variable").split("-"); String site = tab[0].strip(); - String theme = tab[1].strip(); String datatype = tab[2].strip(); - return references["theme_types_de_donnees_par_zone_etudes"] - .findAll {it.refValues["nom du site"].equals(site)} .findAll - {it.refValues["nom du thème"].equals(theme)} .find - {it.refValues["nom du type de données"].equals(datatype)} - .naturalKey - references: theme_types_de_donnees_par_zone_etudes + groovy: + expression: > + String[] tab = datum.get("Nom du Thème-Type de + données-Variable").split("-"); String site = tab[0].strip(); + String theme = tab[1].strip(); String datatype = tab[2].strip(); + return references["theme_types_de_donnees_par_zone_etudes"] + .findAll {it.refValues["nom du site"].equals(site)} .findAll + {it.refValues["nom du thème"].equals(theme)} .find + {it.refValues["nom du type de données"].equals(datatype)} + .naturalKey + references: + - theme_types_de_donnees_par_zone_etudes checkDataypeVariableUnite: description: référence au DataypeVariableUnite checker: @@ -515,18 +521,20 @@ references: refType: variables_par_types_de_donnees columns: variables_par_types_de_donnees required: true - groovy: > - String[] tab = datum.get("Nom du Thème-Type de - données-Variable").split("-"); String datatype = - fr.inra.oresing.rest.OreSiService.escapeKeyComponent( - tab[2].strip()); String variable = - fr.inra.oresing.rest.OreSiService.escapeKeyComponent( - tab[3].strip()); return - references.find{it.key.equals("variables_par_types_de_donnees")}.value - .findAll {it.refValues["nom de la variable"].equals(variable)} - .find {it.refValues["nom du type de données"].equals(datatype)} - .naturalKey - references: variables_par_types_de_donnees + groovy: + expression: > + String[] tab = datum.get("Nom du Thème-Type de + données-Variable").split("-"); String datatype = + fr.inra.oresing.rest.OreSiService.escapeKeyComponent( + tab[2].strip()); String variable = + fr.inra.oresing.rest.OreSiService.escapeKeyComponent( + tab[3].strip()); return + references.find{it.key.equals("variables_par_types_de_donnees")}.value + .findAll {it.refValues["nom de la variable"].equals(variable)} + .find {it.refValues["nom du type de données"].equals(datatype)} + .naturalKey + references: + - variables_par_types_de_donnees codify: true date début: description: date de début @@ -621,16 +629,18 @@ references: params: refType: theme_types_de_donnees_par_zone_etudes columns: theme_types_de_donnees_par_zone_etudes - groovy: > - String[] tab = datum.get("Nom du Thème-Type de - données-Variable").split("-"); String site = tab[0].strip(); - String theme = tab[1].strip(); String datatype = tab[2].strip(); - return references["theme_types_de_donnees_par_zone_etudes"] - .findAll {it.refValues["nom du site"].equals(site)} .findAll - {it.refValues["nom du thème"].equals(theme)} .find - {it.refValues["nom du type de données"].equals(datatype)} - .naturalKey - references: theme_types_de_donnees_par_zone_etudes + groovy: + expression: > + String[] tab = datum.get("Nom du Thème-Type de + données-Variable").split("-"); String site = tab[0].strip(); + String theme = tab[1].strip(); String datatype = tab[2].strip(); + return references["theme_types_de_donnees_par_zone_etudes"] + .findAll {it.refValues["nom du site"].equals(site)} .findAll + {it.refValues["nom du thème"].equals(theme)} .find + {it.refValues["nom du type de données"].equals(datatype)} + .naturalKey + references: + - theme_types_de_donnees_par_zone_etudes checkDataypeVariableUnite: description: référence au DataypeVariableUnite checker: @@ -639,18 +649,20 @@ references: refType: variables_par_types_de_donnees columns: variables_par_types_de_donnees required: true - groovy: > - String[] tab = datum.get("Nom du Thème-Type de - données-Variable").split("-"); String datatype = - fr.inra.oresing.rest.OreSiService.escapeKeyComponent( - tab[2].strip()); String variable = - fr.inra.oresing.rest.OreSiService.escapeKeyComponent( - tab[3].strip()); return - references.find{it.key.equals("variables_par_types_de_donnees")}.value - .findAll {it.refValues["nom de la variable"].equals(variable)} - .find {it.refValues["nom du type de données"].equals(datatype)} - .naturalKey - references: variables_par_types_de_donnees + groovy: + expression: > + String[] tab = datum.get("Nom du Thème-Type de + données-Variable").split("-"); String datatype = + fr.inra.oresing.rest.OreSiService.escapeKeyComponent( + tab[2].strip()); String variable = + fr.inra.oresing.rest.OreSiService.escapeKeyComponent( + tab[3].strip()); return + references.find{it.key.equals("variables_par_types_de_donnees")}.value + .findAll {it.refValues["nom de la variable"].equals(variable)} + .find {it.refValues["nom du type de données"].equals(datatype)} + .naturalKey + references: + - variables_par_types_de_donnees codify: true internationalizationName: fr: >- @@ -730,22 +742,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -963,22 +977,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -1196,22 +1212,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -1429,22 +1447,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -1662,22 +1682,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -1895,22 +1917,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -2111,22 +2135,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -2327,22 +2353,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -2543,22 +2571,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -2759,22 +2789,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -2976,22 +3008,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -3220,22 +3254,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -3466,22 +3502,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -3679,22 +3717,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -3841,7 +3881,6 @@ dataTypes: name: Float params: required: true - codify: true quality_class: checker: name: RegularExpression @@ -3864,7 +3903,6 @@ dataTypes: name: Float params: required: true - codify: true quality_class: checker: name: RegularExpression @@ -3887,7 +3925,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "mètre par seconde" checker: @@ -3903,7 +3940,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "millimètre par demi-heure" checker: @@ -4166,22 +4202,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -4301,7 +4339,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "mégajoule par mètre et par jour" checker: @@ -4317,7 +4354,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "kilogramme par mètre et par seconde" checker: @@ -4333,7 +4369,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "mètre par seconde" checker: @@ -4349,7 +4384,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "millilitre par jour" checker: @@ -4592,22 +4626,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -4727,7 +4763,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "mégajoule par mètre et par mois" checker: @@ -4743,7 +4778,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "kilogramme par mètre et par seconde" checker: @@ -4759,7 +4793,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "mètre par seconde" checker: @@ -4775,7 +4808,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "litre par mois" checker: @@ -5024,22 +5056,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -5172,7 +5206,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "watt par mètre carré" checker: @@ -5188,7 +5221,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "watt par mètre carré" checker: @@ -5204,7 +5236,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "micromole par mètre carré et par seconde" checker: @@ -5220,7 +5251,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "micromole par mètre carré et par seconde" checker: @@ -5587,22 +5617,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -5722,7 +5754,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "watt par mètre carré" checker: @@ -5738,7 +5769,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "watt par mètre carré" checker: @@ -5754,7 +5784,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "micromole par mètre carré et par jour" checker: @@ -5770,7 +5799,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "micromole par mètre carré et par jour" checker: @@ -6133,22 +6161,24 @@ dataTypes: checker: name: Reference params: - groovy: > - String parent = datumByVariableAndComponent.localization.zones_etudes_parent; - String nom = datumByVariableAndComponent.localization.zones_etudes; - String hierarchicalKey = ""; - if ("".equals(nom)){ - hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); - }else{ - parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) - nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) - hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) - } - - return references - .find{it.key.equals("zones_etudes")}.value - .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey - references: zones_etudes + groovy: + expression: > + String parent = datumByVariableAndComponent.localization.zones_etudes_parent; + String nom = datumByVariableAndComponent.localization.zones_etudes; + String hierarchicalKey = ""; + if ("".equals(nom)){ + hierarchicalKey = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent); + }else{ + parent = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(parent) + nom = fr.inra.oresing.rest.OreSiService.escapeKeyComponent(nom) + hierarchicalKey = String.format("%s.%s__%s", parent, parent, nom) + } + + return references + .find{it.key.equals("zones_etudes")}.value + .find {it.hierarchicalKey.equals(hierarchicalKey)} .hierarchicalKey + references: + - zones_etudes codify: true refType: zones_etudes required: true @@ -6268,7 +6298,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "mégajoule par mètre et par mois" checker: @@ -6284,7 +6313,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "mégajoule par mètre et par mois" checker: @@ -6300,7 +6328,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "micromole par mètre carré et par mois" checker: @@ -6316,7 +6343,6 @@ dataTypes: name: Float params: required: true - codify: true unit: defaultValue: return "micromole par mètre carré et par mois" checker: @@ -6614,4 +6640,4 @@ dataTypes: - header: WS boundTo: variable: WS - component: value + component: value \ No newline at end of file diff --git a/src/test/resources/data/monsore/monsore-with-repository.yaml b/src/test/resources/data/monsore/monsore-with-repository.yaml index d6d75547133832529e287a2f1cdcfe0451c9b5ee..20e911205e874e85271eae706ebdcd9f637a3a39 100644 --- a/src/test/resources/data/monsore/monsore-with-repository.yaml +++ b/src/test/resources/data/monsore/monsore-with-repository.yaml @@ -205,9 +205,10 @@ references: checker: name: GroovyExpression params: - expression: > - String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); - return application.getDataType().contains(datatype); + groovy: + expression: > + String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); + return application.getDataType().contains(datatype); keyColumns: ["nom du projet","nom du site","nom du thème","nom du type de données" ] columns: nom du projet: @@ -308,9 +309,10 @@ references: checker: name: GroovyExpression params: - expression: > - String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); - return application.getDataType().contains(datatype); + groovy: + expression: > + String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); + return application.getDataType().contains(datatype); keyColumns: [nom du type de données,nom de la variable] internationalizationName: fr: Variables et unités par type de données @@ -348,7 +350,8 @@ dataTypes: components: bassin: params: - references: sites + references: + - sites replace: true defaultValue: > return references.get("sites") @@ -361,7 +364,8 @@ dataTypes: plateforme: chemin: params: - references: sites + references: + - sites 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"))} @@ -420,31 +424,35 @@ dataTypes: checker: name: GroovyExpression params: - expression: > - return referencesValues.get("variables_et_unites_par_types_de_donnees") - .findAll{it.get("nom du type de données").equals(params.get("datatype"))} - .find{it.get("nom de la variable").equals(params.get("codeVariable"))} - .get("nom de l'unité").equals(datum.get(params.get("variable")).get(params.get("component"))); - references: variables_et_unites_par_types_de_donnees - datatype: "piegeage_en_montee" - variable: "Couleur des individus" - codeVariable: couleur_des_individus - component: unit + groovy: + expression: > + String datatype = "piegeage_en_montee"; + String variable = "Couleur des individus"; + String codeVariable = "couleur_des_individus"; + String component = "unit"; + return referencesValues.get("variables_et_unites_par_types_de_donnees") + .findAll{it.get("nom du type de données").equals(datatype)} + .find{it.get("nom de la variable").equals(codeVariable)} + .get("nom de l'unité").equals(datum.get(variable).get(component)); + references: + - variables_et_unites_par_types_de_donnees unitOfIndividus: description: "vérifie l'unité du nombre d'individus" checker: name: GroovyExpression params: - expression: > - return referencesValues.get("variables_et_unites_par_types_de_donnees") - .findAll{it.get("nom du type de données").equals(params.get("datatype"))} - .find{it.get("nom de la variable").equals(params.get("codeVariable"))} - .get("nom de l'unité").equals(datum.get(params.get("variable")).get(params.get("component"))); - references: variables_et_unites_par_types_de_donnees - datatype: "piegeage_en_montee" - variable: "Nombre d'individus" - codeVariable: nombre_d_individus - component: unit + groovy: + expression: > + String datatype = "piegeage_en_montee"; + String variable = "Nombre d'individus"; + String codeVariable = "nombre_d_individus"; + String component = "unit"; + return referencesValues.get("variables_et_unites_par_types_de_donnees") + .findAll{it.get("nom du type de données").equals(datatype)} + .find{it.get("nom de la variable").equals(codeVariable)} + .get("nom de l'unité").equals(datum.get(variable).get(component)); + references: + - variables_et_unites_par_types_de_donnees format: headerLine: 4 firstRowLine: 5 diff --git a/src/test/resources/data/monsore/monsore.yaml b/src/test/resources/data/monsore/monsore.yaml index f11cc867bbf1dfec7c93659be416bd85d305aaab..e82a399252b3bc616d8ad50a3dd4709ddfd46786 100644 --- a/src/test/resources/data/monsore/monsore.yaml +++ b/src/test/resources/data/monsore/monsore.yaml @@ -2,8 +2,8 @@ version: 0 application: defaultLanguage: fr internationalization: - fr: SOERE mon SOERE - en: SOERE my SOERE + fr: SOERE mon SOERE avec dépôt + en: SOERE my SOERE with repository name: MONSORE version: 1 compositeReferences: @@ -176,7 +176,7 @@ references: internationalizationDisplay: pattern: fr: 'nom du projet: {nom du projet}, nom du site : {nom du site}, nom du thème : {nom du thème}, nom du type de données : {nom du type de données} ' - en: 'project name: {nom du projet}, site name : {nom du site}, thematic : {nom du thème}, datatype : {nom du type de données} ' + en: 'projet name: {nom du projet}, site name : {nom du site}, theme name : {nom du thème}, data type name : {nom du type de données} ' validations: projetRef: description: "référence au projet" @@ -205,9 +205,10 @@ references: checker: name: GroovyExpression params: - expression: > - String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); - return application.getDataType().contains(datatype); + groovy: + expression: > + String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); + return application.getDataType().contains(datatype); keyColumns: ["nom du projet","nom du site","nom du thème","nom du type de données" ] columns: nom du projet: @@ -308,9 +309,10 @@ references: checker: name: GroovyExpression params: - expression: > - String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); - return application.getDataType().contains(datatype); + groovy: + expression: > + String datatype = Arrays.stream(datum.get("nom du type de données").split("_")).collect{it.substring(0, 1)}.join(); + return application.getDataType().contains(datatype); keyColumns: [nom du type de données,nom de la variable] internationalizationName: fr: Variables et unités par type de données @@ -331,9 +333,9 @@ dataTypes: en: Trap in ascent internationalizationDisplay: especes: - pattern: - fr: 'espèce :{esp_nom}' - en: 'espèce :{esp_nom}' + pattern: + fr: 'espèce :{esp_nom}' + en: 'espèce :{esp_nom}' data: projet: components: @@ -346,7 +348,8 @@ dataTypes: components: bassin: params: - references: sites + references: + - sites replace: true defaultValue: > return references.get("sites") @@ -359,7 +362,8 @@ dataTypes: plateforme: chemin: params: - references: sites + references: + - sites 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"))} @@ -418,31 +422,35 @@ dataTypes: checker: name: GroovyExpression params: - expression: > - return referencesValues.get("variables_et_unites_par_types_de_donnees") - .findAll{it.get("nom du type de données").equals(params.get("datatype"))} - .find{it.get("nom de la variable").equals(params.get("codeVariable"))} - .get("nom de l'unité").equals(datum.get(params.get("variable")).get(params.get("component"))); - references: variables_et_unites_par_types_de_donnees - datatype: "piegeage_en_montee" - variable: "Couleur des individus" - codeVariable: couleur_des_individus - component: unit + groovy: + expression: > + String datatype = "piegeage_en_montee"; + String variable = "Couleur des individus"; + String codeVariable = "couleur_des_individus"; + String component = "unit"; + return referencesValues.get("variables_et_unites_par_types_de_donnees") + .findAll{it.get("nom du type de données").equals(datatype)} + .find{it.get("nom de la variable").equals(codeVariable)} + .get("nom de l'unité").equals(datum.get(variable).get(component)); + references: + - variables_et_unites_par_types_de_donnees unitOfIndividus: description: "vérifie l'unité du nombre d'individus" checker: name: GroovyExpression params: - expression: > - return referencesValues.get("variables_et_unites_par_types_de_donnees") - .findAll{it.get("nom du type de données").equals(params.get("datatype"))} - .find{it.get("nom de la variable").equals(params.get("codeVariable"))} - .get("nom de l'unité").equals(datum.get(params.get("variable")).get(params.get("component"))); - references: variables_et_unites_par_types_de_donnees - datatype: "piegeage_en_montee" - variable: "Nombre d'individus" - codeVariable: nombre_d_individus - component: unit + groovy: + expression: > + String datatype = "piegeage_en_montee"; + String variable = "Nombre d'individus"; + String codeVariable = "nombre_d_individus"; + String component = "unit"; + return referencesValues.get("variables_et_unites_par_types_de_donnees") + .findAll{it.get("nom du type de données").equals(datatype)} + .find{it.get("nom de la variable").equals(codeVariable)} + .get("nom de l'unité").equals(datum.get(variable).get(component)); + references: + - variables_et_unites_par_types_de_donnees format: headerLine: 4 firstRowLine: 5 diff --git a/src/test/resources/data/validation/fake-app.yaml b/src/test/resources/data/validation/fake-app.yaml index b9c3ccfa32b371914776e10533df855147161a7e..208988dd0c3fe9fe9d85cbdc9309439247aa0767 100644 --- a/src/test/resources/data/validation/fake-app.yaml +++ b/src/test/resources/data/validation/fake-app.yaml @@ -142,7 +142,8 @@ dataTypes: checker: name: GroovyExpression params: - expression: "true" + groovy: + expression: "true" format: constants: - rowNumber: 1