Skip to content

Commit

Permalink
Merge pull request #13 from gridsuite/feature/import_directory_per_or…
Browse files Browse the repository at this point in the history
…igin

Make target import directory configurable per case origin
  • Loading branch information
celmhari authored Jun 3, 2024
2 parents 062e1c8 + cc9c0d8 commit ac01135
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

/**
* @author Abdelsalem HEDHILI <abdelsalem.hedhili at rte-france.com>
*/
@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
@SpringBootApplication
@ConfigurationPropertiesScan
public class CaseImportApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

package org.gridsuite.caseimport.server;

import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.Map;

/**
* @author Charaf EL MHARI <charaf.elmhari at rte-france.com>
*/

@Setter
@Getter
@ConfigurationProperties("case-import-server")
public class CaseImportConfig {
private Map<String, String> targetDirectories;
private static final String DEFAULT_IMPORT_DIR_KEY = "default";
private static final String DEFAULT_IMPORT_DIR = "Automatic_cases_import";

@PostConstruct
public void addDefaultTarget() {
targetDirectories.putIfAbsent(DEFAULT_IMPORT_DIR_KEY, DEFAULT_IMPORT_DIR);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.gridsuite.caseimport.server.dto.ImportedCase;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -29,17 +30,23 @@
@ComponentScan(basePackageClasses = CaseImportService.class)
public class CaseImportController {

@Autowired
private CaseImportService caseImportService;
private final CaseImportService caseImportService;

public CaseImportController(CaseImportService caseImportService) {
this.caseImportService = caseImportService;
}

@PostMapping(value = "/cases", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = APPLICATION_JSON_VALUE)
@Operation(summary = "Import a case in the parametrized directory")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The case is imported"),
@ApiResponse(responseCode = "400", description = "Invalid case file"),
@ApiResponse(responseCode = "422", description = "File with wrong extension")})
public ResponseEntity<Void> importCase(@Parameter(description = "case file") @RequestPart("caseFile") MultipartFile caseFile,
@RequestHeader("userId") String userId) {
caseImportService.importCaseInDirectory(caseFile, userId);
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).build();
@ApiResponse(responseCode = "422", description = "File with wrong extension"),
@ApiResponse(responseCode = "201", description = "Case created successfully")})
public ResponseEntity<ImportedCase> importCase(@Parameter(description = "case file") @RequestPart("caseFile") MultipartFile caseFile,
@Parameter(description = "origin of case file") @RequestParam(defaultValue = "default", required = false) String caseFileSource,
@RequestHeader("userId") String userId) {
ImportedCase importedCase = caseImportService.importCaseInDirectory(caseFile, caseFileSource, userId);
return ResponseEntity.status(HttpStatus.CREATED)
.body(importedCase);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class CaseImportException extends RuntimeException {
public enum Type {
IMPORT_CASE_FAILED,
INCORRECT_CASE_FILE,
UNKNOWN_CASE_SOURCE,
REMOTE_ERROR,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,51 @@

import org.gridsuite.caseimport.server.dto.AccessRightsAttributes;
import org.gridsuite.caseimport.server.dto.ElementAttributes;
import org.springframework.beans.factory.annotation.Value;
import org.gridsuite.caseimport.server.dto.ImportedCase;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.UUID;

import static org.gridsuite.caseimport.server.CaseImportException.Type.UNKNOWN_CASE_SOURCE;

/**
* @author Abdelsalem HEDHILI <abdelsalem.hedhili at rte-france.com>
*/
@Service
class CaseImportService {

@Value("${target-directory-name:automatic cases}")
private String directoryName;
private final CaseImportConfig caseImportConfig;

private final DirectoryService directoryService;

private final CaseService caseService;

static final String CASE = "CASE";

public CaseImportService(CaseService caseService, DirectoryService directoryService) {
public CaseImportService(CaseService caseService, DirectoryService directoryService, CaseImportConfig caseImportConfig) {
this.caseService = caseService;
this.directoryService = directoryService;
this.caseImportConfig = caseImportConfig;
}

private String getTargetDirectory(String caseOrigin) {
String targetDirectory = caseImportConfig.getTargetDirectories().get(caseOrigin);
if (targetDirectory == null) {
throw new CaseImportException(UNKNOWN_CASE_SOURCE, "Unknown case origin " + caseOrigin);
}
return targetDirectory;
}

void importCaseInDirectory(MultipartFile caseFile, String userId) {
ImportedCase importCaseInDirectory(MultipartFile caseFile, String caseOrigin, String userId) {
String targetDirectory = getTargetDirectory(caseOrigin);
UUID caseUuid = caseService.importCase(caseFile);
var caseElementAttributes = new ElementAttributes(caseUuid, caseFile.getOriginalFilename(), CASE, new AccessRightsAttributes(false), userId, 0L, null);
directoryService.createElementInDirectory(caseElementAttributes, directoryName, userId);
directoryService.createElementInDirectory(caseElementAttributes, targetDirectory, userId);
ImportedCase importedCase = new ImportedCase();
importedCase.setCaseName(caseElementAttributes.getElementName());
importedCase.setCaseUuid(caseElementAttributes.getElementUuid());
importedCase.setParentDirectory(targetDirectory);
return importedCase;
}
}
16 changes: 6 additions & 10 deletions src/main/java/org/gridsuite/caseimport/server/CaseService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.Objects;
import java.util.UUID;

import static org.gridsuite.caseimport.server.CaseImportException.Type.IMPORT_CASE_FAILED;
import static org.gridsuite.caseimport.server.CaseImportException.Type.INCORRECT_CASE_FILE;


Expand Down Expand Up @@ -51,14 +49,12 @@ UUID importCase(MultipartFile multipartFile) {
UUID caseUuid;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
try {
if (multipartFile != null) {
multipartBodyBuilder.part("file", multipartFile.getBytes())
.filename(Objects.requireNonNull(multipartFile.getOriginalFilename()));
}
} catch (IOException e) {
throw new CaseImportException(IMPORT_CASE_FAILED);

if (multipartFile != null) {
multipartBodyBuilder.part("file", multipartFile.getResource())
.filename(Objects.requireNonNull(multipartFile.getOriginalFilename()));
}

HttpEntity<MultiValueMap<String, HttpEntity<?>>> request = new HttpEntity<>(
multipartBodyBuilder.build(), headers);
try {
Expand All @@ -77,7 +73,7 @@ private static CaseImportException wrapRemoteError(String response, HttpStatusCo
if (!"".equals(response)) {
throw new CaseImportException(CaseImportException.Type.REMOTE_ERROR, response);
} else {
throw new CaseImportException(CaseImportException.Type.REMOTE_ERROR, "{\"message\": " + statusCode + "\"}");
throw new CaseImportException(CaseImportException.Type.REMOTE_ERROR, statusCode.toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.client.HttpStatusCodeException;

import java.util.Map;

/**
* @author Abdelsalem Hedhili <abdelsalem.hedhili at rte-france.com>
*/
Expand All @@ -27,20 +29,18 @@ protected ResponseEntity<Object> handleCaseImportException(CaseImportException e
if (LOGGER.isErrorEnabled()) {
LOGGER.error(exception.getMessage(), exception);
}
switch (exception.getType()) {
case REMOTE_ERROR:
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exception.getMessage());
case INCORRECT_CASE_FILE:
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(exception.getMessage());
default:
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
return switch (exception.getType()) {
case REMOTE_ERROR -> new ResponseEntity<>(Map.of("message", exception.getMessage()), HttpStatus.BAD_REQUEST);
case INCORRECT_CASE_FILE, UNKNOWN_CASE_SOURCE ->
new ResponseEntity<>(Map.of("message", exception.getMessage()), HttpStatus.UNPROCESSABLE_ENTITY);
default -> ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
};
}

@ExceptionHandler(value = {Exception.class})
protected ResponseEntity<Object> handleAllException(Exception exception) {
if (exception instanceof HttpStatusCodeException) {
return ResponseEntity.status(((HttpStatusCodeException) exception).getStatusCode()).body(exception.getMessage());
if (exception instanceof HttpStatusCodeException httpStatusCodeException) {
return ResponseEntity.status(httpStatusCodeException.getStatusCode()).body(exception.getMessage());
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(exception.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.caseimport.server.dto;

import lombok.Getter;
import lombok.Setter;

import java.util.UUID;

/**
* @author Charaf EL MHARI <charaf.elmhari at rte-france.com>
*/

@Setter
@Getter
public class ImportedCase {
private UUID caseUuid;
private String caseName;
private String parentDirectory;
}
6 changes: 5 additions & 1 deletion src/main/resources/config/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ spring:
application:
name: case-import-server

target-directory-name: Automatic_cases_import
# mapping of target directories based on the origin of the case
case-import-server:
target-directories:
origin1: case_import_directory_1
default: Automatic_cases_import

server:
max-http-header-size: 64000
60 changes: 43 additions & 17 deletions src/test/java/org/gridsuite/caseimport/server/CaseImportTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
Expand All @@ -28,6 +27,7 @@

import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
Expand All @@ -39,6 +39,10 @@
public class CaseImportTest {
private static final String TEST_FILE = "testCase.xiidm";
private static final String TEST_FILE_WITH_ERRORS = "testCase_with_errors.xiidm";
private static final String DEFAULT_IMPORT_DIRECTORY = "Automatic_cases_import";
private static final String INVALID_CASE_ORIGIN = "invalid_source";
private static final String CASE_ORIGIN_1 = "origin1";
private static final String CASE_ORIGIN_1_DIRECTORY = "case_import_directory_1";

private static final String TEST_INCORRECT_FILE = "application-default.yml";
private static final String USER1 = "user1";
Expand Down Expand Up @@ -71,32 +75,24 @@ public void setup() throws IOException {
@Test
public void testImportCase() throws Exception {
wireMockUtils.stubImportCase(TEST_FILE);
wireMockUtils.stubAddDirectoryElement("Automatic_cases_import");
wireMockUtils.stubAddDirectoryElement(DEFAULT_IMPORT_DIRECTORY);
try (InputStream is = new FileInputStream(ResourceUtils.getFile("classpath:" + TEST_FILE))) {
MockMultipartFile mockFile = new MockMultipartFile("caseFile", TEST_FILE, "text/xml", is);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("caseFile", mockFile.getBytes())
.filename(TEST_FILE)
.contentType(MediaType.TEXT_XML);

mockMvc.perform(multipart("/v1/cases").file(mockFile)
.header("userId", USER1)
.contentType(MediaType.MULTIPART_FORM_DATA)
)
.andExpect(status().isOk());
.andExpect(status().isCreated());
}
}

@Test
public void testImportCaseWithBadRequestError() throws Exception {
wireMockUtils.stubImportCaseWithErrorInvalid(TEST_FILE_WITH_ERRORS);
wireMockUtils.stubAddDirectoryElement("Automatic_cases_import");
wireMockUtils.stubAddDirectoryElement(DEFAULT_IMPORT_DIRECTORY);
try (InputStream is = new FileInputStream(ResourceUtils.getFile("classpath:" + TEST_FILE))) {
MockMultipartFile mockFile = new MockMultipartFile("caseFile", TEST_FILE_WITH_ERRORS, "text/xml", is);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("caseFile", mockFile.getBytes())
.filename(TEST_FILE_WITH_ERRORS)
.contentType(MediaType.TEXT_XML);

mockMvc.perform(multipart("/v1/cases").file(mockFile)
.header("userId", USER1)
Expand All @@ -109,13 +105,9 @@ public void testImportCaseWithBadRequestError() throws Exception {
@Test
public void testImportCaseWithUnprocessableEntityError() throws Exception {
wireMockUtils.stubImportCaseWithErrorBadExtension(TEST_INCORRECT_FILE);
wireMockUtils.stubAddDirectoryElement("Automatic_cases_import");
wireMockUtils.stubAddDirectoryElement(DEFAULT_IMPORT_DIRECTORY);
try (InputStream is = new FileInputStream(ResourceUtils.getFile("classpath:" + TEST_FILE))) {
MockMultipartFile mockFile = new MockMultipartFile("caseFile", TEST_INCORRECT_FILE, "text/xml", is);
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("caseFile", mockFile.getBytes())
.filename(TEST_INCORRECT_FILE)
.contentType(MediaType.TEXT_XML);

mockMvc.perform(multipart("/v1/cases").file(mockFile)
.header("userId", USER1)
Expand All @@ -124,4 +116,38 @@ public void testImportCaseWithUnprocessableEntityError() throws Exception {
.andExpect(status().isUnprocessableEntity());
}
}

@Test
public void testImportCaseWithInvalidOrigin() throws Exception {
wireMockUtils.stubImportCaseWithErrorInvalid(TEST_FILE);
wireMockUtils.stubAddDirectoryElement(DEFAULT_IMPORT_DIRECTORY);
try (InputStream is = new FileInputStream(ResourceUtils.getFile("classpath:" + TEST_FILE))) {
MockMultipartFile mockFile = new MockMultipartFile("caseFile", TEST_FILE, "text/xml", is);

mockMvc.perform(multipart("/v1/cases").file(mockFile)
.header("userId", USER1)
.contentType(MediaType.MULTIPART_FORM_DATA)
.param("caseFileSource", INVALID_CASE_ORIGIN)
)
.andExpect(status().isUnprocessableEntity());
}
}

@Test
public void testImportCaseWithValidOrigin() throws Exception {
wireMockUtils.stubImportCase(TEST_FILE);
wireMockUtils.stubAddDirectoryElement(CASE_ORIGIN_1_DIRECTORY);
try (InputStream is = new FileInputStream(ResourceUtils.getFile("classpath:" + TEST_FILE))) {
MockMultipartFile mockFile = new MockMultipartFile("caseFile", TEST_FILE, "text/xml", is);

mockMvc.perform(multipart("/v1/cases").file(mockFile)
.header("userId", USER1)
.contentType(MediaType.MULTIPART_FORM_DATA)
.param("caseFileSource", CASE_ORIGIN_1)
)
.andExpectAll(status().isCreated(),
jsonPath("caseName").value(TEST_FILE),
jsonPath("parentDirectory").value(CASE_ORIGIN_1_DIRECTORY));
}
}
}

0 comments on commit ac01135

Please sign in to comment.