diff --git a/src/main/java/fr/inra/oresing/checker/DateLineChecker.java b/src/main/java/fr/inra/oresing/checker/DateLineChecker.java index b1ba76f25661e6f063252ca02f68063ead7c9ae4..c2565d47f04cc9691fc6e70c40291c9225d9653c 100644 --- a/src/main/java/fr/inra/oresing/checker/DateLineChecker.java +++ b/src/main/java/fr/inra/oresing/checker/DateLineChecker.java @@ -10,13 +10,9 @@ 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<DateLineCheckerConfiguration> { - public static final String PARAM_PATTERN = "pattern"; - 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 DateLineCheckerConfiguration configuration; @@ -52,15 +48,10 @@ public class DateLineChecker implements CheckerOnOneVariableComponentLineChecker try { value = sortableDateToFormattedDate(value); TemporalAccessor date = dateTimeFormatter.parse(value); - Map<String, Object> params = ImmutableMap.of( - PARAM_PATTERN, pattern, - PARAM_DATE_TIME_FORMATTER, dateTimeFormatter, - target.getType().name(), target.getTarget(), - PARAM_DATE, date - ); - validationCheckResult = DateValidationCheckResult.success(params); + validationCheckResult = DateValidationCheckResult.success(target, date); } catch (DateTimeParseException e) { validationCheckResult = DateValidationCheckResult.error( + target, getTarget().getInternationalizedKey("invalidDate"), ImmutableMap.of( "target", target.getTarget(), "pattern", pattern, @@ -81,6 +72,6 @@ public class DateLineChecker implements CheckerOnOneVariableComponentLineChecker @Override public SqlPrimitiveType getSqlType() { - return SqlPrimitiveType.TEXT; + return SqlPrimitiveType.COMPOSITE_DATE; } } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/persistence/SqlPrimitiveType.java b/src/main/java/fr/inra/oresing/persistence/SqlPrimitiveType.java index 2911e1c6e3fe8b40621d4bd7ab3b6ae4e2b82b72..1d48096ee83ca852275c4732664fce19e1c012e5 100644 --- a/src/main/java/fr/inra/oresing/persistence/SqlPrimitiveType.java +++ b/src/main/java/fr/inra/oresing/persistence/SqlPrimitiveType.java @@ -7,7 +7,8 @@ public enum SqlPrimitiveType { UUID, TEXT, INTEGER, - NUMERIC; + NUMERIC, + COMPOSITE_DATE; /** * Le type en SQL, tel qu'il faut l'écrire pour faire un cast @@ -24,4 +25,4 @@ public enum SqlPrimitiveType { public boolean isEmptyStringValidValue() { return this == TEXT; } -} +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java index f54b289718bd06ccf7b9934fb876960f8d653e1c..1a1da85678a12eca3f034d2831d29649a200e57e 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiService.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java @@ -3,76 +3,23 @@ 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.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.LinkedListMultimap; -import com.google.common.collect.ListMultimap; -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.collect.*; import com.google.common.primitives.Ints; import fr.inra.oresing.OreSiTechnicalException; import fr.inra.oresing.ValidationLevel; -import fr.inra.oresing.checker.CheckerFactory; -import fr.inra.oresing.checker.DateLineChecker; -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.Multiplicity; -import fr.inra.oresing.checker.ReferenceLineChecker; -import fr.inra.oresing.checker.ReferenceLineCheckerConfiguration; +import fr.inra.oresing.checker.*; 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.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.ReferenceColumnMultipleValue; -import fr.inra.oresing.model.ReferenceColumnSingleValue; -import fr.inra.oresing.model.ReferenceColumnValue; -import fr.inra.oresing.model.ReferenceDatum; -import fr.inra.oresing.model.ReferenceValue; -import fr.inra.oresing.model.VariableComponentKey; +import fr.inra.oresing.model.*; 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.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.*; import fr.inra.oresing.persistence.roles.OreSiRightOnApplicationRole; import fr.inra.oresing.persistence.roles.OreSiUserRole; -import fr.inra.oresing.rest.validationcheckresults.DateValidationCheckResult; -import fr.inra.oresing.rest.validationcheckresults.DefaultValidationCheckResult; -import fr.inra.oresing.rest.validationcheckresults.DuplicationLineValidationCheckResult; -import fr.inra.oresing.rest.validationcheckresults.MissingParentLineValidationCheckResult; -import fr.inra.oresing.rest.validationcheckresults.ReferenceValidationCheckResult; +import fr.inra.oresing.rest.validationcheckresults.*; import fr.inra.oresing.transformer.TransformerFactory; import lombok.Value; import lombok.extern.slf4j.Slf4j; @@ -104,21 +51,7 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -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.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -390,8 +323,9 @@ public class OreSiService { public UUID addReference(Application app, String refType, MultipartFile file) throws IOException { ReferenceValueRepository referenceValueRepository = repo.getRepository(app).referenceValue(); + Map<ReferenceColumn, DateValidationCheckResult> dateValidationCheckResultImmutableMap = new HashMap<>(); authenticationService.setRoleForClient(); - UUID fileId = storeFile(app, file,""); + UUID fileId = storeFile(app, file, ""); Configuration conf = app.getConfiguration(); Configuration.ReferenceDescription ref = conf.getReferences().get(refType); @@ -499,7 +433,21 @@ public class OreSiService { ReferenceDatum referenceDatum = ReferenceDatum.copyOf(referenceDatumBeforeChecking); lineCheckers.forEach(lineChecker -> { Set<ValidationCheckResult> validationCheckResults = lineChecker.checkReference(referenceDatumBeforeChecking); - if (lineChecker instanceof ReferenceLineChecker) { + if (lineChecker instanceof DateLineChecker) { + validationCheckResults.stream() + .filter(ValidationCheckResult::isSuccess) + .filter(DateValidationCheckResult.class::isInstance) + .map(DateValidationCheckResult.class::cast) + .forEach(dateValidationCheckResult -> { + ReferenceColumn referenceColumn = (ReferenceColumn) dateValidationCheckResult.getTarget().getTarget(); + ReferenceColumnValue referenceColumnRawValue = referenceDatumBeforeChecking.get(referenceColumn); + ReferenceColumnValue valueToStoreInDatabase = referenceColumnRawValue + .transform(rawValue -> + String.format("date:%s:%s", dateValidationCheckResult.getLocalDateTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), rawValue) + ); + referenceDatum.put(referenceColumn, valueToStoreInDatabase); + }); + } else if (lineChecker instanceof ReferenceLineChecker) { ReferenceLineChecker referenceLineChecker = (ReferenceLineChecker) lineChecker; ReferenceColumn referenceColumn = (ReferenceColumn) referenceLineChecker.getTarget().getTarget(); SetMultimap<ReferenceColumn, String> rawValueReplacedByKeys = HashMultimap.create(); @@ -850,7 +798,7 @@ public class OreSiService { .orElseGet(() -> { UUID fileId = null; try { - fileId = storeFile(app, file,""); + fileId = storeFile(app, file, ""); } catch (IOException e) { return null; } @@ -919,7 +867,7 @@ public class OreSiService { ValidationCheckResult validationCheckResult = lineChecker.check(datum); if (validationCheckResult.isSuccess()) { if (validationCheckResult instanceof DateValidationCheckResult) { - VariableComponentKey variableComponentKey = (VariableComponentKey) ((DateValidationCheckResult) validationCheckResult).getTarget(); + VariableComponentKey variableComponentKey = (VariableComponentKey) ((DateValidationCheckResult) validationCheckResult).getTarget().getTarget(); dateValidationCheckResultImmutableMap.put(variableComponentKey, (DateValidationCheckResult) validationCheckResult); } if (validationCheckResult instanceof ReferenceValidationCheckResult) { @@ -972,7 +920,7 @@ public class OreSiService { String component = variableComponentKey.getComponent(); String value = entry2.getValue(); if (dateValidationCheckResultImmutableMap.containsKey(entry2.getKey())) { - value = String.format("date:%s:%s", dateValidationCheckResultImmutableMap.get(variableComponentKey).getMessage(), value); + value = String.format("date:%s:%s", dateValidationCheckResultImmutableMap.get(variableComponentKey).getLocalDateTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), value); } toStore.computeIfAbsent(variable, k -> new LinkedHashMap<>()).put(component, value); refsLinkedToToStore.computeIfAbsent(variable, k -> new LinkedHashMap<>()).put(component, refsLinkedTo.get(variableComponentKey)); diff --git a/src/main/java/fr/inra/oresing/rest/RelationalService.java b/src/main/java/fr/inra/oresing/rest/RelationalService.java index 9a92d98db5500254fa80636ad4b75ec8af1473cc..09a39eb3da616b0ada62a05efeff877c1a2a2bf5 100644 --- a/src/main/java/fr/inra/oresing/rest/RelationalService.java +++ b/src/main/java/fr/inra/oresing/rest/RelationalService.java @@ -349,12 +349,19 @@ public class RelationalService implements InitializingBean, DisposableBean { String columnsAsSchema = allReferenceColumnsPerMultiplicity.values().stream() .map(referenceColumn -> { String columnName = quoteSqlIdentifier(referenceColumn.getColumn()); - SqlPrimitiveType columnType = sqlTypePerColumns.getOrDefault(referenceColumn, SqlPrimitiveType.TEXT); - String columnDeclaration = String.format("%s %s", columnName, columnType.getSql()); + String columnDeclaration = String.format("%s %s", columnName, SqlPrimitiveType.TEXT); return columnDeclaration; }) .collect(Collectors.joining(", ", "(", ")")); String quotedReferenceType = quoteSqlIdentifier(referenceType); + String castedColumnSelect = allReferenceColumnsPerMultiplicity.values().stream() + .map(referenceColumn -> { + String columnName = quoteSqlIdentifier(referenceColumn.getColumn()); + SqlPrimitiveType columnType = sqlTypePerColumns.getOrDefault(referenceColumn, SqlPrimitiveType.TEXT); + String columnDeclaration = String.format("%s.%s::%s\n", quotedReferenceType,columnName, columnType.getSql()); + return columnDeclaration; + }) + .collect(Collectors.joining(", ")); // par exemple "projet"(nom_en text, nom_fr text, nom_key text, definition_en text, definition_fr text) String schemaDeclaration = quotedReferenceType + columnsAsSchema; @@ -363,8 +370,10 @@ public class RelationalService implements InitializingBean, DisposableBean { String quotedViewHierarchicalKeyColumnName = quoteSqlIdentifier(referenceType + "_hierarchicalKey"); String quotedViewNaturalKeyColumnName = quoteSqlIdentifier(referenceType + "_naturalKey"); String referenceValueTableName = SqlSchema.forApplication(app).referenceValue().getSqlIdentifier(); - String referenceView = "select referenceValue.id as " + quotedViewIdColumnName + ", referenceValue.hierarchicalKey as " + quotedViewHierarchicalKeyColumnName + ", referenceValue.naturalKey as " + quotedViewNaturalKeyColumnName + ", " + quotedReferenceType + ".* " - + " from " + referenceValueTableName + ", jsonb_to_record(referenceValue.refValues) as " + schemaDeclaration + String referenceView = "select referenceValue.id as " + quotedViewIdColumnName + ", referenceValue.hierarchicalKey as " + quotedViewHierarchicalKeyColumnName + ", referenceValue.naturalKey as " + quotedViewNaturalKeyColumnName + ", " + + castedColumnSelect + + " from " + referenceValueTableName + ", " + + "jsonb_to_record(referenceValue.refValues) as " + schemaDeclaration + " where referenceType = '" + referenceType + "' and application = '" + appId + "'::uuid"; if (log.isTraceEnabled()) { diff --git a/src/main/java/fr/inra/oresing/rest/validationcheckresults/DateValidationCheckResult.java b/src/main/java/fr/inra/oresing/rest/validationcheckresults/DateValidationCheckResult.java index 8bf361a4f9a4d3445b4af60f24e80c6c43b32a5a..93be9d30ed2a98c5908382bb9f1e40fb3205971b 100644 --- a/src/main/java/fr/inra/oresing/rest/validationcheckresults/DateValidationCheckResult.java +++ b/src/main/java/fr/inra/oresing/rest/validationcheckresults/DateValidationCheckResult.java @@ -3,7 +3,6 @@ package fr.inra.oresing.rest.validationcheckresults; import com.google.common.collect.ImmutableMap; import fr.inra.oresing.ValidationLevel; import fr.inra.oresing.checker.CheckerTarget; -import fr.inra.oresing.checker.DateLineChecker; import fr.inra.oresing.rest.ValidationCheckResult; import lombok.Value; @@ -14,52 +13,26 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalQueries; import java.util.Map; -import java.util.Optional; @Value public class DateValidationCheckResult implements ValidationCheckResult { ValidationLevel level; String message; Map<String, Object> messageParams; - Object target; + CheckerTarget target; TemporalAccessor date; - - public DateValidationCheckResult(ValidationLevel level, String message, Map<String, Object> messageParams, Object target, TemporalAccessor date) { - this.level = level; - this.message = message; - this.messageParams = messageParams; - this.target = target; - this.date = date; - } - - public DateValidationCheckResult(ValidationLevel success, Map<String, Object> params) { - this.messageParams = params; - this.level = success; - this.target = Optional.ofNullable(messageParams) - .map(mp->mp.getOrDefault( - CheckerTarget.CheckerTargetType.PARAM_COLUMN.name(), - mp.getOrDefault(CheckerTarget.CheckerTargetType.PARAM_VARIABLE_COMPONENT_KEY.name(), null) - )) - .orElse(null); - this.date = (TemporalAccessor) Optional.ofNullable(messageParams).map(mp->mp.getOrDefault(DateLineChecker.PARAM_DATE, null)).orElse(null); - LocalDateTime localDateTime = LocalDateTime.MIN; - if(date!=null){ - LocalDate localdate = date.query(TemporalQueries.localDate()); - localdate=localdate==null?LocalDate.MIN:localdate; - LocalTime localTime = date.query(TemporalQueries.localTime()); - localTime=localTime==null?LocalTime.MIN:localTime; - localDateTime = localdate.atTime(localTime); - } - - - this.message = localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); - } - - public static DateValidationCheckResult success(Map<String, Object> params) { - return new DateValidationCheckResult(ValidationLevel.SUCCESS, params); + LocalDateTime localDateTime; + + public static DateValidationCheckResult success(CheckerTarget target, TemporalAccessor date) { + LocalDate localdate = date.query(TemporalQueries.localDate()); + localdate = localdate == null ? LocalDate.of(1970, 1, 1) : localdate; + LocalTime localTime = date.query(TemporalQueries.localTime()); + localTime = localTime == null ? LocalTime.MIN : localTime; + LocalDateTime localDateTime = localdate.atTime(localTime); + return new DateValidationCheckResult(ValidationLevel.SUCCESS, null, null, target, date, localDateTime); } - public static DateValidationCheckResult error(String message, ImmutableMap<String, Object> messageParams) { - return new DateValidationCheckResult(ValidationLevel.ERROR, message, messageParams, null, null); + public static DateValidationCheckResult error(CheckerTarget target, String message, ImmutableMap<String, Object> messageParams) { + return new DateValidationCheckResult(ValidationLevel.ERROR, message, messageParams, target, null, null); } } \ No newline at end of file diff --git a/src/main/resources/migration/main/V1__init_schema.sql b/src/main/resources/migration/main/V1__init_schema.sql index 9222f240c35558d9a743b64d30109105ce38997d..a7126613fd1120e8efe0a1a84e7dee42ab368316 100644 --- a/src/main/resources/migration/main/V1__init_schema.sql +++ b/src/main/resources/migration/main/V1__init_schema.sql @@ -199,4 +199,29 @@ CREATE POLICY "applicationCreator_Application_select" ON Application AS PERMISSI USING ( true ); CREATE AGGREGATE jsonb_object_agg(jsonb) (SFUNC = 'jsonb_concat', STYPE = jsonb, INITCOND = '{}'); -CREATE AGGREGATE aggregate_by_array_concatenation(anyarray) (SFUNC = 'array_cat', STYPE = anyarray, INITCOND = '{}'); \ No newline at end of file +CREATE AGGREGATE aggregate_by_array_concatenation(anyarray) (SFUNC = 'array_cat', STYPE = anyarray, INITCOND = '{}'); + +create type COMPOSITE_DATE as ( + datetimestamp "timestamp", + formattedDate "varchar" +) ; +CREATE FUNCTION castTextToCompositeDate(Text) RETURNS COMPOSITE_DATE AS + 'select + (substring($1 from 6 for 19)::timestamp, + substring($1 from 26))::COMPOSITE_DATE;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; +CREATE CAST (TEXT AS COMPOSITE_DATE) WITH FUNCTION castTextToCompositeDate(Text) AS ASSIGNMENT; +CREATE FUNCTION castCompositeDateToTimestamp(COMPOSITE_DATE) RETURNS TIMESTAMP +AS 'select ($1).datetimestamp;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; +CREATE CAST (COMPOSITE_DATE AS TIMESTAMP) WITH FUNCTION castCompositeDateToTimestamp(COMPOSITE_DATE) AS ASSIGNMENT; +CREATE FUNCTION castCompositeDateToFormattedDate(COMPOSITE_DATE) RETURNS Text +AS 'select ($1).formattedDate;' + LANGUAGE SQL + IMMUTABLE + RETURNS NULL ON NULL INPUT; +CREATE CAST (COMPOSITE_DATE AS Text) WITH FUNCTION castCompositeDateToFormattedDate(COMPOSITE_DATE) AS ASSIGNMENT; \ No newline at end of file