Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ src/test/resources/OUT/*
/src/test/resources/DONE/*
/src/test/resources/genesis_deleted_schedules/*
/src/test/resources/IN/WEB/SAMPLETEST/
/src/test/resources/IN/WEB/TEST-EDITED-PREVIOUS/
1 change: 1 addition & 0 deletions src/main/java/fr/insee/genesis/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class Constants {

public static final String MONGODB_SCHEDULE_COLLECTION_NAME = "schedules";
public static final String MONGODB_CONTEXT_COLLECTION_NAME = "dataProcessingContexts";
public static final String MONGODB_EDITED_PREVIOUS_COLLECTION_NAME = "editedPrevious";
public static final String LOOP_NAME_PREFIX = "BOUCLE";
public static final String MONGODB_RESPONSE_COLLECTION_NAME = "responses";
public static final String MONGODB_RESPONSE_RAW_COLLECTION_NAME = "lunaticjsondata";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package fr.insee.genesis.controller.rest.responses;

import fr.insee.genesis.configuration.Config;
import fr.insee.genesis.domain.model.surveyunit.Mode;
import fr.insee.genesis.domain.ports.api.EditedPreviousResponseApiPort;
import fr.insee.genesis.exceptions.GenesisException;
import fr.insee.genesis.infrastructure.utils.FileUtils;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;

@RequestMapping(path = "/edited-previous" )
@Controller
@Slf4j
public class EditedPreviousResponseController {
private final EditedPreviousResponseApiPort editedPreviousResponseApiPort;

private final Config config;

@Autowired
public EditedPreviousResponseController(EditedPreviousResponseApiPort editedPreviousResponseApiPort, Config config) {
this.editedPreviousResponseApiPort = editedPreviousResponseApiPort;
this.config = config;
}

@Operation(summary = "Add edited previous json file")
@PostMapping(path = "/json")
@PreAuthorize("hasRole('USER_KRAFTWERK')")
public ResponseEntity<Object> readJson(
@RequestParam("questionnaireId") String questionnaireId,
@RequestParam("mode") Mode mode,
@RequestParam(value = "sourceState", required = false) String sourceState,
@RequestParam(value = "jsonFileName") String jsonFileName
) throws GenesisException {
FileUtils fileUtils = new FileUtils(config);

String filePath = "%s/%s".formatted(
fileUtils.getDataFolder(questionnaireId, mode.getFolder(), null),
jsonFileName
);
if(!jsonFileName.toLowerCase().endsWith(".json")){
throw new GenesisException(400, "File must be a JSON file !");
}
try(InputStream inputStream = new FileInputStream(filePath)){
editedPreviousResponseApiPort.readEditedPreviousFile(inputStream, questionnaireId, sourceState);
} catch (FileNotFoundException e) {
throw new GenesisException(404, "File %s not found".formatted(filePath));
} catch (IOException e) {
throw new GenesisException(500, e.toString());
}
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()));
}
return ResponseEntity.ok("Edited previous variable file %s saved !".formatted(filePath));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public class ModeController implements CommonApiResponse {

public ModeController(SurveyUnitApiPort surveyUnitService) {
this.surveyUnitService = surveyUnitService;

}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package fr.insee.genesis.domain.model.editedprevious;

import lombok.Builder;
import lombok.Data;

import java.util.Map;

@Builder
@Data
public class EditedPreviousResponseModel {
Comment thread
alexisszmundy marked this conversation as resolved.
String id;
String questionnaireId;
String interrogationId;
Map<String,Object> variables;
String sourceState;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package fr.insee.genesis.domain.ports.api;

import fr.insee.genesis.exceptions.GenesisException;

import java.io.InputStream;

public interface EditedPreviousResponseApiPort {
void readEditedPreviousFile(InputStream inputStream, String questionnaireId, String sourceState) throws GenesisException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package fr.insee.genesis.domain.ports.spi;

import fr.insee.genesis.domain.model.editedprevious.EditedPreviousResponseModel;

import java.util.List;

public interface EditedPreviousResponsePersistancePort {
void backup(String questionnaireId);
void deleteBackup(String questionnaireId);
void restoreBackup(String questionnaireId);
void saveAll(List<EditedPreviousResponseModel> editedPreviousResponseModelList);
void delete(String questionnaireId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package fr.insee.genesis.domain.service.editedprevious;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import fr.insee.genesis.domain.model.editedprevious.EditedPreviousResponseModel;
import fr.insee.genesis.domain.ports.api.EditedPreviousResponseApiPort;
import fr.insee.genesis.domain.ports.spi.EditedPreviousResponsePersistancePort;
import fr.insee.genesis.exceptions.GenesisException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
@Slf4j
public class EditedPreviousResponseJsonService implements EditedPreviousResponseApiPort {
private final EditedPreviousResponsePersistancePort editedPreviousResponsePersistancePort;

private static final int BLOCK_SIZE = 1000;

@Autowired
public EditedPreviousResponseJsonService(EditedPreviousResponsePersistancePort editedPreviousResponsePersistancePort) {
this.editedPreviousResponsePersistancePort = editedPreviousResponsePersistancePort;
}

@Override
public void readEditedPreviousFile(InputStream inputStream,
Comment thread
alexisszmundy marked this conversation as resolved.
String questionnaireId,
String sourceState) throws GenesisException {
if(sourceState != null && sourceState.length() > 15){
throw new GenesisException(400, "Source state is too long (>15 characters)");
}

JsonFactory jsonFactory = new JsonFactory();
editedPreviousResponsePersistancePort.backup(questionnaireId);
editedPreviousResponsePersistancePort.delete(questionnaireId);
try(JsonParser jsonParser = jsonFactory.createParser(inputStream)){
List<EditedPreviousResponseModel> toSave = new ArrayList<>();
boolean isTokenFound = false;
while (!isTokenFound){
jsonParser.nextToken();
if(jsonParser.currentToken().equals(JsonToken.FIELD_NAME) && jsonParser.currentName() != null){
if (jsonParser.currentName().equals("editedPrevious")) {
isTokenFound = true;
}
}
}
long savedCount = 0;
Set<String> savedInterrogationIds = new HashSet<>();
jsonParser.nextToken(); //skip field name
jsonParser.nextToken(); //skip [
while (jsonParser.currentToken() != JsonToken.END_ARRAY) {
EditedPreviousResponseModel editedPreviousResponseModel = readNextEditedPrevious(
jsonParser,
questionnaireId,
sourceState
);

if(editedPreviousResponseModel.getInterrogationId() == null){
throw new GenesisException(400,
"Missing interrogationId on the object that ends on line %d"
.formatted(jsonParser.currentLocation().getLineNr())
);
}
if(savedInterrogationIds.contains(editedPreviousResponseModel.getInterrogationId())){
throw new GenesisException(400,
"Double interrogationId : %s".formatted(editedPreviousResponseModel.getInterrogationId()));
}

toSave.add(editedPreviousResponseModel);
savedInterrogationIds.add(editedPreviousResponseModel.getInterrogationId());

if(toSave.size() >= BLOCK_SIZE){
editedPreviousResponsePersistancePort.saveAll(toSave);
savedCount += toSave.size();
toSave.clear();
}
jsonParser.nextToken(); //skip }
}
editedPreviousResponsePersistancePort.saveAll(toSave);
savedCount += toSave.size();
log.info("Reached end of edited previous file, saved %d interrogations".formatted(savedCount));
editedPreviousResponsePersistancePort.deleteBackup(questionnaireId);
}catch (JsonParseException jpe){
editedPreviousResponsePersistancePort.restoreBackup(questionnaireId);
throw new GenesisException(400, "JSON Parsing exception : %s".formatted(jpe.toString()));
}catch (IOException ioe){
editedPreviousResponsePersistancePort.restoreBackup(questionnaireId);
throw new GenesisException(500, ioe.toString());
}
}

private EditedPreviousResponseModel readNextEditedPrevious(JsonParser jsonParser,
String questionnaireId,
String sourceState
) throws IOException {
if(jsonParser.currentToken() != JsonToken.START_OBJECT){
throw new JsonParseException("Expected { on line %d, got token %s".formatted(jsonParser.currentLocation().getLineNr(), jsonParser.currentToken()));
}
EditedPreviousResponseModel editedPreviousResponseModel = EditedPreviousResponseModel.builder()
.questionnaireId(questionnaireId)
.sourceState(sourceState)
.variables(new HashMap<>())
.build();
jsonParser.nextToken(); // read {
while (!jsonParser.currentToken().equals(JsonToken.END_OBJECT)){
if(jsonParser.currentToken().equals(JsonToken.FIELD_NAME) && jsonParser.currentName().equals("interrogationId")){
jsonParser.nextToken();
editedPreviousResponseModel.setInterrogationId(jsonParser.getText());
jsonParser.nextToken();
continue;
}
jsonParser.nextToken();
editedPreviousResponseModel.getVariables().put(
jsonParser.currentName(),
readValue(jsonParser)
);
jsonParser.nextToken();
}
return editedPreviousResponseModel;
}

private Object readValue(JsonParser jsonParser) throws IOException{
switch (jsonParser.currentToken()){
case VALUE_STRING -> {
return jsonParser.getText();
}
case VALUE_NUMBER_INT -> {
return jsonParser.getIntValue();
}
case VALUE_NUMBER_FLOAT -> {
return jsonParser.getDoubleValue();
}
case VALUE_TRUE, VALUE_FALSE -> {
return jsonParser.getBooleanValue();
}
case VALUE_NULL -> {
return null;
}
case START_ARRAY -> {
return readArray(jsonParser);
}
case null, default -> throw new JsonParseException("Unexpected token %s on line %d".formatted(
jsonParser.currentToken(), jsonParser.currentLocation().getLineNr())
);
}
}

private List<Object> readArray(JsonParser jsonParser) throws IOException {
List<Object> list = new ArrayList<>();
jsonParser.nextToken(); //Read [
while(!jsonParser.currentToken().equals(JsonToken.END_ARRAY)){
list.add(readValue(jsonParser));
jsonParser.nextToken();
}
return list;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package fr.insee.genesis.infrastructure.adapter;

import fr.insee.genesis.domain.model.editedprevious.EditedPreviousResponseModel;
import fr.insee.genesis.domain.ports.spi.EditedPreviousResponsePersistancePort;
import fr.insee.genesis.infrastructure.document.editedprevious.EditedPreviousResponseDocument;
import fr.insee.genesis.infrastructure.mappers.EditedPreviousResponseDocumentMapper;
import fr.insee.genesis.infrastructure.repository.EditedPreviousResponseMongoDBRepository;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.MatchOperation;
import org.springframework.data.mongodb.core.aggregation.MergeOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@Qualifier("editedPreviousResponseMongoAdapter")
public class EditedPreviousResponseMongoAdapter implements EditedPreviousResponsePersistancePort {
private final MongoTemplate mongoTemplate;
private final EditedPreviousResponseMongoDBRepository repository;

public EditedPreviousResponseMongoAdapter(EditedPreviousResponseMongoDBRepository repository, MongoTemplate mongoTemplate) {
this.repository = repository;
this.mongoTemplate = mongoTemplate;
}

@Override
public void backup(String questionnaireId) {
deleteBackup(questionnaireId);
MatchOperation match = Aggregation.match(Criteria.where("questionnaireId").is(questionnaireId));
MergeOperation merge = Aggregation
.merge()
.intoCollection("editedPreviousResponses_%s_backup".formatted(questionnaireId))
.whenMatched(MergeOperation.WhenDocumentsMatch.replaceDocument())
.whenDocumentsDontMatch(MergeOperation.WhenDocumentsDontMatch.insertNewDocument())
.build();

Aggregation aggregation = Aggregation.newAggregation(match, merge);

mongoTemplate.aggregate(aggregation, "editedPreviousResponses", EditedPreviousResponseDocument.class);
}

@Override
public void deleteBackup(String questionnaireId) {
if (mongoTemplate.collectionExists("editedPreviousResponses_%s_backup".formatted(questionnaireId))){
mongoTemplate.dropCollection("editedPreviousResponses_%s_backup".formatted(questionnaireId));
}
}

@Override
public void restoreBackup(String questionnaireId) {
delete(questionnaireId);
MergeOperation merge = Aggregation
.merge()
.intoCollection("editedPreviousResponses")
.whenMatched(MergeOperation.WhenDocumentsMatch.replaceDocument())
.whenDocumentsDontMatch(MergeOperation.WhenDocumentsDontMatch.insertNewDocument())
.build();

Aggregation aggregation = Aggregation.newAggregation(merge);

mongoTemplate.aggregate(aggregation, "editedPreviousResponses_%s_backup".formatted(questionnaireId),
EditedPreviousResponseDocument.class);
}

@Override
public void saveAll(List<EditedPreviousResponseModel> editedPreviousResponseModelList) {
repository.saveAll(EditedPreviousResponseDocumentMapper.INSTANCE.listModelToListDocument(
editedPreviousResponseModelList)
);
}

@Override
public void delete(String questionnaireId) {
repository.deleteByQuestionnaireId(questionnaireId);
}
}
Loading
Loading