From a5009d0a1fb7a8003cfb7ee6a8ab12d7404ca643 Mon Sep 17 00:00:00 2001 From: Hajarel-moukh Date: Thu, 26 Feb 2026 09:21:33 +0100 Subject: [PATCH 1/5] create an exceptionHandler class to unify the exception logs --- .../rest/DataProcessingContextController.java | 27 +++--- .../ContextualVariableController.java | 29 ++---- .../rest/responses/ModeController.java | 5 - .../responses/QuestionnaireController.java | 7 +- .../rest/responses/ResponseController.java | 91 +++++++++---------- .../sources/xml/LunaticXmlDataParser.java | 5 +- .../controller/utils/ControllerUtils.java | 8 +- .../context/DataProcessingContextService.java | 21 +++-- .../ContextualVariableJsonService.java | 3 +- ...ContextualExternalVariableJsonService.java | 9 +- ...ContextualPreviousVariableJsonService.java | 11 ++- .../lunaticmodel/LunaticModelService.java | 6 +- .../QuestionnaireMetadataService.java | 8 +- .../rawdata/LunaticJsonRawDataService.java | 3 +- .../service/rawdata/RawResponseService.java | 3 +- .../service/surveyunit/SurveyUnitService.java | 17 ++-- .../genesis/domain/utils/XMLSplitter.java | 3 +- .../genesis/exceptions/GenesisException.java | 19 ++-- .../exceptions/GenesisExceptionHandler.java | 70 ++++++++++++++ .../exceptions/ReviewDisabledException.java | 7 ++ .../SpecificationNotFoundException.java | 13 +++ .../LastJsonExtractionMongoAdapter.java | 3 +- .../infrastructure/utils/FileUtils.java | 27 ++++-- .../LunaticModelDefinitions.java | 3 +- .../ContextualVariableControllerTest.java | 3 +- .../responses/ResponseControllerTest.java | 14 +-- 26 files changed, 257 insertions(+), 158 deletions(-) create mode 100644 src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java create mode 100644 src/main/java/fr/insee/genesis/exceptions/ReviewDisabledException.java create mode 100644 src/main/java/fr/insee/genesis/exceptions/SpecificationNotFoundException.java diff --git a/src/main/java/fr/insee/genesis/controller/rest/DataProcessingContextController.java b/src/main/java/fr/insee/genesis/controller/rest/DataProcessingContextController.java index 803ecc2b..6bd4cd7f 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/DataProcessingContextController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/DataProcessingContextController.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.Parameter; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.support.CronExpression; @@ -46,7 +47,7 @@ public ResponseEntity saveContext( withReview = withReview != null && withReview; //False if null dataProcessingContextApiPort.saveContext(partitionId, withReview); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } return ResponseEntity.ok().build(); } @@ -62,7 +63,7 @@ public ResponseEntity saveContextWithCollectionInstrumentId( withReview = withReview != null && withReview; //False if null dataProcessingContextApiPort.saveContextByCollectionInstrumentId(collectionInstrumentId, withReview); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } return ResponseEntity.ok().build(); } @@ -78,7 +79,7 @@ public ResponseEntity getReviewIndicatorByCollectionInstrumentId( boolean withReview = dataProcessingContextApiPort.getReviewByCollectionInstrumentId(collectionInstrumentId); return ResponseEntity.ok(withReview); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } } @@ -93,7 +94,7 @@ public ResponseEntity getReviewIndicator( boolean withReview = dataProcessingContextApiPort.getReviewByPartitionId(partitionId); return ResponseEntity.ok(withReview); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } } @@ -119,7 +120,7 @@ public ResponseEntity saveSchedule( //Check frequency if(!CronExpression.isValidExpression(frequency)) { log.warn("Returned error for wrong frequency : {}", frequency); - throw new GenesisException(400, "Wrong frequency syntax"); + throw new GenesisException(HttpStatus.BAD_REQUEST, "Wrong frequency syntax"); } TrustParameters trustParameters = null; @@ -138,7 +139,7 @@ public ResponseEntity saveSchedule( scheduleBeginDate, scheduleEndDate, trustParameters ); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } return ResponseEntity.ok().build(); } @@ -165,7 +166,7 @@ public ResponseEntity saveScheduleWithCollectionInstrumentId( //Check frequency if(!CronExpression.isValidExpression(frequency)) { log.warn("Returned error for wrong frequency : {}", frequency); - throw new GenesisException(400, "Wrong frequency syntax"); + throw new GenesisException(HttpStatus.BAD_REQUEST, "Wrong frequency syntax"); } TrustParameters trustParameters = null; @@ -184,7 +185,7 @@ public ResponseEntity saveScheduleWithCollectionInstrumentId( scheduleBeginDate, scheduleEndDate, trustParameters ); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } return ResponseEntity.ok().build(); } @@ -228,7 +229,7 @@ public ResponseEntity setSurveyLastExecution( dataProcessingContextApiPort.updateLastExecutionDate(partitionId, newDate); log.info("{} last execution updated at {} !", partitionId, newDate); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } return ResponseEntity.ok().build(); } @@ -244,7 +245,7 @@ public ResponseEntity setSurveyLastExecutionByCollectionInstrumentId( dataProcessingContextApiPort.updateLastExecutionDateByCollectionInstrumentId(collectionInstrumentId, newDate); log.info("{} last execution updated at {} !", collectionInstrumentId, newDate); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } return ResponseEntity.ok().build(); } @@ -259,7 +260,7 @@ public ResponseEntity deleteSchedules( try { dataProcessingContextApiPort.deleteSchedules(partitionId); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } log.info("Schedule deleted for survey {}", partitionId); return ResponseEntity.ok().build(); @@ -274,7 +275,7 @@ public ResponseEntity deleteSchedulesByCollectionInstrumentId( try { dataProcessingContextApiPort.deleteSchedulesByCollectionInstrumentId(collectionInstrumentId); }catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } log.info("Schedule deleted for survey {}", collectionInstrumentId); return ResponseEntity.ok().build(); @@ -287,7 +288,7 @@ public ResponseEntity deleteExpiredSchedules(){ try{ dataProcessingContextApiPort.deleteExpiredSchedules(fileUtils.getLogFolder()); } catch (GenesisException e){ - return new ResponseEntity<>(e.getMessage(), HttpStatusCode.valueOf(e.getStatus())); + return new ResponseEntity<>(e.getMessage(), e.getStatus()); } log.info("Expired schedules deleted"); return ResponseEntity.ok().build(); diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/ContextualVariableController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/ContextualVariableController.java index f19c4a73..eab7863e 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/ContextualVariableController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/ContextualVariableController.java @@ -52,8 +52,7 @@ public ResponseEntity getContextualVariables( @PreAuthorize("hasAnyRole('USER_PLATINE','SCHEDULER')") public ResponseEntity saveContextualVariables( @RequestParam("questionnaireId") String questionnaireId - ) { - try { + ) throws GenesisException{ FileUtils fileUtils = new FileUtils(config); String contextualFolderPath = fileUtils.getDataFolder(questionnaireId, "WEB", null) + Constants.CONTEXTUAL_FOLDER; @@ -61,9 +60,7 @@ public ResponseEntity saveContextualVariables( int fileCount = contextualVariableApiPort.saveContextualVariableFiles(questionnaireId, fileUtils,contextualFolderPath); return ResponseEntity.ok("%d file(s) processed for questionnaire %s !".formatted(fileCount, questionnaireId)); - } catch (GenesisException ge) { - return ResponseEntity.status(HttpStatusCode.valueOf(ge.getStatus())).body(ge.getMessage()); - } + } @Operation(summary = "Add contextual previous json file") @@ -74,8 +71,8 @@ public ResponseEntity readContextualPreviousJson( @RequestParam("mode") Mode mode, @RequestParam(value = "sourceState", required = false) String sourceState, @RequestParam(value = "jsonFileName") String jsonFileName - ){ - try { + ) throws GenesisException{ + FileUtils fileUtils = new FileUtils(config); fileUtils.ensureContextualFolderExists(questionnaireId, mode); @@ -86,15 +83,11 @@ public ResponseEntity readContextualPreviousJson( jsonFileName ); if (!jsonFileName.toLowerCase().endsWith(".json")) { - throw new GenesisException(400, "File must be a JSON file !"); + throw new GenesisException(HttpStatus.BAD_REQUEST, "File must be a JSON file !"); } contextualPreviousVariableApiPort.readContextualPreviousFile(questionnaireId.toUpperCase(), sourceState, filePath); moveFile(questionnaireId, mode, fileUtils, filePath); return ResponseEntity.ok("Contextual previous variable file %s saved !".formatted(filePath)); - }catch (GenesisException ge){ - return ResponseEntity.status(HttpStatusCode.valueOf(ge.getStatus())).body(ge.getMessage()); - } catch (IOException ioe) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Erreur IO : " + ioe.getMessage()); } } @Operation(summary = "Add contextual external json file") @@ -104,8 +97,7 @@ public ResponseEntity readContextualExternalJson( @RequestParam("questionnaireId") String questionnaireId, @RequestParam("mode") Mode mode, @RequestParam(value = "jsonFileName") String jsonFileName - ){ - try { + ) throws GenesisException{ FileUtils fileUtils = new FileUtils(config); fileUtils.ensureContextualFolderExists(questionnaireId, mode); @@ -116,23 +108,18 @@ public ResponseEntity readContextualExternalJson( jsonFileName ); if (!jsonFileName.toLowerCase().endsWith(".json")) { - throw new GenesisException(400, "File must be a JSON file !"); + throw new GenesisException(HttpStatus.BAD_REQUEST, "File must be a JSON file !"); } contextualExternalVariableApiPort.readContextualExternalFile(questionnaireId, filePath); moveFile(questionnaireId, mode, fileUtils, filePath); return ResponseEntity.ok("Contextual external variable file %s saved !".formatted(filePath)); - }catch (GenesisException ge){ - return ResponseEntity.status(HttpStatusCode.valueOf(ge.getStatus())).body(ge.getMessage()); - } catch (IOException ioe) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Erreur IO : " + ioe.getMessage()); - } } private static void moveFile(String questionnaireId, Mode mode, FileUtils fileUtils, String filePath) throws GenesisException { try { fileUtils.moveFiles(Path.of(filePath), fileUtils.getDoneFolder(questionnaireId, mode.getFolder())); } catch (IOException e) { - throw new GenesisException(500, "Error while moving file to done : %s".formatted(e.toString())); + throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR, "Error while moving file to done"); } } } diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/ModeController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/ModeController.java index b782c571..90ac712c 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/ModeController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/ModeController.java @@ -32,13 +32,8 @@ public ModeController(SurveyUnitApiPort surveyUnitService) { @Operation(summary = "List sources/modes used for a given collection instrument (ex questionnaire)") @GetMapping(path = "/by-questionnaire") public ResponseEntity> getModesByQuestionnaire(@RequestParam("collectionInstrumentId") String collectionInstrumentId) { - try { List modes = surveyUnitService.findModesByCollectionInstrumentId(collectionInstrumentId); return ResponseEntity.ok(modes); - } catch (QuestionnaireNotFoundException e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(Collections.emptyList()); - } } @Operation(summary = "List sources/modes used for a given campaign") diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/QuestionnaireController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/QuestionnaireController.java index 840685c8..2038c19f 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/QuestionnaireController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/QuestionnaireController.java @@ -85,14 +85,9 @@ public ResponseEntity> getQuestionnairesByCampaignV2(@RequestParam(" @Operation(summary = "Get the questionnaireId corresponding to an interrogationId") @GetMapping(path = "/by-interrogation") @ApiResponse(responseCode = "200", description = "Successfully retrieved the questionnaireId") - public ResponseEntity getQuestionnaireByInterrogation(@RequestParam("interrogationId") String interrogationId){ - try { + public ResponseEntity getQuestionnaireByInterrogation(@RequestParam("interrogationId") String interrogationId) throws GenesisException { String questionnaireId = surveyUnitService.findQuestionnaireIdByInterrogationId(interrogationId); return ResponseEntity.ok(questionnaireId); - } catch (GenesisException e) { - return ResponseEntity.status(e.getStatus()).body(e.getMessage()); - } - } diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java index b136f0a4..34a4b741 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java @@ -31,6 +31,7 @@ import fr.insee.genesis.exceptions.GenesisError; import fr.insee.genesis.exceptions.GenesisException; import fr.insee.genesis.exceptions.NoDataException; +import fr.insee.genesis.exceptions.ReviewDisabledException; import fr.insee.genesis.infrastructure.utils.FileUtils; import fr.insee.modelefiliere.RawResponseDto; import io.swagger.v3.oas.annotations.Operation; @@ -127,30 +128,25 @@ public ResponseEntity saveResponsesFromXmlFile(@RequestParam("pathLunati @PreAuthorize("hasRole('ADMIN')") public ResponseEntity saveResponsesFromXmlCampaignFolder(@RequestParam("campaignName") String campaignName, @RequestParam(value = "mode", required = false) Mode modeSpecified - )throws Exception { - List errors = new ArrayList<>(); + )throws GenesisException { boolean isAnyDataSaved = false; log.info("Try to import XML data for campaign: {}", campaignName); List modesList = controllerUtils.getModesList(campaignName, modeSpecified); for (Mode currentMode : modesList) { + log.info("Processing campaign {} with mode {}", campaignName, currentMode); try { processCampaignWithMode(campaignName, currentMode, null); isAnyDataSaved = true; }catch (NoDataException nde){ //Don't stop if NoDataError thrown - log.warn(nde.getMessage()); - }catch (Exception e){ - log.error(CAMPAIGN_ERROR, campaignName, e.toString()); - return ResponseEntity.status(500).body(e.getMessage()); + log.warn("No data for campaign {} mode {} : {}", campaignName, currentMode, nde.getMessage()); } } - if (errors.isEmpty()){ - return ResponseEntity.ok(getSuccessMessage(isAnyDataSaved)); - } - return ResponseEntity.internalServerError().body(errors.getFirst().getMessage()); + return ResponseEntity.ok(getSuccessMessage(isAnyDataSaved)); + } //SAVE ALL @@ -249,7 +245,7 @@ public ResponseEntity findResponsesByInterrogationAndQuestionnaireLatest contextService.getContext(interrogationId); if(dataProcessingContextModel == null || !dataProcessingContextModel.isWithReview()){ - return ResponseEntity.status(403).body(new ApiError("Review is disabled for that partition")); + throw new ReviewDisabledException(); } SurveyUnitDto response = surveyUnitService.findLatestValuesByStateByIdAndByCollectionInstrumentId(interrogationId, questionnaireId); @@ -268,7 +264,7 @@ public ResponseEntity findResponsesByInterrogationAndCollectionInstrumen DataProcessingContextModel dataProcessingContextModel = contextService.getContext(interrogationId); if(dataProcessingContextModel == null || !dataProcessingContextModel.isWithReview()){ - return ResponseEntity.status(403).body(new ApiError("Review is disabled for that partition")); + throw new ReviewDisabledException(); } SurveyUnitDto response = surveyUnitService.findLatestValuesByStateByIdAndByCollectionInstrumentId(interrogationId, collectionInstrumentId); @@ -464,8 +460,7 @@ private SurveyUnitSimplified fusionWithLastUpdated(List respons @PreAuthorize("hasRole('USER_PLATINE')") public ResponseEntity saveEditedVariables( @RequestBody SurveyUnitInputDto surveyUnitInputDto - ) { - try { + ) throws GenesisException { log.debug("Received in save edited : {}", surveyUnitInputDto.toString()); //Code quality : we need to put all that logic out of this controller //Parse metadata @@ -479,7 +474,7 @@ public ResponseEntity saveEditedVariables( fileUtils, errors); if(metadataModel == null){ - throw new GenesisException(404, errors.getLast().getMessage()); + throw new GenesisException(HttpStatus.NOT_FOUND, errors.getLast().getMessage()); } //Check if input edited variables are in metadatas @@ -512,9 +507,6 @@ public ResponseEntity saveEditedVariables( //Save documents surveyUnitService.saveSurveyUnits(surveyUnitModels); return ResponseEntity.ok(SUCCESS_MESSAGE); - } catch (GenesisException ge) { - return ResponseEntity.status(ge.getStatus()).body(ge.getMessage()); - } } @@ -526,26 +518,37 @@ public ResponseEntity saveEditedVariables( * @param mode mode of collected data */ private void processCampaignWithMode(String campaignName, Mode mode, String rootDataFolder) - throws IOException, ParserConfigurationException, SAXException, XMLStreamException, NoDataException, GenesisException { - log.info("Starting data import for mode: {}", mode.getModeName()); - - String dataFolder = rootDataFolder == null ? - fileUtils.getDataFolder(campaignName, mode.getFolder(), null) - : fileUtils.getDataFolder(campaignName, mode.getFolder(), rootDataFolder); - List dataFiles = fileUtils.listFiles(dataFolder); - log.info("Number of files to load in folder {} : {}", dataFolder, dataFiles.size()); - if (dataFiles.isEmpty()) { - throw new NoDataException("No data file found in folder %s".formatted(dataFolder)); - } + throws NoDataException, GenesisException { - //For each XML data file - for (String fileName : dataFiles.stream().filter(s -> s.endsWith(".xml")).toList()) { - processOneXmlFileForCampaign(campaignName, mode, fileName, dataFolder); - } + try { + log.info("Starting data import for mode: {}", mode.getModeName()); + + String dataFolder = rootDataFolder == null ? + fileUtils.getDataFolder(campaignName, mode.getFolder(), null) + : fileUtils.getDataFolder(campaignName, mode.getFolder(), rootDataFolder); + List dataFiles = fileUtils.listFiles(dataFolder); + log.info("Number of files to load in folder {} : {}", dataFolder, dataFiles.size()); + if (dataFiles.isEmpty()) { + throw new NoDataException("No data file found in folder %s".formatted(dataFolder)); + } + + //For each XML data file + for (String fileName : dataFiles.stream().filter(s -> s.endsWith(".xml")).toList()) { + processOneXmlFileForCampaign(campaignName, mode, fileName, dataFolder); + } + + //Create context if not exist + if(contextService.getContextByCollectionInstrumentId(campaignName) == null){ + contextService.saveContext(campaignName, false); + } + } catch (IOException | ParserConfigurationException | SAXException | XMLStreamException e) { - //Create context if not exist - if(contextService.getContextByCollectionInstrumentId(campaignName) == null){ - contextService.saveContext(campaignName, false); + // Wrap technique -> métier + throw new GenesisException( + HttpStatus.INTERNAL_SERVER_ERROR, + "Error while importing campaign %s (mode %s)" + .formatted(campaignName, mode.getModeName()) + ); } } @@ -594,12 +597,8 @@ private ResponseEntity processXmlFileWithMemory(Path filepath, LunaticXmlCampaign campaign; // DOM method LunaticXmlDataParser parser = new LunaticXmlDataParser(); - try { + campaign = parser.parseDataFile(filepath); - } catch (GenesisException e) { - log.error(e.toString()); - return ResponseEntity.status(e.getStatus()).body(e.getMessage()); - } List surveyUnitModels = new ArrayList<>(); VariablesMap variablesMap = null; @@ -671,7 +670,7 @@ private VariablesMap getVariablesMap(Mode modeSpecified, genesisErrors ); if(!genesisErrors.isEmpty()){ - throw new GenesisException(400,genesisErrors.getLast().getMessage()); + throw new GenesisException(HttpStatus.BAD_REQUEST,genesisErrors.getLast().getMessage()); } variablesMap = metadataModel.getVariables(); return variablesMap; @@ -686,11 +685,11 @@ private static VariablesMap getVariablesMapWithPath(String metadataFilePath) thr return ReaderUtils.getMetadataFromDDIAndLunatic(Path.of(metadataFilePath).toFile().toURI().toURL().toString(), metadataInputStream,metadataInputStream).getVariables(); } catch (MetadataParserException e) { - throw new GenesisException(500, e.getMessage()); + throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); } catch (FileNotFoundException fnfe){ - throw new GenesisException(404, fnfe.toString()); + throw new GenesisException(HttpStatus.NOT_FOUND, fnfe.toString()); } catch (MalformedURLException mue){ - throw new GenesisException(400, mue.toString()); + throw new GenesisException(HttpStatus.BAD_REQUEST, mue.toString()); } }else{ //Parse Lunatic @@ -698,7 +697,7 @@ private static VariablesMap getVariablesMapWithPath(String metadataFilePath) thr try { return LunaticReader.getMetadataFromLunatic(new FileInputStream(metadataFilePath)).getVariables(); } catch (FileNotFoundException fnfe){ - throw new GenesisException(404, fnfe.toString()); + throw new GenesisException(HttpStatus.NOT_FOUND, fnfe.toString()); } } } diff --git a/src/main/java/fr/insee/genesis/controller/sources/xml/LunaticXmlDataParser.java b/src/main/java/fr/insee/genesis/controller/sources/xml/LunaticXmlDataParser.java index 1f303467..2c8e3f37 100644 --- a/src/main/java/fr/insee/genesis/controller/sources/xml/LunaticXmlDataParser.java +++ b/src/main/java/fr/insee/genesis/controller/sources/xml/LunaticXmlDataParser.java @@ -3,6 +3,7 @@ import fr.insee.genesis.Constants; import fr.insee.genesis.exceptions.GenesisException; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -41,7 +42,7 @@ private Document readXmlFile(Path filePath) throws IOException, SAXException, Ge DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(file); if (document == null){ - throw new GenesisException(500,"Can't read file {}"); + throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR, "Unable to read XML file: " + filePath.getFileName()); } return document; } @@ -246,7 +247,7 @@ private static void setValues(LunaticXmlCollectedData varData, Node value, List< varData.setPrevious(valueTypes); break; default: - throw new GenesisException(421, String.format("Tag %s not recognized", valueElement.getTagName())); + throw new GenesisException(HttpStatus.DESTINATION_LOCKED, "Tag not recognized: " + valueElement.getTagName()); } } diff --git a/src/main/java/fr/insee/genesis/controller/utils/ControllerUtils.java b/src/main/java/fr/insee/genesis/controller/utils/ControllerUtils.java index 83051a4e..bdea6daf 100644 --- a/src/main/java/fr/insee/genesis/controller/utils/ControllerUtils.java +++ b/src/main/java/fr/insee/genesis/controller/utils/ControllerUtils.java @@ -4,8 +4,10 @@ import java.util.Collections; import java.util.List; +import fr.insee.genesis.exceptions.SpecificationNotFoundException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import fr.insee.genesis.domain.model.surveyunit.Mode; @@ -41,8 +43,8 @@ public List getModesList(String campaign, Mode modeSpecified) throws Genes String specFolder = fileUtils.getSpecFolder(campaign); List modeSpecFolders = fileUtils.listFolders(specFolder); if (modeSpecFolders.isEmpty()) { - throw new GenesisException(404, "No specification folder found " + specFolder); - } + throw new SpecificationNotFoundException(campaign); + } for(String modeSpecFolder : modeSpecFolders){ if(Mode.getEnumFromModeName(modeSpecFolder) == null) { log.warn("There is an invalid mode folder name in spec folder : {}", modeSpecFolder); @@ -51,7 +53,7 @@ public List getModesList(String campaign, Mode modeSpecified) throws Genes modes.add(Mode.getEnumFromModeName(modeSpecFolder)); } if (modes.contains(Mode.F2F) && modes.contains(Mode.TEL)) { - throw new GenesisException(409, "Cannot treat simultaneously TEL and FAF modes"); + throw new GenesisException(HttpStatus.CONFLICT, "Cannot treat simultaneously TEL and FAF modes"); } return modes; } diff --git a/src/main/java/fr/insee/genesis/domain/service/context/DataProcessingContextService.java b/src/main/java/fr/insee/genesis/domain/service/context/DataProcessingContextService.java index 43cfb563..ca92eb30 100644 --- a/src/main/java/fr/insee/genesis/domain/service/context/DataProcessingContextService.java +++ b/src/main/java/fr/insee/genesis/domain/service/context/DataProcessingContextService.java @@ -17,6 +17,7 @@ import fr.insee.genesis.infrastructure.mappers.DataProcessingContextMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.io.IOException; @@ -135,7 +136,7 @@ public void updateLastExecutionDate(String partitionId, LocalDateTime newDate) t dataProcessingContextPersistancePort.findByPartitionId(partitionId) ); if (dataProcessingContextModel == null) { - throw new GenesisException(404, NOT_FOUND_MESSAGE); + throw new GenesisException(HttpStatus.NOT_FOUND, NOT_FOUND_MESSAGE); } dataProcessingContextModel.setLastExecution(newDate); dataProcessingContextPersistancePort.save(DataProcessingContextMapper.INSTANCE.modelToDocument(dataProcessingContextModel)); @@ -145,7 +146,7 @@ public void updateLastExecutionDate(String partitionId, LocalDateTime newDate) t public void updateLastExecutionDateByCollectionInstrumentId(String collectionInstrumentId, LocalDateTime newDate) throws GenesisException { DataProcessingContextModel dataProcessingContextModel = dataProcessingContextPersistancePort.findByCollectionInstrumentId(collectionInstrumentId); if (dataProcessingContextModel == null) { - throw new GenesisException(404, NOT_FOUND_MESSAGE); + throw new GenesisException(HttpStatus.NOT_FOUND, NOT_FOUND_MESSAGE); } dataProcessingContextModel.setLastExecution(newDate); dataProcessingContextPersistancePort.save(DataProcessingContextMapper.INSTANCE.modelToDocument(dataProcessingContextModel)); @@ -158,7 +159,7 @@ public void deleteSchedules(String partitionId) throws GenesisException { dataProcessingContextPersistancePort.findByPartitionId(partitionId) ); if (dataProcessingContextModel == null) { - throw new GenesisException(404, NOT_FOUND_MESSAGE); + throw new GenesisException(HttpStatus.NOT_FOUND, NOT_FOUND_MESSAGE); } dataProcessingContextModel.setKraftwerkExecutionScheduleList(new ArrayList<>()); dataProcessingContextPersistancePort.save(DataProcessingContextMapper.INSTANCE.modelToDocument(dataProcessingContextModel)); @@ -169,7 +170,7 @@ public void deleteSchedulesByCollectionInstrumentId(String collectionInstrumentI DataProcessingContextModel dataProcessingContextModel = dataProcessingContextPersistancePort.findByCollectionInstrumentId(collectionInstrumentId); if (dataProcessingContextModel == null) { - throw new GenesisException(404, NOT_FOUND_MESSAGE); + throw new GenesisException(HttpStatus.NOT_FOUND, NOT_FOUND_MESSAGE); } dataProcessingContextModel.setKraftwerkExecutionScheduleList(new ArrayList<>()); dataProcessingContextPersistancePort.save(DataProcessingContextMapper.INSTANCE.modelToDocument(dataProcessingContextModel)); @@ -219,7 +220,7 @@ public void deleteExpiredSchedules(String logFolder) throws GenesisException { } } catch (IOException e) { String name = context.getCollectionInstrumentId()!=null?context.getCollectionInstrumentId() :context.getPartitionId(); - throw new GenesisException(500,String.format("An error occured trying to delete expired schedules for %s",name)); + throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR,String.format("An error occured trying to delete expired schedules for %s",name)); } } } @@ -233,7 +234,7 @@ public long countSchedules() { public DataProcessingContextModel getContext(String interrogationId) throws GenesisException { List surveyUnitModels = surveyUnitPersistencePort.findByInterrogationId(interrogationId); if(surveyUnitModels.isEmpty()){ - throw new GenesisException(404, "No interrogation in database with id %s".formatted(interrogationId)); + throw new GenesisException(HttpStatus.NOT_FOUND, "No interrogation in database with id %s".formatted(interrogationId)); } Set collectionInstrumentIds = new HashSet<>(); @@ -244,11 +245,11 @@ public DataProcessingContextModel getContext(String interrogationId) throws Gene } if(collectionInstrumentIds.size() > 1){ - throw new GenesisException(500,"Multiple collection instruments for interrogation %s".formatted(interrogationId)); + throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR,"Multiple collection instruments for interrogation %s".formatted(interrogationId)); } if(collectionInstrumentIds.isEmpty()){ - throw new GenesisException(404,"No collection instrument found for interrogation %s".formatted(interrogationId)); + throw new GenesisException(HttpStatus.NOT_FOUND,"No collection instrument found for interrogation %s".formatted(interrogationId)); } return dataProcessingContextPersistancePort.findByCollectionInstrumentId(collectionInstrumentIds.stream().toList().getFirst()); @@ -292,7 +293,7 @@ public boolean getReviewByPartitionId(String partitionId) throws GenesisExceptio DataProcessingContextDocument dataProcessingContextDocument = dataProcessingContextPersistancePort.findByPartitionId(partitionId); if(dataProcessingContextDocument == null){ - throw new GenesisException(404, "Data processing context not found"); + throw new GenesisException(HttpStatus.NOT_FOUND, "Data processing context not found"); } return DataProcessingContextMapper.INSTANCE.documentToModel( dataProcessingContextPersistancePort.findByPartitionId(partitionId) @@ -304,7 +305,7 @@ public boolean getReviewByCollectionInstrumentId(String collectionInstrumentId) DataProcessingContextModel dataProcessingContextModel = dataProcessingContextPersistancePort.findByCollectionInstrumentId(collectionInstrumentId); if(dataProcessingContextModel == null){ - throw new GenesisException(404, "Data processing context not found"); + throw new GenesisException(HttpStatus.NOT_FOUND, "Data processing context not found"); } return dataProcessingContextModel.isWithReview(); } diff --git a/src/main/java/fr/insee/genesis/domain/service/contextualvariable/ContextualVariableJsonService.java b/src/main/java/fr/insee/genesis/domain/service/contextualvariable/ContextualVariableJsonService.java index 7084f101..5b95bea0 100644 --- a/src/main/java/fr/insee/genesis/domain/service/contextualvariable/ContextualVariableJsonService.java +++ b/src/main/java/fr/insee/genesis/domain/service/contextualvariable/ContextualVariableJsonService.java @@ -14,6 +14,7 @@ import fr.insee.genesis.infrastructure.utils.FileUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.io.IOException; @@ -104,7 +105,7 @@ private static void moveFile(String collectionInstrumentId, Mode mode, FileUtils try { fileUtils.moveFiles(Path.of(filePath), fileUtils.getDoneFolder(collectionInstrumentId, mode.getFolder())); } catch (IOException e) { - throw new GenesisException(500, "Error while moving file to done : %s".formatted(e.toString())); + throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR, "Error while moving file to done : %s".formatted(e.toString())); } } diff --git a/src/main/java/fr/insee/genesis/domain/service/contextualvariable/external/ContextualExternalVariableJsonService.java b/src/main/java/fr/insee/genesis/domain/service/contextualvariable/external/ContextualExternalVariableJsonService.java index f2c835a0..2f88d7a5 100644 --- a/src/main/java/fr/insee/genesis/domain/service/contextualvariable/external/ContextualExternalVariableJsonService.java +++ b/src/main/java/fr/insee/genesis/domain/service/contextualvariable/external/ContextualExternalVariableJsonService.java @@ -11,6 +11,7 @@ import fr.insee.genesis.exceptions.GenesisException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.io.FileInputStream; @@ -72,10 +73,10 @@ public boolean readContextualExternalFile(String collectionInstrumentId, String } }catch (JsonParseException jpe){ contextualExternalVariablePersistancePort.restoreBackup(collectionInstrumentId); - throw new GenesisException(400, "JSON Parsing exception : %s".formatted(jpe.toString())); + throw new GenesisException(HttpStatus.BAD_REQUEST, "JSON Parsing exception : %s".formatted(jpe.toString())); }catch (IOException ioe){ contextualExternalVariablePersistancePort.restoreBackup(collectionInstrumentId); - throw new GenesisException(500, ioe.toString()); + throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR, ioe.toString()); } } @@ -113,13 +114,13 @@ private long saveBlock(List toSave, long savedC private static void checkModel(ContextualExternalVariableModel contextualExternalVariableModel, JsonParser jsonParser, Set savedInterrogationIds) throws GenesisException { if(contextualExternalVariableModel.getInterrogationId() == null){ - throw new GenesisException(400, + throw new GenesisException(HttpStatus.BAD_REQUEST, "Missing interrogationId on the object that ends on line %d" .formatted(jsonParser.currentLocation().getLineNr()) ); } if(savedInterrogationIds.contains(contextualExternalVariableModel.getInterrogationId())){ - throw new GenesisException(400, + throw new GenesisException(HttpStatus.BAD_REQUEST, "Double interrogationId : %s".formatted(contextualExternalVariableModel.getInterrogationId())); } } diff --git a/src/main/java/fr/insee/genesis/domain/service/contextualvariable/previous/ContextualPreviousVariableJsonService.java b/src/main/java/fr/insee/genesis/domain/service/contextualvariable/previous/ContextualPreviousVariableJsonService.java index e8b9b98d..901fe058 100644 --- a/src/main/java/fr/insee/genesis/domain/service/contextualvariable/previous/ContextualPreviousVariableJsonService.java +++ b/src/main/java/fr/insee/genesis/domain/service/contextualvariable/previous/ContextualPreviousVariableJsonService.java @@ -11,6 +11,7 @@ import fr.insee.genesis.exceptions.GenesisException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.io.FileInputStream; @@ -76,10 +77,10 @@ public boolean readContextualPreviousFile(String collectionInstrumentId, } }catch (JsonParseException jpe){ contextualPreviousVariablePersistancePort.restoreBackup(collectionInstrumentId); - throw new GenesisException(400, "JSON Parsing exception : %s".formatted(jpe.toString())); + throw new GenesisException(HttpStatus.BAD_REQUEST, "JSON Parsing exception : %s".formatted(jpe.toString())); }catch (IOException ioe){ contextualPreviousVariablePersistancePort.restoreBackup(collectionInstrumentId); - throw new GenesisException(500, ioe.toString()); + throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR, "I/O error while processing contextual previous file"); } } @@ -105,7 +106,7 @@ private void moveCollectionToBackup(String collectionInstrumentId) { private static void checkSourceStateLength(String sourceState) throws GenesisException { if(sourceState != null && sourceState.length() > 15){ - throw new GenesisException(400, "Source state is too long (>15 characters)"); + throw new GenesisException(HttpStatus.BAD_REQUEST, "Source state is too long (>15 characters)"); } } @@ -192,13 +193,13 @@ private List readArray(JsonParser jsonParser) throws IOException { private static void checkModel(ContextualPreviousVariableModel contextualPreviousVariableModel, JsonParser jsonParser, Set savedInterrogationIds) throws GenesisException { if(contextualPreviousVariableModel.getInterrogationId() == null){ - throw new GenesisException(400, + throw new GenesisException(HttpStatus.BAD_REQUEST, "Missing interrogationId on the object that ends on line %d" .formatted(jsonParser.currentLocation().getLineNr()) ); } if(savedInterrogationIds.contains(contextualPreviousVariableModel.getInterrogationId())){ - throw new GenesisException(400, + throw new GenesisException(HttpStatus.BAD_REQUEST, "Double interrogationId : %s".formatted(contextualPreviousVariableModel.getInterrogationId())); } } diff --git a/src/main/java/fr/insee/genesis/domain/service/lunaticmodel/LunaticModelService.java b/src/main/java/fr/insee/genesis/domain/service/lunaticmodel/LunaticModelService.java index f4f46983..d7eee672 100644 --- a/src/main/java/fr/insee/genesis/domain/service/lunaticmodel/LunaticModelService.java +++ b/src/main/java/fr/insee/genesis/domain/service/lunaticmodel/LunaticModelService.java @@ -4,9 +4,11 @@ import fr.insee.genesis.domain.ports.api.LunaticModelApiPort; import fr.insee.genesis.domain.ports.spi.LunaticModelPersistancePort; import fr.insee.genesis.exceptions.GenesisException; +import fr.insee.genesis.exceptions.QuestionnaireNotFoundException; import fr.insee.genesis.infrastructure.mappers.LunaticModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -34,9 +36,9 @@ public void save(String collectionInstrumentId, Map lunaticModel } @Override - public LunaticModelModel get(String collectionInstrumentId) throws GenesisException { + public LunaticModelModel get(String collectionInstrumentId) { if(lunaticModelPersistancePort.find(collectionInstrumentId).isEmpty()){ - throw new GenesisException(404,"Questionnaire not found"); + throw new QuestionnaireNotFoundException(collectionInstrumentId); } return LunaticModelMapper.INSTANCE.documentToModel(lunaticModelPersistancePort.find(collectionInstrumentId).getFirst()); } diff --git a/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java b/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java index 2e254a87..e65ed811 100644 --- a/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java +++ b/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java @@ -13,9 +13,11 @@ import fr.insee.genesis.domain.ports.spi.QuestionnaireMetadataPersistencePort; import fr.insee.genesis.exceptions.GenesisError; import fr.insee.genesis.exceptions.GenesisException; +import fr.insee.genesis.exceptions.QuestionnaireNotFoundException; import fr.insee.genesis.infrastructure.utils.FileUtils; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.io.FileInputStream; @@ -35,11 +37,11 @@ public class QuestionnaireMetadataService implements QuestionnaireMetadataApiPor @Override - public MetadataModel find(String collectionInstrumentId, Mode mode) throws GenesisException { + public MetadataModel find(String collectionInstrumentId, Mode mode) { List questionnaireMetadataModels = questionnaireMetadataPersistencePort.find(collectionInstrumentId, mode); if(questionnaireMetadataModels.isEmpty()){ - throw new GenesisException(404, "Collection instrument metadata not found"); + throw new QuestionnaireNotFoundException(collectionInstrumentId); } return questionnaireMetadataModels.getFirst().metadataModel(); } @@ -107,7 +109,7 @@ private MetadataModel readMetadatas(String campaignName, String modeName, FileUt } if(!errors.isEmpty()){ - throw new GenesisException(404, errors.getLast().getMessage()); + throw new GenesisException(HttpStatus.NOT_FOUND, errors.getLast().getMessage()); } return metadataModel; } diff --git a/src/main/java/fr/insee/genesis/domain/service/rawdata/LunaticJsonRawDataService.java b/src/main/java/fr/insee/genesis/domain/service/rawdata/LunaticJsonRawDataService.java index d9531892..ef70c3c2 100644 --- a/src/main/java/fr/insee/genesis/domain/service/rawdata/LunaticJsonRawDataService.java +++ b/src/main/java/fr/insee/genesis/domain/service/rawdata/LunaticJsonRawDataService.java @@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -226,7 +227,7 @@ private VariablesMap getVariablesMap(String questionnaireId, Mode mode, List findInterrogationIdsAndModesByQuestionnaireId(Strin public List findModesByCollectionInstrumentId(String collectionInstrumentId) { List surveyUnitModels = surveyUnitPersistencePort.findInterrogationIdsByCollectionInstrumentId(collectionInstrumentId); if (surveyUnitModels == null || surveyUnitModels.isEmpty()) { - log.warn("No collectionInstrument found with id: {}", collectionInstrumentId); throw new QuestionnaireNotFoundException(collectionInstrumentId); } List sources = surveyUnitModels.stream().map(SurveyUnitModel::getMode).distinct().toList(); @@ -459,7 +459,7 @@ public List parseEditedVariables( .toList(); if (statesReceived.contains(DataState.COLLECTED)){ - throw new GenesisException(400,"You can not persist in database a new value with the state COLLECTED"); + throw new GenesisException(HttpStatus.BAD_REQUEST,"You can not persist in database a new value with the state COLLECTED"); } List surveyUnitModels = new ArrayList<>(); @@ -507,17 +507,20 @@ public List parseEditedVariables( public String findQuestionnaireIdByInterrogationId(String interrogationId) throws GenesisException { List surveyUnitModels = surveyUnitPersistencePort.findByInterrogationId(interrogationId); if (surveyUnitModels.isEmpty()){ - throw new GenesisException(404,String.format("The interrogationId %s is not in database",interrogationId)); + throw new GenesisException(HttpStatus.NOT_FOUND,String.format("The interrogationId %s is not in database",interrogationId)); } Set questionnaireIds = new HashSet<>(); for(SurveyUnitModel surveyUnitModel : surveyUnitModels){ questionnaireIds.add(surveyUnitModel.getCollectionInstrumentId()); } if(questionnaireIds.size() > 1){ - throw new GenesisException(207,String.format("Multiple questionnaires for %s :%n%s", - interrogationId, - String.join("\n", questionnaireIds) - )); + throw new GenesisException( + HttpStatus.CONFLICT, + "Multiple questionnaires for %s:%n%s".formatted( + interrogationId, + String.join("\n", questionnaireIds) + ) + ); } return questionnaireIds.iterator().next(); //Return first (and supposed only) element of set diff --git a/src/main/java/fr/insee/genesis/domain/utils/XMLSplitter.java b/src/main/java/fr/insee/genesis/domain/utils/XMLSplitter.java index 137dd7e0..f8873133 100644 --- a/src/main/java/fr/insee/genesis/domain/utils/XMLSplitter.java +++ b/src/main/java/fr/insee/genesis/domain/utils/XMLSplitter.java @@ -2,6 +2,7 @@ import fr.insee.genesis.exceptions.GenesisException; import lombok.experimental.UtilityClass; +import org.springframework.http.HttpStatus; import javax.xml.XMLConstants; import javax.xml.stream.XMLEventFactory; @@ -133,7 +134,7 @@ private static List getHeader(String xmlResource, String condition) th } xer.close(); } catch (IOException e) { - throw new GenesisException(500,e.getMessage()); + throw new GenesisException(HttpStatus.INTERNAL_SERVER_ERROR,e.getMessage()); } return List.of(); } diff --git a/src/main/java/fr/insee/genesis/exceptions/GenesisException.java b/src/main/java/fr/insee/genesis/exceptions/GenesisException.java index 30a4a30f..42d5ad52 100644 --- a/src/main/java/fr/insee/genesis/exceptions/GenesisException.java +++ b/src/main/java/fr/insee/genesis/exceptions/GenesisException.java @@ -2,21 +2,20 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import org.springframework.http.HttpStatus; import java.io.Serial; @Getter -@AllArgsConstructor -public class GenesisException extends Exception{ +public class GenesisException extends Exception { - /** - * - */ - @Serial - private static final long serialVersionUID = 3356078796351491095L; + @Serial + private static final long serialVersionUID = 3356078796351491095L; - private final int status; - - private final String message; + private final HttpStatus status; + public GenesisException(HttpStatus status, String message) { + super(message); + this.status = status; + } } diff --git a/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java b/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java new file mode 100644 index 00000000..33db3d47 --- /dev/null +++ b/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java @@ -0,0 +1,70 @@ +package fr.insee.genesis.exceptions; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +@Slf4j +public class GenesisExceptionHandler { + + @ExceptionHandler(GenesisException.class) + public ResponseEntity handleGenesis(GenesisException exception) { + log.error("Genesis error (Type: {}) : {}", + exception.getClass().getSimpleName(), + exception.getMessage()); + + return ResponseEntity + .status(exception.getStatus()) + .body(exception.getMessage()); + } + + @ExceptionHandler(QuestionnaireNotFoundException.class) + public ResponseEntity handleQuestionnaireNotFound(QuestionnaireNotFoundException exception) { + log.error("Questionnaire not found (Type: {}) : {}", + exception.getClass().getSimpleName(), + exception.getMessage()); + + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(exception.getMessage()); + } + + @ExceptionHandler(NoDataException.class) + public ResponseEntity handleNoData(NoDataException exception) { + log.error("No data found (Type: {}) : {}", + exception.getClass().getSimpleName(), + exception.getMessage()); + + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(exception.getMessage()); + } + + @ExceptionHandler(SpecificationNotFoundException.class) + public ResponseEntity handleSpec(SpecificationNotFoundException exception) { + log.error("Specifications not available for collectionInstrumentId: {} (Type: {})", + exception.getCollectionInstrumentId(), + exception.getClass().getSimpleName()); + + return ResponseEntity + .status(HttpStatus.NOT_FOUND) + .body(exception.getMessage()); + } + + @ExceptionHandler(ReviewDisabledException.class) + public ResponseEntity handleReviewDisabled(ReviewDisabledException ex) { + log.error("[{}] {}", ex.getClass().getSimpleName(), ex.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(ex.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAny(Exception ex) { + log.error("Unexpected error (Type: {}) : {}", ex.getClass().getSimpleName(), ex.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Internal server error"); + } +} diff --git a/src/main/java/fr/insee/genesis/exceptions/ReviewDisabledException.java b/src/main/java/fr/insee/genesis/exceptions/ReviewDisabledException.java new file mode 100644 index 00000000..e89cda1d --- /dev/null +++ b/src/main/java/fr/insee/genesis/exceptions/ReviewDisabledException.java @@ -0,0 +1,7 @@ +package fr.insee.genesis.exceptions; + +public class ReviewDisabledException extends RuntimeException { + public ReviewDisabledException() { + super("Review is disabled for that partition"); + } +} diff --git a/src/main/java/fr/insee/genesis/exceptions/SpecificationNotFoundException.java b/src/main/java/fr/insee/genesis/exceptions/SpecificationNotFoundException.java new file mode 100644 index 00000000..56c18cd4 --- /dev/null +++ b/src/main/java/fr/insee/genesis/exceptions/SpecificationNotFoundException.java @@ -0,0 +1,13 @@ +package fr.insee.genesis.exceptions; + +import lombok.Getter; + +@Getter +public class SpecificationNotFoundException extends RuntimeException { + final String collectionInstrumentId; + + public SpecificationNotFoundException(String collectionInstrumentId) { + super("No specification folder found for collectionInstrumentId: " + collectionInstrumentId); + this.collectionInstrumentId = collectionInstrumentId; + } +} diff --git a/src/main/java/fr/insee/genesis/infrastructure/adapter/LastJsonExtractionMongoAdapter.java b/src/main/java/fr/insee/genesis/infrastructure/adapter/LastJsonExtractionMongoAdapter.java index 823fb4cc..a65a0f6e 100644 --- a/src/main/java/fr/insee/genesis/infrastructure/adapter/LastJsonExtractionMongoAdapter.java +++ b/src/main/java/fr/insee/genesis/infrastructure/adapter/LastJsonExtractionMongoAdapter.java @@ -10,6 +10,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import java.util.Optional; @@ -39,7 +40,7 @@ public LastJsonExtractionModel getLastExecutionDate(String collectionInstrumentI return LastJsonExtractionDocumentMapper.INSTANCE.documentToModel(extraction.get()); } else { String message = String.format("No extraction date found for collection instrument %s and mode %s",collectionInstrumentId,mode==null?null:mode.getModeName()); - throw new GenesisException(404,message); + throw new GenesisException(HttpStatus.NOT_FOUND,message); } } diff --git a/src/main/java/fr/insee/genesis/infrastructure/utils/FileUtils.java b/src/main/java/fr/insee/genesis/infrastructure/utils/FileUtils.java index da01ec7a..cf9e9fd1 100644 --- a/src/main/java/fr/insee/genesis/infrastructure/utils/FileUtils.java +++ b/src/main/java/fr/insee/genesis/infrastructure/utils/FileUtils.java @@ -5,8 +5,10 @@ import fr.insee.genesis.configuration.Config; import fr.insee.genesis.domain.model.surveyunit.Mode; import fr.insee.genesis.domain.model.surveyunit.SurveyUnitModel; +import fr.insee.genesis.exceptions.GenesisException; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import java.io.*; @@ -254,13 +256,24 @@ public List listAllSpecsFolders() { .filter(File::isDirectory) .toList(); } - public void ensureContextualFolderExists(String questionnaireId, Mode mode) throws IOException { - String contextualFolderPath = getDataFolder(questionnaireId, mode.getFolder(), null) + Constants.CONTEXTUAL_FOLDER; - if (!isFolderPresent(contextualFolderPath)) { - Files.createDirectories(Path.of(contextualFolderPath)); - log.debug("contextual folder created : {}", contextualFolderPath); - } else { - log.debug("contextual folder already exists : {}", contextualFolderPath); + + public void ensureContextualFolderExists(String questionnaireId, Mode mode) throws GenesisException { + try { + String contextualFolderPath = + getDataFolder(questionnaireId, mode.getFolder(), null) + Constants.CONTEXTUAL_FOLDER; + + if (!isFolderPresent(contextualFolderPath)) { + Files.createDirectories(Path.of(contextualFolderPath)); + log.debug("contextual folder created : {}", contextualFolderPath); + } else { + log.debug("contextual folder already exists : {}", contextualFolderPath); + } + + } catch (IOException e) { + throw new GenesisException( + HttpStatus.INTERNAL_SERVER_ERROR, + "Unable to create contextual folder" + ); } } diff --git a/src/test/java/cucumber/functional_tests/LunaticModelDefinitions.java b/src/test/java/cucumber/functional_tests/LunaticModelDefinitions.java index 16e9c0cd..b9248be6 100644 --- a/src/test/java/cucumber/functional_tests/LunaticModelDefinitions.java +++ b/src/test/java/cucumber/functional_tests/LunaticModelDefinitions.java @@ -12,6 +12,7 @@ import fr.insee.genesis.domain.service.metadata.QuestionnaireMetadataService; import fr.insee.genesis.domain.service.surveyunit.SurveyUnitService; import fr.insee.genesis.domain.utils.JsonUtils; +import fr.insee.genesis.exceptions.GenesisException; import fr.insee.genesis.infrastructure.document.lunaticmodel.LunaticModelDocument; import fr.insee.genesis.infrastructure.utils.FileUtils; import fr.insee.genesis.stubs.ConfigStub; @@ -148,7 +149,7 @@ public void get_lunatic_model(String questionnaireId) throws JsonProcessingExcep } @When("We get questionnaire id for interrogation {string}") - public void get_questionnaire_id(String interrogationId) { + public void get_questionnaire_id(String interrogationId) throws GenesisException { lastResponse = questionnaireController.getQuestionnaireByInterrogation(interrogationId); } diff --git a/src/test/java/fr/insee/genesis/controller/rest/responses/ContextualVariableControllerTest.java b/src/test/java/fr/insee/genesis/controller/rest/responses/ContextualVariableControllerTest.java index 22bf2f9e..bcc69e0a 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/responses/ContextualVariableControllerTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/responses/ContextualVariableControllerTest.java @@ -10,6 +10,7 @@ import fr.insee.genesis.domain.service.contextualvariable.ContextualVariableJsonService; import fr.insee.genesis.domain.service.contextualvariable.external.ContextualExternalVariableJsonService; import fr.insee.genesis.domain.service.contextualvariable.previous.ContextualPreviousVariableJsonService; +import fr.insee.genesis.exceptions.GenesisException; import fr.insee.genesis.infrastructure.document.contextualexternal.ContextualExternalVariableDocument; import fr.insee.genesis.infrastructure.document.contextualprevious.ContextualPreviousVariableDocument; import fr.insee.genesis.stubs.ConfigStub; @@ -400,7 +401,7 @@ void readPreviousJson_sourceState(String sourceState){ testOKCase(sourceState); } - private void testOKCase(String sourceState) throws IOException { + private void testOKCase(String sourceState) throws IOException, GenesisException { //GIVEN Path contextualPath = SOURCE_PATH_PREVIOUS.resolve("contextual"); Files.createDirectories(contextualPath); diff --git a/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java b/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java index a6ccd3cd..072d084d 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java @@ -372,7 +372,7 @@ void getLatestByStatesSurveyDataTest_invalid_collected() throws GenesisException } @Test - void saveEditedTest() { + void saveEditedTest() throws GenesisException { //GIVEN surveyUnitPersistencePortStub.getMongoStub().clear(); String questionnaireId = DEFAULT_COLLECTION_INSTRUMENT_ID; @@ -433,7 +433,7 @@ void saveEditedTest() { } @Test - void saveEditedTest_DocumentEdited() { + void saveEditedTest_DocumentEdited() throws GenesisException { //GIVEN surveyUnitPersistencePortStub.getMongoStub().clear(); String questionnaireId = DEFAULT_COLLECTION_INSTRUMENT_ID; @@ -503,7 +503,7 @@ void saveEditedTest_DocumentEdited() { } @Test - void saveEditedTest_DocumentFormatted() { + void saveEditedTest_DocumentFormatted() throws GenesisException { //GIVEN surveyUnitPersistencePortStub.getMongoStub().clear(); String questionnaireId = DEFAULT_COLLECTION_INSTRUMENT_ID; @@ -574,7 +574,7 @@ void saveEditedTest_DocumentFormatted() { Assertions.assertThat(surveyUnitPersistencePortStub.getMongoStub().getLast().getModifiedBy()).isNull(); } @Test - void saveEditedTest_No_Metadata_Error() { + void saveEditedTest_No_Metadata_Error() throws GenesisException { //GIVEN surveyUnitPersistencePortStub.getMongoStub().clear(); String campaignId = "TEST"; @@ -619,7 +619,7 @@ void saveEditedTest_No_Metadata_Error() { } @Test - void saveTest_With_Collected_State_Error(){ + void saveTest_With_Collected_State_Error() throws GenesisException { //GIVEN surveyUnitPersistencePortStub.getMongoStub().clear(); String varId = "PRENOM_C"; @@ -658,7 +658,7 @@ void saveTest_With_Collected_State_Error(){ } @Test - void saveEditedTest_int() { + void saveEditedTest_int() throws GenesisException { //GIVEN surveyUnitPersistencePortStub.getMongoStub().clear(); String questionnaireId = DEFAULT_COLLECTION_INSTRUMENT_ID; @@ -719,7 +719,7 @@ void saveEditedTest_int() { } @Test - void saveEditedTest_null() { + void saveEditedTest_null() throws GenesisException { //GIVEN surveyUnitPersistencePortStub.getMongoStub().clear(); String questionnaireId = DEFAULT_COLLECTION_INSTRUMENT_ID; From 5104f43a19689545cee26381bced9d38d4b2c149 Mon Sep 17 00:00:00 2001 From: Hajarel-moukh Date: Thu, 26 Feb 2026 17:14:18 +0100 Subject: [PATCH 2/5] continuer les modifs --- .../insee/genesis/controller/rest/UtilsController.java | 2 +- .../controller/rest/responses/ResponseController.java | 5 +++-- .../service/metadata/QuestionnaireMetadataService.java | 6 ++++-- .../genesis/exceptions/GenesisExceptionHandler.java | 10 +++++----- .../insee/genesis/infrastructure/utils/FileUtils.java | 3 ++- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/fr/insee/genesis/controller/rest/UtilsController.java b/src/main/java/fr/insee/genesis/controller/rest/UtilsController.java index 3554f935..c7f78318 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/UtilsController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/UtilsController.java @@ -47,7 +47,7 @@ public ResponseEntity saveResponsesFromXmlFile(@RequestParam("inputFolde XMLSplitter.split(inputFolder, filename, outputFolder, "SurveyUnit", nbSU); return ResponseEntity.ok("File split"); } - +//TODO @Operation(summary = "Record volumetrics of each campaign in a folder") @PutMapping(path = "/volumetrics/save-all-campaigns") @PreAuthorize("hasRole('SCHEDULER')") diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java index 34a4b741..66bf89ed 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java @@ -578,8 +578,8 @@ private void processOneXmlFileForCampaign(String campaignName, fileUtils.moveDataFile(campaignName, mode.getFolder(),filepath); return; } - log.error("Error {} on file {} : {}", response.getStatusCode(), fileName, response.getBody()); - + log.error("Failed to process file {} for campaign {} mode {} (HTTP: {})", + fileName, campaignName, mode.getModeName(), response.getStatusCode()); } private static long getFileSizeInMB(Path filepath) { @@ -676,6 +676,7 @@ private VariablesMap getVariablesMap(Mode modeSpecified, return variablesMap; } + // private static VariablesMap getVariablesMapWithPath(String metadataFilePath) throws GenesisException { if(metadataFilePath.endsWith(".xml")) { //Parse DDI diff --git a/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java b/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java index e65ed811..3636edf7 100644 --- a/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java +++ b/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java @@ -23,6 +23,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.List; @@ -82,7 +83,7 @@ private void saveMetadata(String collectionInstrumentId, Mode mode, MetadataMode * @return VariablesMap or null if parsing fails */ private MetadataModel readMetadatas(String campaignName, String modeName, FileUtils fileUtils, - List errors) throws GenesisException{ + List errors) throws GenesisException{ Path ddiFilePath; Path lunaticFilePath; @@ -91,7 +92,8 @@ private MetadataModel readMetadatas(String campaignName, String modeName, FileUt ddiFilePath = fileUtils.findFile(String.format("%s/%s", fileUtils.getSpecFolder(campaignName), modeName), DDI_FILE_PATTERN); lunaticFilePath = fileUtils.findFile(String.format("%s/%s", fileUtils.getSpecFolder(campaignName), modeName), LUNATIC_FILE_PATTERN); metadataModel = parseMetadata(lunaticFilePath, ddiFilePath); - } catch (RuntimeException e) { + } catch (NoSuchFileException e) { + log.warn("Specification file missing for campaign={}, mode={}", campaignName, modeName); //DDI file not found and already log - Go to next step } catch (IOException e) { log.warn("No DDI File found for {}, {} mode. Will try to use Lunatic...", campaignName, modeName); diff --git a/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java b/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java index 33db3d47..19a1aafb 100644 --- a/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java +++ b/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java @@ -55,15 +55,15 @@ public ResponseEntity handleSpec(SpecificationNotFoundException exceptio } @ExceptionHandler(ReviewDisabledException.class) - public ResponseEntity handleReviewDisabled(ReviewDisabledException ex) { - log.error("[{}] {}", ex.getClass().getSimpleName(), ex.getMessage()); + public ResponseEntity handleReviewDisabled(ReviewDisabledException exception) { + log.error("[{}] {}", exception.getClass().getSimpleName(), exception.getMessage()); return ResponseEntity.status(HttpStatus.FORBIDDEN) - .body(ex.getMessage()); + .body(exception.getMessage()); } @ExceptionHandler(Exception.class) - public ResponseEntity handleAny(Exception ex) { - log.error("Unexpected error (Type: {}) : {}", ex.getClass().getSimpleName(), ex.getMessage()); + public ResponseEntity handleAny(Exception exception) { + log.error("Unexpected error (Type: {}) : {}", exception.getClass().getSimpleName(), exception.getMessage(), exception); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Internal server error"); } diff --git a/src/main/java/fr/insee/genesis/infrastructure/utils/FileUtils.java b/src/main/java/fr/insee/genesis/infrastructure/utils/FileUtils.java index cf9e9fd1..d5d6df19 100644 --- a/src/main/java/fr/insee/genesis/infrastructure/utils/FileUtils.java +++ b/src/main/java/fr/insee/genesis/infrastructure/utils/FileUtils.java @@ -13,6 +13,7 @@ import java.io.*; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; @@ -130,7 +131,7 @@ public List listFolders(String dir) { public Path findFile(String directory, String regex) throws IOException { try (Stream files = Files.find(Path.of(directory), 1, (path, basicFileAttributes) -> path.toFile().getName().toLowerCase().matches(regex))) { return files.findFirst() - .orElseThrow(() -> new RuntimeException("No file (%s) found in ".formatted(regex) + directory)); + .orElseThrow(() -> new NoSuchFileException("No file (%s) found in %s".formatted(regex, directory))); } } From 501d49030aa7a8754e2d00d81c53c48eeb25ce3f Mon Sep 17 00:00:00 2001 From: Hajarel-moukh Date: Fri, 27 Feb 2026 15:14:52 +0100 Subject: [PATCH 3/5] continuer les modifs --- .../controller/rest/responses/ResponseController.java | 1 - .../service/metadata/QuestionnaireMetadataService.java | 7 +++++-- .../insee/genesis/exceptions/GenesisExceptionHandler.java | 8 ++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java index 66bf89ed..5997b854 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java @@ -543,7 +543,6 @@ private void processCampaignWithMode(String campaignName, Mode mode, String root } } catch (IOException | ParserConfigurationException | SAXException | XMLStreamException e) { - // Wrap technique -> métier throw new GenesisException( HttpStatus.INTERNAL_SERVER_ERROR, "Error while importing campaign %s (mode %s)" diff --git a/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java b/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java index 3636edf7..e1a49958 100644 --- a/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java +++ b/src/main/java/fr/insee/genesis/domain/service/metadata/QuestionnaireMetadataService.java @@ -93,16 +93,19 @@ private MetadataModel readMetadatas(String campaignName, String modeName, FileUt lunaticFilePath = fileUtils.findFile(String.format("%s/%s", fileUtils.getSpecFolder(campaignName), modeName), LUNATIC_FILE_PATTERN); metadataModel = parseMetadata(lunaticFilePath, ddiFilePath); } catch (NoSuchFileException e) { - log.warn("Specification file missing for campaign={}, mode={}", campaignName, modeName); + log.debug("Specification file missing for campaign={}, mode={}", campaignName, modeName); //DDI file not found and already log - Go to next step } catch (IOException e) { log.warn("No DDI File found for {}, {} mode. Will try to use Lunatic...", campaignName, modeName); } if(metadataModel == null ){ - log.warn("DDI not found or error occurred. Trying Lunatic metadata...for {}, {} mode", campaignName, modeName); + log.debug("DDI not found or error occurred. Trying Lunatic metadata...for {}, {} mode", campaignName, modeName); try { lunaticFilePath = fileUtils.findFile(String.format("%s/%s", fileUtils.getSpecFolder(campaignName), modeName), LUNATIC_FILE_PATTERN); return parseMetadata(lunaticFilePath, null); + } catch (NoSuchFileException e) { + log.debug("No Lunatic specification file found for campaign={}, mode={}", campaignName, modeName); + return null; } catch (Exception ex) { log.error("Error reading Lunatic metadata file", ex); errors.add(new GenesisError(ex.toString())); diff --git a/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java b/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java index 19a1aafb..c989f115 100644 --- a/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java +++ b/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java @@ -63,8 +63,12 @@ public ResponseEntity handleReviewDisabled(ReviewDisabledException excep @ExceptionHandler(Exception.class) public ResponseEntity handleAny(Exception exception) { - log.error("Unexpected error (Type: {}) : {}", exception.getClass().getSimpleName(), exception.getMessage(), exception); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + log.error("Unexpected error (Type: {}) : {}", + exception.getClass().getSimpleName(), + exception.getMessage(), + exception); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Internal server error"); } } From 4e81871b79c89b9b7e08dbca2660c28cf2ae8e64 Mon Sep 17 00:00:00 2001 From: Hajarel-moukh Date: Fri, 27 Feb 2026 17:30:25 +0100 Subject: [PATCH 4/5] modify tests --- .../rest/QuestionnaireMetadataController.java | 7 +- .../rest/responses/ResponseController.java | 15 ++- .../api/QuestionnaireMetadataApiPort.java | 3 +- .../exceptions/GenesisExceptionHandler.java | 11 --- .../ContextualVariableControllerTest.java | 94 +++++++++++++++---- .../responses/ResponseControllerTest.java | 22 +++-- .../DataProcessingContextServiceTest.java | 5 +- 7 files changed, 112 insertions(+), 45 deletions(-) diff --git a/src/main/java/fr/insee/genesis/controller/rest/QuestionnaireMetadataController.java b/src/main/java/fr/insee/genesis/controller/rest/QuestionnaireMetadataController.java index 62352b52..72758658 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/QuestionnaireMetadataController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/QuestionnaireMetadataController.java @@ -3,9 +3,10 @@ import fr.insee.bpm.metadata.model.MetadataModel; import fr.insee.genesis.domain.model.surveyunit.Mode; import fr.insee.genesis.domain.ports.api.QuestionnaireMetadataApiPort; -import fr.insee.genesis.exceptions.GenesisException; +import fr.insee.genesis.exceptions.QuestionnaireNotFoundException; import io.swagger.v3.oas.annotations.Operation; import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; @@ -27,8 +28,8 @@ public ResponseEntity getMetadata( ){ try { return ResponseEntity.ok().body(questionnaireMetadataApiPort.find(questionnaireId, mode)); - } catch (GenesisException e) { - return ResponseEntity.status(e.getStatus()).body(e.getMessage()); + } catch (QuestionnaireNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); } } diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java index 5997b854..5108a534 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/ResponseController.java @@ -142,7 +142,11 @@ public ResponseEntity saveResponsesFromXmlCampaignFolder(@RequestParam(" }catch (NoDataException nde){ //Don't stop if NoDataError thrown log.warn("No data for campaign {} mode {} : {}", campaignName, currentMode, nde.getMessage()); - } + } catch (Exception e) { + log.warn("Error while processing campaign {} mode {} : {}", + campaignName, + currentMode, + e.getMessage()); } } return ResponseEntity.ok(getSuccessMessage(isAnyDataSaved)); @@ -474,8 +478,13 @@ public ResponseEntity saveEditedVariables( fileUtils, errors); if(metadataModel == null){ - throw new GenesisException(HttpStatus.NOT_FOUND, errors.getLast().getMessage()); - } + String msg = errors.isEmpty() + ? "Empty metadataModel for questionnaireId=%s, mode=%s" + .formatted(surveyUnitInputDto.getQuestionnaireId(), surveyUnitInputDto.getMode()) + : errors.getLast().getMessage(); + + throw new GenesisException(HttpStatus.NOT_FOUND, msg); + } //Check if input edited variables are in metadatas List absentCollectedVariableNames = diff --git a/src/main/java/fr/insee/genesis/domain/ports/api/QuestionnaireMetadataApiPort.java b/src/main/java/fr/insee/genesis/domain/ports/api/QuestionnaireMetadataApiPort.java index 33593726..4eb18804 100644 --- a/src/main/java/fr/insee/genesis/domain/ports/api/QuestionnaireMetadataApiPort.java +++ b/src/main/java/fr/insee/genesis/domain/ports/api/QuestionnaireMetadataApiPort.java @@ -4,12 +4,13 @@ import fr.insee.genesis.domain.model.surveyunit.Mode; import fr.insee.genesis.exceptions.GenesisError; import fr.insee.genesis.exceptions.GenesisException; +import fr.insee.genesis.exceptions.QuestionnaireNotFoundException; import fr.insee.genesis.infrastructure.utils.FileUtils; import java.util.List; public interface QuestionnaireMetadataApiPort { - MetadataModel find(String collectionInstrumentId, Mode mode) throws GenesisException; + MetadataModel find(String collectionInstrumentId, Mode mode) throws QuestionnaireNotFoundException; MetadataModel loadAndSaveIfNotExists(String campaignName, String collectionInstrumentId, Mode mode, FileUtils fileUtils, List errors) throws GenesisException; void remove(String collectionInstrumentId, Mode mode); diff --git a/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java b/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java index c989f115..3b008826 100644 --- a/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java +++ b/src/main/java/fr/insee/genesis/exceptions/GenesisExceptionHandler.java @@ -60,15 +60,4 @@ public ResponseEntity handleReviewDisabled(ReviewDisabledException excep return ResponseEntity.status(HttpStatus.FORBIDDEN) .body(exception.getMessage()); } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleAny(Exception exception) { - log.error("Unexpected error (Type: {}) : {}", - exception.getClass().getSimpleName(), - exception.getMessage(), - exception); - return ResponseEntity - .status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("Internal server error"); - } } diff --git a/src/test/java/fr/insee/genesis/controller/rest/responses/ContextualVariableControllerTest.java b/src/test/java/fr/insee/genesis/controller/rest/responses/ContextualVariableControllerTest.java index bcc69e0a..6d8b165b 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/responses/ContextualVariableControllerTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/responses/ContextualVariableControllerTest.java @@ -11,6 +11,11 @@ import fr.insee.genesis.domain.service.contextualvariable.external.ContextualExternalVariableJsonService; import fr.insee.genesis.domain.service.contextualvariable.previous.ContextualPreviousVariableJsonService; import fr.insee.genesis.exceptions.GenesisException; +import fr.insee.genesis.exceptions.GenesisExceptionHandler; +import fr.insee.genesis.exceptions.NoDataException; +import fr.insee.genesis.exceptions.QuestionnaireNotFoundException; +import fr.insee.genesis.exceptions.ReviewDisabledException; +import fr.insee.genesis.exceptions.SpecificationNotFoundException; import fr.insee.genesis.infrastructure.document.contextualexternal.ContextualExternalVariableDocument; import fr.insee.genesis.infrastructure.document.contextualprevious.ContextualPreviousVariableDocument; import fr.insee.genesis.stubs.ConfigStub; @@ -26,6 +31,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.FileSystemUtils; @@ -607,10 +613,14 @@ void readPreviousJson_sourceState_too_long(String sourceState){ ); //WHEN + THEN - ResponseEntity response = contextualVariableController.readContextualPreviousJson(QUESTIONNAIRE_ID_PREVIOUS, Mode.WEB, sourceState, - fileName); - Assertions.assertEquals(400,response.getStatusCode().value()); - } + GenesisException ex = Assertions.assertThrows( + GenesisException.class, + () -> contextualVariableController.readContextualPreviousJson( + QUESTIONNAIRE_ID_PREVIOUS, Mode.WEB, sourceState, fileName + ) + ); + + Assertions.assertEquals(400, ex.getStatus().value()); } @Test @SneakyThrows @@ -628,9 +638,17 @@ void readPreviousJson_invalid_syntax(){ ); //WHEN + THEN - ResponseEntity response = contextualVariableController.readContextualPreviousJson(QUESTIONNAIRE_ID_PREVIOUS, Mode.WEB, null, - syntaxErrorFileName); - Assertions.assertEquals(400,response.getStatusCode().value()); + GenesisException ex = Assertions.assertThrows( + GenesisException.class, + () -> contextualVariableController.readContextualPreviousJson( + QUESTIONNAIRE_ID_PREVIOUS, + Mode.WEB, + null, + syntaxErrorFileName + ) + ); + + Assertions.assertEquals(400, ex.getStatus().value()); } @Test @SneakyThrows @@ -648,9 +666,14 @@ void readPreviousJson_not_a_json(){ ); //WHEN + THEN - ResponseEntity response = contextualVariableController.readContextualPreviousJson(QUESTIONNAIRE_ID_PREVIOUS, Mode.WEB, null, - syntaxErrorFileName); - Assertions.assertEquals(400,response.getStatusCode().value()); + GenesisException ex = Assertions.assertThrows( + GenesisException.class, + () -> contextualVariableController.readContextualPreviousJson( + QUESTIONNAIRE_ID_PREVIOUS, Mode.WEB, null, syntaxErrorFileName + ) + ); + + Assertions.assertEquals(400, ex.getStatus().value()); } @@ -671,8 +694,14 @@ void readPreviousJson_no_interrogation_id(String fileName){ ); //WHEN + THEN - ResponseEntity response = contextualVariableController.readContextualPreviousJson(QUESTIONNAIRE_ID_PREVIOUS, Mode.WEB, null, fileName); - Assertions.assertEquals(400,response.getStatusCode().value()); + GenesisException ex = Assertions.assertThrows( + GenesisException.class, + () -> contextualVariableController.readContextualPreviousJson( + QUESTIONNAIRE_ID_PREVIOUS, Mode.WEB, null, fileName + ) + ); + + Assertions.assertEquals(400, ex.getStatus().value()); } @@ -856,6 +885,27 @@ static Stream overrideExternalCases() { ); } + private ResponseEntity toResponse(Exception e) { + GenesisExceptionHandler handler = new GenesisExceptionHandler(); + + if (e instanceof GenesisException ge) { + return handler.handleGenesis(ge); + } + if (e instanceof QuestionnaireNotFoundException qnfe) { + return handler.handleQuestionnaireNotFound(qnfe); + } + if (e instanceof NoDataException nde) { + return handler.handleNoData(nde); + } + if (e instanceof SpecificationNotFoundException snfe) { + return handler.handleSpec(snfe); + } + if (e instanceof ReviewDisabledException rde) { + return handler.handleReviewDisabled(rde); + } + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal server error"); + } + @ParameterizedTest @ValueSource(strings = {"invalid_syntax.json", "not_a_json.xml", @@ -864,8 +914,8 @@ static Stream overrideExternalCases() { "double_interrogationId.json"} ) @SneakyThrows - void readExternalJson_error_400(String fileName){ - //GIVEN + void readExternalJson_error_400(String fileName) { + // GIVEN Path contextualPath = SOURCE_PATH_EXTERNAL.resolve("contextual"); Files.createDirectories(contextualPath); @@ -877,9 +927,19 @@ void readExternalJson_error_400(String fileName){ StandardCopyOption.REPLACE_EXISTING ); - //WHEN + THEN - ResponseEntity response = contextualVariableController.readContextualExternalJson(QUESTIONNAIRE_ID_EXTERNAL, Mode.WEB, fileName); - Assertions.assertEquals(400,response.getStatusCode().value()); + // WHEN + THEN + ResponseEntity response; + try { + ResponseEntity raw = contextualVariableController.readContextualExternalJson( + QUESTIONNAIRE_ID_EXTERNAL, Mode.WEB, fileName + ); + response = ResponseEntity.status(raw.getStatusCode()) + .body(raw.getBody() == null ? null : raw.getBody().toString()); + } catch (Exception e) { + response = toResponse(e); + } + + Assertions.assertEquals(400, response.getStatusCode().value()); } //UTILS diff --git a/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java b/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java index 072d084d..10ff555e 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/responses/ResponseControllerTest.java @@ -32,7 +32,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import java.io.IOException; @@ -45,6 +44,8 @@ import static fr.insee.genesis.TestConstants.DEFAULT_COLLECTION_INSTRUMENT_ID; import static fr.insee.genesis.TestConstants.DEFAULT_INTERROGATION_ID; import static fr.insee.genesis.TestConstants.DEFAULT_SURVEY_UNIT_ID; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; class ResponseControllerTest { //Given @@ -611,11 +612,12 @@ void saveEditedTest_No_Metadata_Error() throws GenesisException { .build(); surveyUnitPersistencePortStub.getMongoStub().add(suModel); - ResponseEntity response = responseControllerStatic.saveEditedVariables( - surveyUnitInputDto); - Assertions.assertThat( - response.getStatusCode() - ).isEqualTo(HttpStatusCode.valueOf(404)); + GenesisException ex = assertThrows( + GenesisException.class, + () -> responseControllerStatic.saveEditedVariables(surveyUnitInputDto) + ); + + assertEquals(404, ex.getStatus().value()); } @Test @@ -654,8 +656,12 @@ void saveTest_With_Collected_State_Error() throws GenesisException { .build(); surveyUnitPersistencePortStub.getMongoStub().add(suModel); - Assertions.assertThat(responseControllerStatic.saveEditedVariables(surveyUnitInputDto).getStatusCode()).isEqualTo(HttpStatusCode.valueOf(400)); - } + GenesisException ex = assertThrows( + GenesisException.class, + () -> responseControllerStatic.saveEditedVariables(surveyUnitInputDto) + ); + + assertEquals(400, ex.getStatus().value()); } @Test void saveEditedTest_int() throws GenesisException { diff --git a/src/test/java/fr/insee/genesis/domain/service/context/DataProcessingContextServiceTest.java b/src/test/java/fr/insee/genesis/domain/service/context/DataProcessingContextServiceTest.java index 1e911815..82a148bc 100644 --- a/src/test/java/fr/insee/genesis/domain/service/context/DataProcessingContextServiceTest.java +++ b/src/test/java/fr/insee/genesis/domain/service/context/DataProcessingContextServiceTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; import java.time.LocalDateTime; import java.util.ArrayList; @@ -212,7 +213,7 @@ void getContext_shouldThrow500IfMultipleCollectionInstruments() { GenesisException ex = assertThrows(GenesisException.class, () -> dataProcessingContextService.getContext("00001")); //To ensure test is portable on Unix/Linux/macOS and windows systems String normalizedMessage = ex.getMessage().replaceAll("\\r?\\n", ""); - Assertions.assertThat(ex.getStatus()).isEqualTo(500); + Assertions.assertThat(ex.getStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); Assertions.assertThat(normalizedMessage).isEqualTo("Multiple collection instruments for interrogation 00001"); } @@ -220,7 +221,7 @@ void getContext_shouldThrow500IfMultipleCollectionInstruments() { void getContext_shouldThrow404IfNoInterrogations() { // When & Then GenesisException ex = assertThrows(GenesisException.class, () -> dataProcessingContextService.getContext("00001")); - Assertions.assertThat(ex.getStatus()).isEqualTo(404); + Assertions.assertThat(ex.getStatus()).isEqualTo(HttpStatus.NOT_FOUND); Assertions.assertThat(ex.getMessage()).isEqualTo("No interrogation in database with id 00001"); } From 6cb89900e0060dc36b8df57fce9c7317e0e6eb19 Mon Sep 17 00:00:00 2001 From: Hajarel-moukh Date: Fri, 27 Feb 2026 17:44:23 +0100 Subject: [PATCH 5/5] modify tests --- .../controller/rest/responses/ContextualVariableController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/fr/insee/genesis/controller/rest/responses/ContextualVariableController.java b/src/main/java/fr/insee/genesis/controller/rest/responses/ContextualVariableController.java index eab7863e..530c302d 100644 --- a/src/main/java/fr/insee/genesis/controller/rest/responses/ContextualVariableController.java +++ b/src/main/java/fr/insee/genesis/controller/rest/responses/ContextualVariableController.java @@ -12,7 +12,6 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller;