Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<!-- Pi Test-->
<pitest.version>1.23.0</pitest.version>
<pitest.junit.version>1.2.3</pitest.junit.version>
<bpm.version>1.1.0</bpm.version>
<bpm.version>1.1.1</bpm.version>
</properties>
<dependencies>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package fr.insee.genesis.controller.dto;

import fr.insee.genesis.controller.utils.ExportType;
import fr.insee.genesis.domain.model.context.schedule.DestinationType;
import fr.insee.genesis.domain.model.context.schedule.TrustParameters;
import fr.insee.genesis.domain.model.surveyunit.Mode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class KraftwerkExecutionScheduleInput {
private String collectionInstrumentId;
private String scheduleUuid;
private ExportType exportType;
private String frequency;
private LocalDateTime startDate;
private LocalDateTime endDate;
private Mode mode;
private DestinationType destinationType;
private boolean addStates;
private String destinationFolder;
private boolean useAsymmetricEncryption;
private boolean useSymmetricEncryption;
private TrustParameters trustParameters;
private Integer batchSize;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package fr.insee.genesis.controller.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import fr.insee.genesis.controller.utils.ExportType;
import fr.insee.genesis.controller.validation.schedule.ValidScheduleRequest;
import fr.insee.genesis.domain.model.context.schedule.DestinationType;
import fr.insee.genesis.domain.model.surveyunit.Mode;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Request used to schedule a Kraftwerk export workflow")
@ValidScheduleRequest
public class ScheduleRequestDto {

@NotBlank
@Schema(description = "Collection instrument to call Kraftwerk on", example = "EAP2026A00", requiredMode = Schema.RequiredMode.REQUIRED)
private String collectionInstrumentId;

@NotNull
@Schema(description = "Export type", allowableValues = {"JSON", "CSV_PARQUET"}, requiredMode = Schema.RequiredMode.REQUIRED)
private ExportType exportType;

@NotBlank
@Schema(description = "Frequency in Spring cron format (6 inputs). Example: 0 0 6 * * *", example = "0 0 6 * * *", requiredMode = Schema.RequiredMode.REQUIRED)
private String frequency;

@NotNull
@Schema(description = "Schedule effective date and time", example = "2024-01-01T12:00:00", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime scheduleBeginDate;

@NotNull
@Schema(description = "Schedule end date and time", example = "2024-01-01T12:00:00", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime scheduleEndDate;

private Mode mode;

@Schema(defaultValue = "APPLISHARE")
private DestinationType destinationType = DestinationType.APPLISHARE;

@Schema(defaultValue = "false")
private boolean useSymmetricEncryption = false;

@Schema(defaultValue = "false")
private boolean useAsymmetricEncryption = false;

@Schema(description = "Encryption vault path")
private String encryptionVaultPath = "";

@Schema(defaultValue = "false")
private boolean useSignature = false;

@Schema(description = "Add variable states to export", example = "false", defaultValue = "false")
private boolean addStates = false;

@NotBlank
@Schema(description = "Destination folder (Applishare or S3)", requiredMode = Schema.RequiredMode.REQUIRED)
private String destinationFolder;

@Schema(description = "Batch size", defaultValue = "100")
private Integer batchSize;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package fr.insee.genesis.controller.dto.rawdata;

import com.fasterxml.jackson.annotation.JsonFormat;
import fr.insee.genesis.controller.utils.ExportType;
import fr.insee.genesis.domain.model.context.schedule.DestinationType;
import fr.insee.genesis.domain.model.surveyunit.Mode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ScheduleResponseDto {

private String scheduleUuid;
private String collectionInstrumentId;
private LocalDateTime lastExecution;

private String frequency;
private ExportType exportType;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime scheduleBeginDate;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime scheduleEndDate;

private Mode mode;
private DestinationType destinationType;
private boolean useAsymmetricEncryption;
private boolean useSymmetricEncryption;
private String encryptionVaultPath;
private boolean useSignature;
private boolean addStates;
private String destinationFolder;
private Integer batchSize;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package fr.insee.genesis.controller.rest;

import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class ControllerExceptionHandler {

@ExceptionHandler(DuplicateKeyException.class)
public ResponseEntity<String> handleDuplicate(DuplicateKeyException ex) {
return ResponseEntity
.status(HttpStatus.CONFLICT)
.body(ex.getMessage());
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationExceptions(MethodArgumentNotValidException ex) {

Check failure on line 24 in src/main/java/fr/insee/genesis/controller/rest/ControllerExceptionHandler.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove usage of generic wildcard type.

See more on https://sonarcloud.io/project/issues?id=InseeFr_genesis-api&issues=AZ1EDzrtFMaHh4D8vdow&open=AZ1EDzrtFMaHh4D8vdow&pullRequest=427

Map<String, String> errors = new HashMap<>();

ex.getBindingResult().getFieldErrors().forEach(error -> {
errors.put(error.getField(), error.getDefaultMessage());
});

ex.getBindingResult().getGlobalErrors().forEach(error -> {
errors.put(error.getObjectName(), error.getDefaultMessage());
});

Map<String, Object> body = new HashMap<>();
body.put("status", 400);
body.put("message", "Validation failed");
body.put("errors", errors);

return ResponseEntity.badRequest().body(body);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package fr.insee.genesis.controller.rest;

import fr.insee.genesis.Constants;
import fr.insee.genesis.controller.dto.KraftwerkExecutionScheduleInput;
import fr.insee.genesis.controller.dto.ScheduleDto;
import fr.insee.genesis.controller.dto.ScheduleRequestDto;
import fr.insee.genesis.controller.dto.rawdata.ScheduleResponseDto;
import fr.insee.genesis.domain.model.context.schedule.ServiceToCall;
import fr.insee.genesis.domain.model.context.schedule.TrustParameters;
import fr.insee.genesis.domain.ports.api.DataProcessingContextApiPort;
import fr.insee.genesis.exceptions.GenesisException;
import fr.insee.genesis.infrastructure.utils.FileUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatusCode;
Expand Down Expand Up @@ -97,6 +101,46 @@ public ResponseEntity<Object> getReviewIndicator(
}
}

@Operation(summary = "Create a Kraftwerk execution schedule V2")
@PostMapping(path = "/contexts/schedules/v2")
@PreAuthorize("hasRole('USER_KRAFTWERK')")
public ResponseEntity<Object> createScheduleV2(
@Valid @RequestBody ScheduleRequestDto request
) {
try {
TrustParameters trustParameters = null;
if (request.isUseAsymmetricEncryption()) {
trustParameters = new TrustParameters(
fileUtils.getKraftwerkOutFolder(request.getCollectionInstrumentId()),
"",
request.getEncryptionVaultPath(),
request.isUseSignature()
);
}

KraftwerkExecutionScheduleInput scheduleInput = KraftwerkExecutionScheduleInput.builder()
.collectionInstrumentId(request.getCollectionInstrumentId())
.exportType(request.getExportType())
.frequency(request.getFrequency())
.startDate(request.getScheduleBeginDate())
.endDate(request.getScheduleEndDate())
.mode(request.getMode())
.destinationType(request.getDestinationType())
.addStates(request.isAddStates())
.destinationFolder(request.getDestinationFolder())
.trustParameters(trustParameters)
.batchSize(request.getBatchSize())
.build();

String scheduleUuid = dataProcessingContextApiPort.createKraftwerkExecutionSchedule(scheduleInput);

return ResponseEntity.ok(scheduleUuid);

} catch (GenesisException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus()));
}
}

@Deprecated(forRemoval = true)
@Operation(summary = "Schedule a Kraftwerk execution")
@PutMapping(path = "/context/schedules")
Expand Down Expand Up @@ -189,6 +233,51 @@ public ResponseEntity<Object> saveScheduleWithCollectionInstrumentId(
return ResponseEntity.ok().build();
}

@Operation(summary = "Update a Kraftwerk execution schedule V2")
@PutMapping(path = "/contexts/{collectionInstrumentId}/schedules/v2/{scheduleUuid}")
@PreAuthorize("hasRole('USER_KRAFTWERK')")
public ResponseEntity<Object> updateScheduleV2(
@PathVariable("collectionInstrumentId") String collectionInstrumentId,
@PathVariable("scheduleUuid") String scheduleUuid,
@Valid @RequestBody ScheduleRequestDto request
) {
try {
TrustParameters trustParameters = null;
if (request.isUseAsymmetricEncryption()) {
trustParameters = new TrustParameters(
fileUtils.getKraftwerkOutFolder(collectionInstrumentId),
"",
request.getEncryptionVaultPath(),
request.isUseSignature()
);
}

KraftwerkExecutionScheduleInput scheduleInput = KraftwerkExecutionScheduleInput.builder()
.collectionInstrumentId(collectionInstrumentId)
.scheduleUuid(scheduleUuid)
.exportType(request.getExportType())
.frequency(request.getFrequency())
.startDate(request.getScheduleBeginDate())
.endDate(request.getScheduleEndDate())
.mode(request.getMode())
.destinationType(request.getDestinationType())
.addStates(request.isAddStates())
.destinationFolder(request.getDestinationFolder())
.useAsymmetricEncryption(request.isUseAsymmetricEncryption())
.useSymmetricEncryption(request.isUseSymmetricEncryption())
.trustParameters(trustParameters)
.batchSize(request.getBatchSize())
.build();

dataProcessingContextApiPort.updateKraftwerkExecutionSchedule(scheduleInput);

} catch (GenesisException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus()));
}

return ResponseEntity.ok().build();
}

@Deprecated(forRemoval = true)
@Operation(summary = "Fetch all schedules")
@GetMapping(path = "/context/schedules")
Expand All @@ -215,6 +304,29 @@ public ResponseEntity<Object> getAllSchedulesV2() {
return ResponseEntity.ok(surveyScheduleDocumentModels);
}

@Operation(summary = "Fetch all schedules V2")
@GetMapping(path = "/contexts/schedules/v2")
@PreAuthorize("hasAnyRole('SCHEDULER','READER')")
public ResponseEntity<Object> getAllSchedulesV3() {
log.debug("Got GET all schedules V2 request");

List<ScheduleResponseDto> schedules = dataProcessingContextApiPort.getAllSchedulesV2();

log.info("Returning {} V2 schedule documents...", schedules.size());
return ResponseEntity.ok(schedules);
}

@Operation(summary = "Fetch V2 schedules by collection instrument id")
@GetMapping(path = "/contexts/{collectionInstrumentId}/schedules/v2")
@PreAuthorize("hasAnyRole('SCHEDULER','READER')")
public ResponseEntity<Object> getSchedulesV2ByCollectionInstrumentId(
@PathVariable("collectionInstrumentId") String collectionInstrumentId
) {
List<ScheduleResponseDto> schedules =
dataProcessingContextApiPort.getSchedulesV2ByCollectionInstrumentId(collectionInstrumentId);

return ResponseEntity.ok(schedules);
}

@Deprecated(forRemoval = true)
@Operation(summary = "Set last execution date of a partition with new date or nothing")
Expand Down Expand Up @@ -280,6 +392,37 @@ public ResponseEntity<Object> deleteSchedulesByCollectionInstrumentId(
return ResponseEntity.ok().build();
}

@Operation(summary = "Delete all V2 Kraftwerk execution schedules of a collection instrument id")
@DeleteMapping(path = "/contexts/{collectionInstrumentId}/schedules/v2")
@PreAuthorize("hasRole('USER_KRAFTWERK')")
public ResponseEntity<Object> deleteSchedulesV2ByCollectionInstrumentId(
@PathVariable("collectionInstrumentId") String collectionInstrumentId
){
try {
dataProcessingContextApiPort.deleteSchedulesV2ByCollectionInstrumentId(collectionInstrumentId);
} catch (GenesisException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus()));
}
log.info("All V2 schedules deleted for collection instrument {}", collectionInstrumentId);
return ResponseEntity.ok().build();
}

@Operation(summary = "Delete a V2 Kraftwerk execution schedule")
@DeleteMapping(path = "/contexts/{collectionInstrumentId}/schedules/v2/{scheduleUuid}")
@PreAuthorize("hasRole('USER_KRAFTWERK')")
public ResponseEntity<Object> deleteScheduleV2(
@PathVariable(value = "collectionInstrumentId") String collectionInstrumentId,
@PathVariable(value = "scheduleUuid") String scheduleUuid
){
try {
dataProcessingContextApiPort.deleteScheduleV2(collectionInstrumentId, scheduleUuid);
} catch (GenesisException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus()));
}
log.info("V2 schedule {} deleted for collection instrument {}", scheduleUuid, collectionInstrumentId);
return ResponseEntity.ok().build();
}

@Operation(summary = "Delete expired schedules")
@DeleteMapping(path = "/context/schedules/expired-schedules")
@PreAuthorize("hasRole('SCHEDULER')")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package fr.insee.genesis.controller.utils;

public enum ExportType {
JSON,
CSV_PARQUET
}
Loading
Loading