From f96fc73b4b0efbe8320fbaf9aca78214d3d6485b Mon Sep 17 00:00:00 2001 From: m-brophy Date: Wed, 15 Sep 2021 15:21:05 +0100 Subject: [PATCH 01/10] TACKLE-312: import dependencies --- .../entities/ApplicationImport.java | 24 ++++ .../entities/ApplicationImportForCsv.java | 4 + .../ApplicationDependencyAPIMapper.java | 46 ++++++++ .../services/ImportService.java | 103 +++++++++++------ .../V20210914.1__alter_application_import.sql | 3 + .../flyway/FlywayMigrationTest.java | 4 +- .../resources/ApplicationImportNullTest.java | 2 + .../services/ImportServiceTest.java | 105 ++++++++++++++++-- src/test/resources/dependency_import.csv | 7 ++ .../resources/long_characters_columns.csv | 6 +- .../resources/only_dependencies_imported.csv | 2 + 11 files changed, 260 insertions(+), 46 deletions(-) create mode 100644 src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java create mode 100644 src/main/resources/db/migration/V20210914.1__alter_application_import.sql create mode 100644 src/test/resources/dependency_import.csv create mode 100644 src/test/resources/only_dependencies_imported.csv diff --git a/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java index 09cfb81..aa12a0a 100644 --- a/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java +++ b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java @@ -150,6 +150,10 @@ public class ApplicationImport extends AbstractEntity { @Size(max = 40) private String tag20; + private String dependency; + + private String dependencyDirection; + private String errorMessage; @Filterable(check = CheckType.EQUAL) @@ -597,6 +601,26 @@ public void setFilename(String filename) { } + + public String getDependency() { + return dependency; + } + + @JsonSetter("Dependency") + public void setDependency(String dependency) { + this.dependency = dependency != null ? dependency.trim() : null; + } + + public String getDependencyDirection() { + return dependencyDirection; + } + + @JsonSetter("Dependency Direction") + public void setDependencyDirection(String dependencyDirection) { + this.dependencyDirection = dependencyDirection != null ? dependencyDirection.trim() : null; + } + + } diff --git a/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java index c8523db..18b67d1 100644 --- a/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java +++ b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java @@ -97,6 +97,10 @@ public abstract class ApplicationImportForCsv { private String tagType20; @JsonProperty("Tag 20") private String tag20; + @JsonProperty("Dependency") + private String dependency; + @JsonProperty("Dependency Direction") + private String dependencyDirection; } diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java new file mode 100644 index 0000000..745e34e --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java @@ -0,0 +1,46 @@ +package io.tackle.applicationinventory.mapper; + +import io.tackle.applicationinventory.BusinessService; +import io.tackle.applicationinventory.Tag; +import io.tackle.applicationinventory.entities.Application; +import io.tackle.applicationinventory.entities.ApplicationImport; +import io.tackle.applicationinventory.entities.ApplicationsDependency; + +import javax.ws.rs.core.Response; +import java.util.Set; + +public class ApplicationDependencyAPIMapper extends ApplicationMapper { + + private static final String FROM_DIRECTION = "FROM"; + private static final String TO_DIRECTION = "TO"; + + public ApplicationDependencyAPIMapper(Set tags, Set businessServices) { + super(tags, businessServices); + } + + @Override + public Response map(ApplicationImport importApp, Long parentId) + { + ApplicationsDependency dependency = new ApplicationsDependency(); + + Application application = Application.find("name", importApp.getApplicationName()).firstResult(); + Application applicationDependency = Application.find("name", importApp.getDependency()).firstResult(); + if (importApp.getDependencyDirection().equals(FROM_DIRECTION)) + { + dependency.from = application; + dependency.to = applicationDependency; + } + else if (importApp.getDependencyDirection().equals(TO_DIRECTION)) + { + dependency.from = applicationDependency; + dependency.to = application; + } + else + { + importApp.setErrorMessage("Invalid Dependency Direction"); + return Response.serverError().build(); + } + dependency.persistAndFlush(); + return Response.ok().build(); + } +} diff --git a/src/main/java/io/tackle/applicationinventory/services/ImportService.java b/src/main/java/io/tackle/applicationinventory/services/ImportService.java index 0f9fd34..0d94f69 100644 --- a/src/main/java/io/tackle/applicationinventory/services/ImportService.java +++ b/src/main/java/io/tackle/applicationinventory/services/ImportService.java @@ -10,6 +10,7 @@ import io.tackle.applicationinventory.Tag; import io.tackle.applicationinventory.entities.ApplicationImport; import io.tackle.applicationinventory.entities.ImportSummary; +import io.tackle.applicationinventory.mapper.ApplicationDependencyAPIMapper; import io.tackle.applicationinventory.mapper.ApplicationInventoryAPIMapper; import io.tackle.applicationinventory.mapper.ApplicationMapper; import org.apache.commons.lang3.StringUtils; @@ -40,6 +41,8 @@ public class ImportService { public static final String COMPLETED_STATUS = "Completed"; public static final String IN_PROGRESS_STATUS = "In Progress"; public static final String FAILED_STATUS = "Failed"; + public static final String APPLICATION_IMPORT_TYPE = "1"; + public static final String DEPENDENCY_IMPORT_TYPE = "2"; @Inject EntityManager entityManager; @@ -69,43 +72,68 @@ public Response importFile(@MultipartForm MultipartImportBody data) { parentRecord.filename = data.getFileName(); parentRecord.importStatus = IN_PROGRESS_STATUS; parentRecord.persistAndFlush(); - Set tags = tagService.getListOfTags(0, 1000); - if (tags == null) - { - String msg = "Unable to retrieve TagTypes from remote resource"; - parentRecord.errorMessage = msg; - throw new Exception(msg); + + List importList = writeFile(data.getFile(), data.getFileName(), parentRecord); + + List applicationTypeImports = importList.stream().filter(anImportRow -> + anImportRow.getRecordType1().equals(APPLICATION_IMPORT_TYPE)).collect(Collectors.toList()); + + if(!applicationTypeImports.isEmpty()) { + + //Only check for tags and business services for Application (Type 1) Imports + Set tags = tagService.getListOfTags(0, 1000); + if (tags == null) { + String msg = "Unable to retrieve TagTypes from remote resource"; + parentRecord.errorMessage = msg; + throw new Exception(msg); + } + Set businessServices = businessServiceService.getListOfBusinessServices(0, 1000); + if (businessServices == null) { + String msg = "Unable to retrieve BusinessServices from remote resource"; + parentRecord.errorMessage = msg; + throw new Exception(msg); + } + + + //we're not allowed duplicate application names within the file + Set discreteAppNames = new HashSet(); + //make a list of all the duplicate app names + List importListMinusDuplicates = applicationTypeImports; + List duplicateAppNames = applicationTypeImports.stream().filter(importApp -> + !discreteAppNames.add(importApp.getApplicationName())).collect(Collectors.toList()); + if (!duplicateAppNames.isEmpty()) { + //find all the imported apps with a duplicate name and set appropriate error message + duplicateAppNames.forEach(app -> { + applicationTypeImports.stream().filter(importApp -> + app.getApplicationName().equals(importApp.getApplicationName())).collect(Collectors.toList()) + .forEach(duplicateApp -> { + importListMinusDuplicates.remove(duplicateApp); + duplicateApp.setErrorMessage("Duplicate Application Name within file: " + duplicateApp.getApplicationName()); + markFailedImportAsInvalid(duplicateApp); + }); + + }); + } + mapImportsToApplication(importListMinusDuplicates, tags, businessServices, parentRecord); } - Set businessServices =businessServiceService.getListOfBusinessServices(0, 1000); - if (businessServices == null) - { - String msg = "Unable to retrieve BusinessServices from remote resource"; - parentRecord.errorMessage = msg; - throw new Exception(msg); + + List dependencyTypeImports = importList.stream().filter(anImportRow -> + anImportRow.getRecordType1().equals(DEPENDENCY_IMPORT_TYPE)).collect(Collectors.toList()); + + if(!dependencyTypeImports.isEmpty()) { + mapImportsToDependency(dependencyTypeImports, parentRecord); } - List importList = writeFile(data.getFile(), data.getFileName(), parentRecord); - //we're not allowed duplicate application names within the file - Set discreteAppNames = new HashSet(); - //make a list of all the duplicate app names - List importListMinusDuplicates = importList; - List duplicateAppNames = importList.stream().filter(importApp -> - !discreteAppNames.add(importApp.getApplicationName())).collect(Collectors.toList()); - if( !duplicateAppNames.isEmpty()) - { - //find all the imported apps with a duplicate name and set appropriate error message - duplicateAppNames.forEach(app -> { - importList.stream().filter(importApp -> - app.getApplicationName().equals(importApp.getApplicationName())).collect(Collectors.toList()) - .forEach(duplicateApp -> { - importListMinusDuplicates.remove(duplicateApp); - duplicateApp.setErrorMessage("Duplicate Application Name within file: " + duplicateApp.getApplicationName()); - markFailedImportAsInvalid(duplicateApp); - }); + List noTypeImports = importList.stream().filter(anImportRow -> + !anImportRow.getRecordType1().equals(APPLICATION_IMPORT_TYPE) + && !anImportRow.getRecordType1().equals(DEPENDENCY_IMPORT_TYPE)).collect(Collectors.toList()); + if(!noTypeImports.isEmpty()) { + noTypeImports.forEach(noTypeImport -> { + noTypeImport.setErrorMessage("Invalid Record Type"); + markFailedImportAsInvalid(noTypeImport); }); } - mapImportsToApplication(importListMinusDuplicates, tags, businessServices, parentRecord); parentRecord.importStatus = COMPLETED_STATUS; parentRecord.flush(); @@ -186,6 +214,19 @@ public void mapImportsToApplication(List importList, Set }); } + public void mapImportsToDependency(List importList,ImportSummary parentRecord) + { + ApplicationMapper mapper = new ApplicationDependencyAPIMapper(null, null); + importList.forEach(importedApplication -> { + Response response = mapper.map(importedApplication, parentRecord.id); + if (response.getStatus() != Response.Status.OK.getStatusCode()) + { + markFailedImportAsInvalid(importedApplication); + } + + }); + } + private void markFailedImportAsInvalid(ApplicationImport importFile) { importFile.setValid(Boolean.FALSE); diff --git a/src/main/resources/db/migration/V20210914.1__alter_application_import.sql b/src/main/resources/db/migration/V20210914.1__alter_application_import.sql new file mode 100644 index 0000000..80c8fc7 --- /dev/null +++ b/src/main/resources/db/migration/V20210914.1__alter_application_import.sql @@ -0,0 +1,3 @@ +alter table if exists application_import + add column dependency varchar (255), + add column dependencyDirection varchar (255); \ No newline at end of file diff --git a/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java b/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java index 55ac7c4..8456bbe 100644 --- a/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java +++ b/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java @@ -20,9 +20,9 @@ public class FlywayMigrationTest { @Test public void testMigration() { // check the number of migrations applied equals the number of files in resources/db/migration folder - assertEquals(17, flyway.info().applied().length); + assertEquals(18, flyway.info().applied().length); // check the current migration version is the one from the last file in resources/db/migration folder - assertEquals("20210809.1", flyway.info().current().getVersion().toString()); + assertEquals("20210914.1", flyway.info().current().getVersion().toString()); // just a basic test to double check the application started // to prove the flyway scripts ran successfully during startup given() diff --git a/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java index 8d405f6..95d9210 100644 --- a/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java +++ b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java @@ -160,6 +160,8 @@ protected void testNullTagTypes() { appImport1.setErrorMessage(null); appImport1.setRecordType1(null); appImport1.setComments(null); + appImport1.setDependency(null); + appImport1.setDependencyDirection(null); Set tags = new HashSet<>() ; Tag.TagType tagType1 = new Tag.TagType(); diff --git a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java index 67e9e8a..08be6b4 100644 --- a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java +++ b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java @@ -10,6 +10,7 @@ import io.restassured.http.ContentType; import io.restassured.response.Response; import io.tackle.applicationinventory.MultipartImportBody; +import io.tackle.applicationinventory.entities.Application; import io.tackle.applicationinventory.entities.ApplicationImport; import io.tackle.applicationinventory.entities.ImportSummary; import io.tackle.commons.testcontainers.KeycloakTestResource; @@ -183,13 +184,13 @@ protected void createDummyRejectedImports() final String multipartPayload = "Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1,Tag 1,Tag Type 2,Tag 2,Tag Type 3,Tag 3" + ",Tag Type 4,Tag 4,Tag Type 5,Tag 5,Tag Type 6,Tag 6,Tag Type 7,Tag 7,Tag Type 8,Tag 8,Tag Type 9,Tag 9" + ",Tag Type 10,Tag 10,Tag Type 11,Tag 11,Tag Type 12,Tag 12,Tag Type 13,Tag 13,Tag Type 14,Tag 14,Tag Type 15,Tag 15,Tag Type 16,Tag 16" + - ",Tag Type 17,Tag 17,Tag Type 18,Tag 18,Tag Type 19,Tag 19,Tag Type 20,Tag 20\n" + + ",Tag Type 17,Tag 17,Tag Type 18,Tag 18,Tag Type 19,Tag 19,Tag Type 20,Tag 20,Dependency,Dependency Direction\n" + "1,,hello,,BS 1,,\n" + "1, ,,,BS 2,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1,tag type 1,tag 1\n" + "1,name 1,and this,,BS 2,,,,,,mystery tag,,\n" + "1,name 4,and this,,BS 1,,,mystery tag type,\n" + "1,name 5,and this,,BS 2,,tag1\n" + - ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"; + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"; given() .config(RestAssured.config().encoderConfig(EncoderConfig.encoderConfig().encodeContentTypeAs("multipart/form-data", ContentType.JSON))) .contentType(MediaType.MULTIPART_FORM_DATA) @@ -530,6 +531,84 @@ protected void testImportServiceNoBSRetrieved() { + @Test + @Order(10) + protected void testImportDependencies() { + + ClassLoader classLoader = getClass().getClassLoader(); + File importFile = new File(classLoader.getResource("dependency_import.csv").getFile()); + + + Response response = given() + .config(RestAssured.config().encoderConfig(EncoderConfig.encoderConfig().encodeContentTypeAs("multipart/form-data", ContentType.JSON))) + .contentType(MediaType.MULTIPART_FORM_DATA) + .accept(MediaType.MULTIPART_FORM_DATA) + .multiPart("file",importFile) + .multiPart("fileName","dependency_import.csv") + .when().post(PATH) + .then() + .log().all() + .statusCode(200).extract().response(); + + assertEquals(200, response.getStatusCode()); + + + given() + .accept("application/hal+json") + .queryParam("isValid", Boolean.TRUE) + .when() + .get("/application-import") + .then() + .statusCode(200) + .log().body() + .body("_embedded.'application-import'.size()", is(5)); + + File importFile2 = new File(classLoader.getResource("only_dependencies_imported.csv").getFile()); + + + given() + .config(RestAssured.config().encoderConfig(EncoderConfig.encoderConfig().encodeContentTypeAs("multipart/form-data", ContentType.JSON))) + .contentType(MediaType.MULTIPART_FORM_DATA) + .accept(MediaType.MULTIPART_FORM_DATA) + .multiPart("file",importFile2) + .multiPart("fileName","only_dependencies_imported.csv") + .when().post(PATH) + .then() + .log().all() + .statusCode(500).extract().response(); + + + + + given() + .accept("application/hal+json") + .queryParam("isValid", Boolean.TRUE) + .when() + .get("/application-import") + .then() + .statusCode(200) + .log().body() + .body("_embedded.'application-import'.size()", is(5)); + + + + /** given() + .accept("application/hal+json") + .queryParam("isValid", Boolean.FALSE) + .when() + .get("/application-import") + .then() + .statusCode(200) + .log().body() + .body("_embedded.'application-import'.size()", is(6)); */ + + + removeTestObjects(Arrays.asList("OrderHub","OrderHubDependency","OrderHubDependency2")); + + } + + + private void removeTestObjects(List appNamesToDelete) { ImportSummary[] summaryList = @@ -566,7 +645,10 @@ private void removeTestObjects(List appNamesToDelete) .then() .statusCode(204)); + System.out.println("Deleting applications:"); + for (String appName : appNamesToDelete) { + System.out.println("appName: " + appName); long firstApplicationId = Long.parseLong(given() .queryParam("name", appName) .accept(ContentType.JSON) @@ -574,18 +656,21 @@ private void removeTestObjects(List appNamesToDelete) .get("/application") .then() .statusCode(200) - .body("size()", Is.is(1)) + .log().body() .extract() .path("[0].id") .toString()); - given() - .accept(ContentType.JSON) - .pathParam("id", firstApplicationId) - .when() - .delete("/application/{id}") - .then() - .statusCode(204); + + + given() + .accept(ContentType.JSON) + .pathParam("id", firstApplicationId) + .when() + .delete("/application/{id}") + .then() + .statusCode(204); + } } diff --git a/src/test/resources/dependency_import.csv b/src/test/resources/dependency_import.csv new file mode 100644 index 0000000..8efcc2b --- /dev/null +++ b/src/test/resources/dependency_import.csv @@ -0,0 +1,7 @@ +Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1,Tag 1,Tag Type 2,Tag 2,Tag Type 3,Tag 3,Tag Type 4,Tag 4,Dependency,Dependency Direction +1,OrderHub,"Create, amend and cancel food orders",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat +1,OrderHubDependency,"Dependent on OrderHub",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat +1,OrderHubDependency2,"OrderHub Dependent on this",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat +2,OrderHub,,,,,,,,,,,,OrderHubDependency,FROM +2,OrderHub,,,,,,,,,,,,OrderHubDependency2,TO +2,OrderHub,,,,,,,,,,,,OrderHubDependency2,NORTHBOUND \ No newline at end of file diff --git a/src/test/resources/long_characters_columns.csv b/src/test/resources/long_characters_columns.csv index 06d288a..17da618 100644 --- a/src/test/resources/long_characters_columns.csv +++ b/src/test/resources/long_characters_columns.csv @@ -1,4 +1,4 @@ Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1,Tag 1,Tag Type 2,Tag 2,Tag Type 3,Tag 3,Tag Type 4,Tag 4,Tag Type 5,Tag 5,Tag Type 6,Tag 6,Tag Type 7,Tag 7,Tag Type 8,Tag 8,Tag Type 9,Tag 9,Tag Type 10,Tag 10,Tag Type 11,Tag 11,Tag Type 12,Tag 12,Tag Type 13,Tag 13,Tag Type 14,Tag 14,Tag Type 15,Tag 15,Tag Type 16,Tag 16,Tag Type 17,Tag 17,Tag Type 18,Tag 18,Tag Type 19,Tag 19,Tag Type 20,Tag 20 -,Import-app-8,Food vendors maintain,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,Import-app-9,Description for imported app,Food vendors maintain their meFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via Orderhubnu and price data which is curated and presented to customers via Orderhub,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -,Very-long-app-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name,Description,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +1,Import-app-8,Food vendors maintain,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +1,Import-app-9,Description for imported app,Food vendors maintain their meFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via OrderhubFood vendors maintain their menu and price data which is curated and presented to customers via Orderhubnu and price data which is curated and presented to customers via Orderhub,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +1,Very-long-app-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name-name,Description,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/src/test/resources/only_dependencies_imported.csv b/src/test/resources/only_dependencies_imported.csv new file mode 100644 index 0000000..f315f0f --- /dev/null +++ b/src/test/resources/only_dependencies_imported.csv @@ -0,0 +1,2 @@ +Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1,Tag 1,Tag Type 2,Tag 2,Tag Type 3,Tag 3,Tag Type 4,Tag 4,Dependency,Dependency Direction +2,OrderHub,,,,,,,,,,,,OrderHubDependency,FROM \ No newline at end of file From 99f901861b704bec94c2673c4835bd6de029b302 Mon Sep 17 00:00:00 2001 From: m-brophy Date: Thu, 16 Sep 2021 14:32:46 +0100 Subject: [PATCH 02/10] TACKLE-312: add validation for dependency import --- .../ApplicationDependencyAPIMapper.java | 48 +++++++++++++++++-- .../services/ImportService.java | 2 +- .../resources/ApplicationImportNullTest.java | 43 +++++++++++++++++ .../services/ImportServiceTest.java | 2 +- .../resources/only_dependencies_imported.csv | 5 +- 5 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java index 745e34e..79a9751 100644 --- a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java +++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java @@ -14,17 +14,48 @@ public class ApplicationDependencyAPIMapper extends ApplicationMapper { private static final String FROM_DIRECTION = "FROM"; private static final String TO_DIRECTION = "TO"; - public ApplicationDependencyAPIMapper(Set tags, Set businessServices) { - super(tags, businessServices); + public ApplicationDependencyAPIMapper() { + super(null, null); } @Override public Response map(ApplicationImport importApp, Long parentId) { + Application application = null; + Application applicationDependency = null; + if (importApp.getApplicationName() != null) + { + application = Application.find("name", importApp.getApplicationName()).firstResult(); + } + + if (application == null) + { + importApp.setErrorMessage("Invalid Application Name"); + return Response.serverError().build(); + } + + if (importApp.getDependency() != null) + { + applicationDependency = Application.find("name", importApp.getDependency()).firstResult(); + } + + if (applicationDependency == null) + { + importApp.setErrorMessage("Invalid Dependency"); + return Response.serverError().build(); + } + + if (applicationDependency == application) + { + importApp.setErrorMessage("Application cannot be a dependency of itself"); + return Response.serverError().build(); + } + + + + ApplicationsDependency dependency = new ApplicationsDependency(); - Application application = Application.find("name", importApp.getApplicationName()).firstResult(); - Application applicationDependency = Application.find("name", importApp.getDependency()).firstResult(); if (importApp.getDependencyDirection().equals(FROM_DIRECTION)) { dependency.from = application; @@ -40,6 +71,15 @@ else if (importApp.getDependencyDirection().equals(TO_DIRECTION)) importApp.setErrorMessage("Invalid Dependency Direction"); return Response.serverError().build(); } + + ApplicationsDependency found = ApplicationsDependency.find("to_id = ?1 and from_id = ?2", dependency.to.id, dependency.from.id).firstResult(); + + if(found != null) + { + importApp.setErrorMessage("Dependency already exists"); + return Response.serverError().build(); + } + dependency.persistAndFlush(); return Response.ok().build(); } diff --git a/src/main/java/io/tackle/applicationinventory/services/ImportService.java b/src/main/java/io/tackle/applicationinventory/services/ImportService.java index 0d94f69..b5299a9 100644 --- a/src/main/java/io/tackle/applicationinventory/services/ImportService.java +++ b/src/main/java/io/tackle/applicationinventory/services/ImportService.java @@ -216,7 +216,7 @@ public void mapImportsToApplication(List importList, Set public void mapImportsToDependency(List importList,ImportSummary parentRecord) { - ApplicationMapper mapper = new ApplicationDependencyAPIMapper(null, null); + ApplicationMapper mapper = new ApplicationDependencyAPIMapper(); importList.forEach(importedApplication -> { Response response = mapper.map(importedApplication, parentRecord.id); if (response.getStatus() != Response.Status.OK.getStatusCode()) diff --git a/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java index 95d9210..b0b05f9 100644 --- a/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java +++ b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java @@ -8,6 +8,7 @@ import io.tackle.applicationinventory.Tag; import io.tackle.applicationinventory.entities.ApplicationImport; import io.tackle.applicationinventory.entities.ImportSummary; +import io.tackle.applicationinventory.mapper.ApplicationDependencyAPIMapper; import io.tackle.applicationinventory.mapper.ApplicationInventoryAPIMapper; import io.tackle.commons.testcontainers.KeycloakTestResource; import io.tackle.commons.testcontainers.PostgreSQLDatabaseTestResource; @@ -232,4 +233,46 @@ protected void testNullTagTypes() { } + + @Test + @Transactional + protected void testNullDependency() { + ImportSummary appImportParent = new ImportSummary(); + appImportParent.persistAndFlush(); + + ApplicationImport appImport1 = new ApplicationImport(); + appImport1.setBusinessService(null); + appImport1.setApplicationName("Online Investments service"); + appImport1.importSummary = appImportParent; + appImport1.setRecordType1("2"); + appImport1.setDependency(null); + appImport1.setDependencyDirection(null); + + ApplicationDependencyAPIMapper apiMapper = new ApplicationDependencyAPIMapper(); + apiMapper.map(appImport1, appImportParent.id); + + assertNull(appImport1.getDependency()); + + ImportSummary appImportParent2 = new ImportSummary(); + appImportParent2.persistAndFlush(); + + ApplicationImport appImport2 = new ApplicationImport(); + appImport2.setBusinessService(null); + appImport2.setApplicationName(null); + appImport2.importSummary = appImportParent; + appImport1.setRecordType1("2"); + appImport1.setDependency(null); + appImport1.setDependencyDirection(null); + + apiMapper.map(appImport2, appImportParent2.id); + + assertNull(appImport1.getDependency()); + + + + + + + } + } diff --git a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java index 08be6b4..8a8a26c 100644 --- a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java +++ b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java @@ -575,7 +575,7 @@ protected void testImportDependencies() { .when().post(PATH) .then() .log().all() - .statusCode(500).extract().response(); + .statusCode(200).extract().response(); diff --git a/src/test/resources/only_dependencies_imported.csv b/src/test/resources/only_dependencies_imported.csv index f315f0f..c0d4a92 100644 --- a/src/test/resources/only_dependencies_imported.csv +++ b/src/test/resources/only_dependencies_imported.csv @@ -1,2 +1,5 @@ Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1,Tag 1,Tag Type 2,Tag 2,Tag Type 3,Tag 3,Tag Type 4,Tag 4,Dependency,Dependency Direction -2,OrderHub,,,,,,,,,,,,OrderHubDependency,FROM \ No newline at end of file +2,OrderHub,,,,,,,,,,,,,FROM +2,,,,,,,,,,,,,OrderHubDependency,FROM +2,OrderHub,,,,,,,,,,,,OrderHubDependency,FROM +2,OrderHub,,,,,,,,,,,,OrderHub,FROM \ No newline at end of file From 6f3e582a762d114e1ad7601df63f35e53a70bc00 Mon Sep 17 00:00:00 2001 From: m-brophy Date: Fri, 17 Sep 2021 10:48:41 +0100 Subject: [PATCH 03/10] TACKLE-312: dependency import change literals 'from' and 'to' to southbound and northbound --- .../mapper/ApplicationDependencyAPIMapper.java | 4 ++-- .../services/ImportServiceTest.java | 12 ------------ src/test/resources/dependency_import.csv | 6 +++--- src/test/resources/only_dependencies_imported.csv | 8 ++++---- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java index 79a9751..7568aab 100644 --- a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java +++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java @@ -11,8 +11,8 @@ public class ApplicationDependencyAPIMapper extends ApplicationMapper { - private static final String FROM_DIRECTION = "FROM"; - private static final String TO_DIRECTION = "TO"; + private static final String FROM_DIRECTION = "SOUTHBOUND"; + private static final String TO_DIRECTION = "NORTHBOUND"; public ApplicationDependencyAPIMapper() { super(null, null); diff --git a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java index 8a8a26c..121e3d5 100644 --- a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java +++ b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java @@ -591,18 +591,6 @@ protected void testImportDependencies() { .body("_embedded.'application-import'.size()", is(5)); - - /** given() - .accept("application/hal+json") - .queryParam("isValid", Boolean.FALSE) - .when() - .get("/application-import") - .then() - .statusCode(200) - .log().body() - .body("_embedded.'application-import'.size()", is(6)); */ - - removeTestObjects(Arrays.asList("OrderHub","OrderHubDependency","OrderHubDependency2")); } diff --git a/src/test/resources/dependency_import.csv b/src/test/resources/dependency_import.csv index 8efcc2b..d66c04c 100644 --- a/src/test/resources/dependency_import.csv +++ b/src/test/resources/dependency_import.csv @@ -2,6 +2,6 @@ Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1, 1,OrderHub,"Create, amend and cancel food orders",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat 1,OrderHubDependency,"Dependent on OrderHub",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat 1,OrderHubDependency2,"OrderHub Dependent on this",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat -2,OrderHub,,,,,,,,,,,,OrderHubDependency,FROM -2,OrderHub,,,,,,,,,,,,OrderHubDependency2,TO -2,OrderHub,,,,,,,,,,,,OrderHubDependency2,NORTHBOUND \ No newline at end of file +2,OrderHub,,,,,,,,,,,,OrderHubDependency,SOUTHBOUND +2,OrderHub,,,,,,,,,,,,OrderHubDependency2,NORTHBOUND +2,OrderHub,,,,,,,,,,,,OrderHubDependency2,TO \ No newline at end of file diff --git a/src/test/resources/only_dependencies_imported.csv b/src/test/resources/only_dependencies_imported.csv index c0d4a92..b1efc5e 100644 --- a/src/test/resources/only_dependencies_imported.csv +++ b/src/test/resources/only_dependencies_imported.csv @@ -1,5 +1,5 @@ Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1,Tag 1,Tag Type 2,Tag 2,Tag Type 3,Tag 3,Tag Type 4,Tag 4,Dependency,Dependency Direction -2,OrderHub,,,,,,,,,,,,,FROM -2,,,,,,,,,,,,,OrderHubDependency,FROM -2,OrderHub,,,,,,,,,,,,OrderHubDependency,FROM -2,OrderHub,,,,,,,,,,,,OrderHub,FROM \ No newline at end of file +2,OrderHub,,,,,,,,,,,,,NORTHBOUND +2,,,,,,,,,,,,,OrderHubDependency,NORTHBOUND +2,OrderHub,,,,,,,,,,,,OrderHubDependency,NORTHBOUND +2,OrderHub,,,,,,,,,,,,OrderHub,NORTHBOUND \ No newline at end of file From 176ea73bdc77e9db5a4d2263a1544f6f2e023786 Mon Sep 17 00:00:00 2001 From: m-brophy Date: Fri, 17 Sep 2021 11:38:24 +0100 Subject: [PATCH 04/10] TACKLE-312: code test coverage --- src/test/resources/dependency_import.csv | 4 +++- src/test/resources/only_dependencies_imported.csv | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/resources/dependency_import.csv b/src/test/resources/dependency_import.csv index d66c04c..38c7fdf 100644 --- a/src/test/resources/dependency_import.csv +++ b/src/test/resources/dependency_import.csv @@ -4,4 +4,6 @@ Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1, 1,OrderHubDependency2,"OrderHub Dependent on this",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat 2,OrderHub,,,,,,,,,,,,OrderHubDependency,SOUTHBOUND 2,OrderHub,,,,,,,,,,,,OrderHubDependency2,NORTHBOUND -2,OrderHub,,,,,,,,,,,,OrderHubDependency2,TO \ No newline at end of file +2,OrderHub,,,,,,,,,,,,OrderHubDependency2,TO +2,OrderHub,,,,,,,,,,,,OrderHub,NORTHBOUND +2,Home Banking BU,,,,,,,,,,,,Online Investments service,SOUTHBOUND \ No newline at end of file diff --git a/src/test/resources/only_dependencies_imported.csv b/src/test/resources/only_dependencies_imported.csv index b1efc5e..8eac955 100644 --- a/src/test/resources/only_dependencies_imported.csv +++ b/src/test/resources/only_dependencies_imported.csv @@ -1,5 +1,4 @@ Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1,Tag 1,Tag Type 2,Tag 2,Tag Type 3,Tag 3,Tag Type 4,Tag 4,Dependency,Dependency Direction 2,OrderHub,,,,,,,,,,,,,NORTHBOUND 2,,,,,,,,,,,,,OrderHubDependency,NORTHBOUND -2,OrderHub,,,,,,,,,,,,OrderHubDependency,NORTHBOUND -2,OrderHub,,,,,,,,,,,,OrderHub,NORTHBOUND \ No newline at end of file +2,OrderHub,,,,,,,,,,,,OrderHubDependency,NORTHBOUND \ No newline at end of file From 61d2b38670af9e3a109f9bf4465706536c7893e1 Mon Sep 17 00:00:00 2001 From: m-brophy Date: Fri, 17 Sep 2021 15:06:48 +0100 Subject: [PATCH 05/10] TACKLE-312: import dependencies make dependency direction case insensitive --- .../mapper/ApplicationDependencyAPIMapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java index 7568aab..ae6438c 100644 --- a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java +++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java @@ -56,12 +56,12 @@ public Response map(ApplicationImport importApp, Long parentId) ApplicationsDependency dependency = new ApplicationsDependency(); - if (importApp.getDependencyDirection().equals(FROM_DIRECTION)) + if (importApp.getDependencyDirection().equalsIgnoreCase(FROM_DIRECTION)) { dependency.from = application; dependency.to = applicationDependency; } - else if (importApp.getDependencyDirection().equals(TO_DIRECTION)) + else if (importApp.getDependencyDirection().equalsIgnoreCase(TO_DIRECTION)) { dependency.from = applicationDependency; dependency.to = application; From 07a9a60d0e41f2fa89737635b21a64da76dd0d50 Mon Sep 17 00:00:00 2001 From: m-brophy Date: Thu, 23 Sep 2021 11:07:29 +0100 Subject: [PATCH 06/10] TACKLE-312: applicationsDependency hibernatevalidator first cut --- .../entities/ApplicationsDependency.java | 2 + .../ApplicationDependencyAPIMapper.java | 39 +++++++++-- .../services/ImportService.java | 11 ++-- .../ApplicationsDependencyNoCycle.java | 25 ++++++++ ...pplicationsDependencyNoCycleValidator.java | 64 +++++++++++++++++++ .../resources/ValidationMessages.properties | 1 + .../V20210914.1__alter_application_import.sql | 2 +- .../resources/ApplicationImportNullTest.java | 10 --- .../services/ImportServiceTest.java | 11 ---- 9 files changed, 133 insertions(+), 32 deletions(-) create mode 100644 src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java create mode 100644 src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycleValidator.java create mode 100644 src/main/resources/ValidationMessages.properties diff --git a/src/main/java/io/tackle/applicationinventory/entities/ApplicationsDependency.java b/src/main/java/io/tackle/applicationinventory/entities/ApplicationsDependency.java index 62fa20f..8c725e2 100644 --- a/src/main/java/io/tackle/applicationinventory/entities/ApplicationsDependency.java +++ b/src/main/java/io/tackle/applicationinventory/entities/ApplicationsDependency.java @@ -2,6 +2,7 @@ import io.quarkus.panache.common.Sort; import io.tackle.applicationinventory.exceptions.ApplicationsInventoryException; +import io.tackle.applicationinventory.validator.ApplicationsDependencyNoCycle; import io.tackle.commons.annotations.Filterable; import io.tackle.commons.entities.AbstractEntity; import org.hibernate.annotations.ResultCheckStyle; @@ -29,6 +30,7 @@ ) @SQLDelete(sql = "UPDATE applications_dependency SET deleted = true WHERE id = ?", check = ResultCheckStyle.COUNT) @Where(clause = "deleted = false") +@ApplicationsDependencyNoCycle public class ApplicationsDependency extends AbstractEntity { @ManyToOne // in this bean, since the 'uniqueConstraints' has been specified, diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java index ae6438c..91f2aeb 100644 --- a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java +++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java @@ -1,16 +1,25 @@ package io.tackle.applicationinventory.mapper; -import io.tackle.applicationinventory.BusinessService; -import io.tackle.applicationinventory.Tag; import io.tackle.applicationinventory.entities.Application; import io.tackle.applicationinventory.entities.ApplicationImport; import io.tackle.applicationinventory.entities.ApplicationsDependency; +import io.tackle.applicationinventory.exceptions.ApplicationsInventoryException; +import javax.inject.Inject; +import javax.transaction.Transactional; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; import javax.ws.rs.core.Response; + import java.util.Set; +import static javax.transaction.Transactional.TxType.REQUIRES_NEW; + public class ApplicationDependencyAPIMapper extends ApplicationMapper { + @Inject + Validator validator; + private static final String FROM_DIRECTION = "SOUTHBOUND"; private static final String TO_DIRECTION = "NORTHBOUND"; @@ -19,6 +28,7 @@ public ApplicationDependencyAPIMapper() { } @Override + @Transactional(REQUIRES_NEW) public Response map(ApplicationImport importApp, Long parentId) { Application application = null; @@ -45,7 +55,7 @@ public Response map(ApplicationImport importApp, Long parentId) return Response.serverError().build(); } - if (applicationDependency == application) + if (applicationDependency.equals(application)) { importApp.setErrorMessage("Application cannot be a dependency of itself"); return Response.serverError().build(); @@ -80,7 +90,28 @@ else if (importApp.getDependencyDirection().equalsIgnoreCase(TO_DIRECTION)) return Response.serverError().build(); } - dependency.persistAndFlush(); + + System.out.println("Validating application: " + dependency.from + ", dependency: " + dependency.to); + System.out.println("Validator instantiated: " + validator); + + Set> constraintViolations = validator.validate( dependency ); + + if (constraintViolations.size() > 0) + { + importApp.setErrorMessage(constraintViolations.iterator().next().getMessage()); + System.out.println(constraintViolations.iterator().next().getMessage()); + return Response.serverError().build(); + } + + try { + dependency.persistAndFlush(); + } + catch(ApplicationsInventoryException aie) + { + importApp.setErrorMessage("Dependency cycle would be created"); + return Response.serverError().build(); + } + System.out.println("Success for application: " + dependency.from + ", dependency: " + dependency.to); return Response.ok().build(); } } diff --git a/src/main/java/io/tackle/applicationinventory/services/ImportService.java b/src/main/java/io/tackle/applicationinventory/services/ImportService.java index b5299a9..67c73b1 100644 --- a/src/main/java/io/tackle/applicationinventory/services/ImportService.java +++ b/src/main/java/io/tackle/applicationinventory/services/ImportService.java @@ -128,12 +128,11 @@ public Response importFile(@MultipartForm MultipartImportBody data) { !anImportRow.getRecordType1().equals(APPLICATION_IMPORT_TYPE) && !anImportRow.getRecordType1().equals(DEPENDENCY_IMPORT_TYPE)).collect(Collectors.toList()); - if(!noTypeImports.isEmpty()) { - noTypeImports.forEach(noTypeImport -> { - noTypeImport.setErrorMessage("Invalid Record Type"); - markFailedImportAsInvalid(noTypeImport); - }); - } + noTypeImports.forEach(noTypeImport -> { + noTypeImport.setErrorMessage("Invalid Record Type"); + markFailedImportAsInvalid(noTypeImport); + }); + parentRecord.importStatus = COMPLETED_STATUS; parentRecord.flush(); diff --git a/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java b/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java new file mode 100644 index 0000000..fc96216 --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java @@ -0,0 +1,25 @@ +package io.tackle.applicationinventory.validator; + + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({ TYPE, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@Constraint(validatedBy = { ApplicationsDependencyNoCycleValidator.class }) +@Documented +public @interface ApplicationsDependencyNoCycle { + + String message() default "{io.tackle.applicationinventory.validator.ApplicationsDependencyNoCycle.message}"; + + Class[] groups() default { }; + + Class[] payload() default { }; +} diff --git a/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycleValidator.java b/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycleValidator.java new file mode 100644 index 0000000..5b680c7 --- /dev/null +++ b/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycleValidator.java @@ -0,0 +1,64 @@ +package io.tackle.applicationinventory.validator; + +import io.quarkus.panache.common.Sort; +import io.tackle.applicationinventory.entities.Application; +import io.tackle.applicationinventory.entities.ApplicationsDependency; +import org.jgrapht.Graph; +import org.jgrapht.Graphs; +import org.jgrapht.alg.cycle.CycleDetector; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.graph.builder.GraphTypeBuilder; + +import javax.enterprise.context.ApplicationScoped; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.stream.Stream; + +@ApplicationScoped +public class ApplicationsDependencyNoCycleValidator implements ConstraintValidator { + + static Graph graph; + + @Override + public void initialize(ApplicationsDependencyNoCycle constraintAnnotation) { + + } + + @Override + public boolean isValid(ApplicationsDependency dependency, ConstraintValidatorContext context) { + if ( dependency == null ) { + return true; + } + + if (graph == null) + { + graph = GraphTypeBuilder. + directed() + .edgeClass(DefaultEdge.class) + .allowingSelfLoops(false) + .allowingMultipleEdges(false) + .weighted(false) + .buildGraph(); + + // load current data from DB that must be fine (i.e. without cycles) by definition + try (Stream dependencies = ApplicationsDependency.streamAll(Sort.by("id"))) { + dependencies.forEach(applicationsDependency -> Graphs.addEdgeWithVertices(graph, applicationsDependency.from, applicationsDependency.to)); + } + } + + // add the dependency + DefaultEdge edge = Graphs.addEdgeWithVertices(graph, dependency.from, dependency.to); + // check if it creates an cycle + CycleDetector cycleDetector = new CycleDetector<>(graph); + + if(cycleDetector.detectCycles()) + { + graph.removeEdge(edge); + return false; + } + else + { + return true; + } + } +} diff --git a/src/main/resources/ValidationMessages.properties b/src/main/resources/ValidationMessages.properties new file mode 100644 index 0000000..72270cd --- /dev/null +++ b/src/main/resources/ValidationMessages.properties @@ -0,0 +1 @@ +io.tackle.applicationinventory.validator.ApplicationsDependencyNoCycle.message=Oh No it's a Cycle \ No newline at end of file diff --git a/src/main/resources/db/migration/V20210914.1__alter_application_import.sql b/src/main/resources/db/migration/V20210914.1__alter_application_import.sql index 80c8fc7..8b65304 100644 --- a/src/main/resources/db/migration/V20210914.1__alter_application_import.sql +++ b/src/main/resources/db/migration/V20210914.1__alter_application_import.sql @@ -1,3 +1,3 @@ alter table if exists application_import add column dependency varchar (255), - add column dependencyDirection varchar (255); \ No newline at end of file + add column dependencyDirection varchar (50); \ No newline at end of file diff --git a/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java index b0b05f9..ff6d51b 100644 --- a/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java +++ b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java @@ -228,10 +228,6 @@ protected void testNullTagTypes() { assertNull(appImport1.getTagType19()); assertNull(appImport1.getTag20()); assertNull(appImport1.getTagType20()); - - - - } @Test @@ -267,12 +263,6 @@ protected void testNullDependency() { apiMapper.map(appImport2, appImportParent2.id); assertNull(appImport1.getDependency()); - - - - - - } } diff --git a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java index 121e3d5..44e0513 100644 --- a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java +++ b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java @@ -534,7 +534,6 @@ protected void testImportServiceNoBSRetrieved() { @Test @Order(10) protected void testImportDependencies() { - ClassLoader classLoader = getClass().getClassLoader(); File importFile = new File(classLoader.getResource("dependency_import.csv").getFile()); @@ -547,7 +546,6 @@ protected void testImportDependencies() { .multiPart("fileName","dependency_import.csv") .when().post(PATH) .then() - .log().all() .statusCode(200).extract().response(); assertEquals(200, response.getStatusCode()); @@ -560,7 +558,6 @@ protected void testImportDependencies() { .get("/application-import") .then() .statusCode(200) - .log().body() .body("_embedded.'application-import'.size()", is(5)); File importFile2 = new File(classLoader.getResource("only_dependencies_imported.csv").getFile()); @@ -574,12 +571,9 @@ protected void testImportDependencies() { .multiPart("fileName","only_dependencies_imported.csv") .when().post(PATH) .then() - .log().all() .statusCode(200).extract().response(); - - given() .accept("application/hal+json") .queryParam("isValid", Boolean.TRUE) @@ -587,7 +581,6 @@ protected void testImportDependencies() { .get("/application-import") .then() .statusCode(200) - .log().body() .body("_embedded.'application-import'.size()", is(5)); @@ -633,10 +626,7 @@ private void removeTestObjects(List appNamesToDelete) .then() .statusCode(204)); - System.out.println("Deleting applications:"); - for (String appName : appNamesToDelete) { - System.out.println("appName: " + appName); long firstApplicationId = Long.parseLong(given() .queryParam("name", appName) .accept(ContentType.JSON) @@ -644,7 +634,6 @@ private void removeTestObjects(List appNamesToDelete) .get("/application") .then() .statusCode(200) - .log().body() .extract() .path("[0].id") .toString()); From 3bbf639da29d6ee79b359eff63f424ab86e1dbde Mon Sep 17 00:00:00 2001 From: m-brophy Date: Thu, 23 Sep 2021 14:48:40 +0100 Subject: [PATCH 07/10] TACKLE-312: applications depependency cycle validation --- .../mapper/ApplicationDependencyAPIMapper.java | 18 ++++-------------- .../services/ImportService.java | 7 +++++-- .../ApplicationsDependencyNoCycle.java | 1 - .../resources/ValidationMessages.properties | 2 +- .../services/ImportServiceTest.java | 3 +++ src/test/resources/dependency_import.csv | 1 + 6 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java index 91f2aeb..1bbd365 100644 --- a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java +++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java @@ -3,18 +3,16 @@ import io.tackle.applicationinventory.entities.Application; import io.tackle.applicationinventory.entities.ApplicationImport; import io.tackle.applicationinventory.entities.ApplicationsDependency; -import io.tackle.applicationinventory.exceptions.ApplicationsInventoryException; +import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; -import javax.transaction.Transactional; import javax.validation.ConstraintViolation; import javax.validation.Validator; import javax.ws.rs.core.Response; import java.util.Set; -import static javax.transaction.Transactional.TxType.REQUIRES_NEW; - +@ApplicationScoped public class ApplicationDependencyAPIMapper extends ApplicationMapper { @Inject @@ -28,7 +26,6 @@ public ApplicationDependencyAPIMapper() { } @Override - @Transactional(REQUIRES_NEW) public Response map(ApplicationImport importApp, Long parentId) { Application application = null; @@ -99,18 +96,11 @@ else if (importApp.getDependencyDirection().equalsIgnoreCase(TO_DIRECTION)) if (constraintViolations.size() > 0) { importApp.setErrorMessage(constraintViolations.iterator().next().getMessage()); - System.out.println(constraintViolations.iterator().next().getMessage()); return Response.serverError().build(); } - try { - dependency.persistAndFlush(); - } - catch(ApplicationsInventoryException aie) - { - importApp.setErrorMessage("Dependency cycle would be created"); - return Response.serverError().build(); - } + dependency.persistAndFlush(); + System.out.println("Success for application: " + dependency.from + ", dependency: " + dependency.to); return Response.ok().build(); } diff --git a/src/main/java/io/tackle/applicationinventory/services/ImportService.java b/src/main/java/io/tackle/applicationinventory/services/ImportService.java index 67c73b1..bc44099 100644 --- a/src/main/java/io/tackle/applicationinventory/services/ImportService.java +++ b/src/main/java/io/tackle/applicationinventory/services/ImportService.java @@ -61,6 +61,9 @@ public class ImportService { @Inject Validator validator; + @Inject + ApplicationDependencyAPIMapper dependencyMapper; + @POST @Path("/upload") @Consumes(MediaType.MULTIPART_FORM_DATA) @@ -215,9 +218,9 @@ public void mapImportsToApplication(List importList, Set public void mapImportsToDependency(List importList,ImportSummary parentRecord) { - ApplicationMapper mapper = new ApplicationDependencyAPIMapper(); + importList.forEach(importedApplication -> { - Response response = mapper.map(importedApplication, parentRecord.id); + Response response = dependencyMapper.map(importedApplication, parentRecord.id); if (response.getStatus() != Response.Status.OK.getStatusCode()) { markFailedImportAsInvalid(importedApplication); diff --git a/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java b/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java index fc96216..f4bd4cf 100644 --- a/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java +++ b/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java @@ -1,6 +1,5 @@ package io.tackle.applicationinventory.validator; - import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; diff --git a/src/main/resources/ValidationMessages.properties b/src/main/resources/ValidationMessages.properties index 72270cd..457c299 100644 --- a/src/main/resources/ValidationMessages.properties +++ b/src/main/resources/ValidationMessages.properties @@ -1 +1 @@ -io.tackle.applicationinventory.validator.ApplicationsDependencyNoCycle.message=Oh No it's a Cycle \ No newline at end of file +io.tackle.applicationinventory.validator.ApplicationsDependencyNoCycle.message=Dependency Cycle would be created \ No newline at end of file diff --git a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java index 44e0513..b0d75cf 100644 --- a/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java +++ b/src/test/java/io/tackle/applicationinventory/services/ImportServiceTest.java @@ -557,9 +557,12 @@ protected void testImportDependencies() { .when() .get("/application-import") .then() + .log().body() .statusCode(200) .body("_embedded.'application-import'.size()", is(5)); + + File importFile2 = new File(classLoader.getResource("only_dependencies_imported.csv").getFile()); diff --git a/src/test/resources/dependency_import.csv b/src/test/resources/dependency_import.csv index 38c7fdf..64b4ca8 100644 --- a/src/test/resources/dependency_import.csv +++ b/src/test/resources/dependency_import.csv @@ -4,6 +4,7 @@ Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1, 1,OrderHubDependency2,"OrderHub Dependent on this",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat 2,OrderHub,,,,,,,,,,,,OrderHubDependency,SOUTHBOUND 2,OrderHub,,,,,,,,,,,,OrderHubDependency2,NORTHBOUND +2,OrderHubDependency,,,,,,,,,,,,OrderHubDependency2,SOUTHBOUND 2,OrderHub,,,,,,,,,,,,OrderHubDependency2,TO 2,OrderHub,,,,,,,,,,,,OrderHub,NORTHBOUND 2,Home Banking BU,,,,,,,,,,,,Online Investments service,SOUTHBOUND \ No newline at end of file From dd84f1e61440d01dc8c7d6fa375254bf3dfde768 Mon Sep 17 00:00:00 2001 From: m-brophy Date: Fri, 24 Sep 2021 15:14:55 +0100 Subject: [PATCH 08/10] TACKLE-312: dependency import cycle check change of approach to validation --- .../entities/ApplicationsDependency.java | 6 +- .../ApplicationDependencyAPIMapper.java | 37 +++-------- .../ApplicationsDependencyNoCycle.java | 24 ------- ...pplicationsDependencyNoCycleValidator.java | 64 ------------------- .../resources/ValidationMessages.properties | 1 - 5 files changed, 12 insertions(+), 120 deletions(-) delete mode 100644 src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java delete mode 100644 src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycleValidator.java delete mode 100644 src/main/resources/ValidationMessages.properties diff --git a/src/main/java/io/tackle/applicationinventory/entities/ApplicationsDependency.java b/src/main/java/io/tackle/applicationinventory/entities/ApplicationsDependency.java index 8c725e2..2d2dd90 100644 --- a/src/main/java/io/tackle/applicationinventory/entities/ApplicationsDependency.java +++ b/src/main/java/io/tackle/applicationinventory/entities/ApplicationsDependency.java @@ -2,7 +2,6 @@ import io.quarkus.panache.common.Sort; import io.tackle.applicationinventory.exceptions.ApplicationsInventoryException; -import io.tackle.applicationinventory.validator.ApplicationsDependencyNoCycle; import io.tackle.commons.annotations.Filterable; import io.tackle.commons.entities.AbstractEntity; import org.hibernate.annotations.ResultCheckStyle; @@ -30,7 +29,6 @@ ) @SQLDelete(sql = "UPDATE applications_dependency SET deleted = true WHERE id = ?", check = ResultCheckStyle.COUNT) @Where(clause = "deleted = false") -@ApplicationsDependencyNoCycle public class ApplicationsDependency extends AbstractEntity { @ManyToOne // in this bean, since the 'uniqueConstraints' has been specified, @@ -51,6 +49,10 @@ public class ApplicationsDependency extends AbstractEntity { @PrePersist @PreUpdate public void preChangesCheck() { + validate(from, to); + } + + public static void validate(Application from, Application to) throws ApplicationsInventoryException { // validate input data if (from == null || to == null) throw new ApplicationsInventoryException("Not valid application reference provided", Response.Status.BAD_REQUEST); // "self-loop" for dependencies is not allowed diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java index 1bbd365..0991642 100644 --- a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java +++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationDependencyAPIMapper.java @@ -3,21 +3,14 @@ import io.tackle.applicationinventory.entities.Application; import io.tackle.applicationinventory.entities.ApplicationImport; import io.tackle.applicationinventory.entities.ApplicationsDependency; +import io.tackle.applicationinventory.exceptions.ApplicationsInventoryException; import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; -import javax.validation.ConstraintViolation; -import javax.validation.Validator; import javax.ws.rs.core.Response; -import java.util.Set; - @ApplicationScoped public class ApplicationDependencyAPIMapper extends ApplicationMapper { - @Inject - Validator validator; - private static final String FROM_DIRECTION = "SOUTHBOUND"; private static final String TO_DIRECTION = "NORTHBOUND"; @@ -52,23 +45,14 @@ public Response map(ApplicationImport importApp, Long parentId) return Response.serverError().build(); } - if (applicationDependency.equals(application)) - { - importApp.setErrorMessage("Application cannot be a dependency of itself"); - return Response.serverError().build(); - } - - - - ApplicationsDependency dependency = new ApplicationsDependency(); - if (importApp.getDependencyDirection().equalsIgnoreCase(FROM_DIRECTION)) + if (importApp.getDependencyDirection() != null && importApp.getDependencyDirection().equalsIgnoreCase(FROM_DIRECTION)) { dependency.from = application; dependency.to = applicationDependency; } - else if (importApp.getDependencyDirection().equalsIgnoreCase(TO_DIRECTION)) + else if (importApp.getDependencyDirection() != null && importApp.getDependencyDirection().equalsIgnoreCase(TO_DIRECTION)) { dependency.from = applicationDependency; dependency.to = application; @@ -87,21 +71,16 @@ else if (importApp.getDependencyDirection().equalsIgnoreCase(TO_DIRECTION)) return Response.serverError().build(); } - - System.out.println("Validating application: " + dependency.from + ", dependency: " + dependency.to); - System.out.println("Validator instantiated: " + validator); - - Set> constraintViolations = validator.validate( dependency ); - - if (constraintViolations.size() > 0) + try{ + ApplicationsDependency.validate(dependency.from, dependency.to); + }catch(ApplicationsInventoryException aie) { - importApp.setErrorMessage(constraintViolations.iterator().next().getMessage()); - return Response.serverError().build(); + importApp.setErrorMessage(aie.getMessage()); + return aie.getResponse(); } dependency.persistAndFlush(); - System.out.println("Success for application: " + dependency.from + ", dependency: " + dependency.to); return Response.ok().build(); } } diff --git a/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java b/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java deleted file mode 100644 index f4bd4cf..0000000 --- a/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycle.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.tackle.applicationinventory.validator; - -import javax.validation.Constraint; -import javax.validation.Payload; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.ANNOTATION_TYPE; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Target({ TYPE, ANNOTATION_TYPE }) -@Retention(RUNTIME) -@Constraint(validatedBy = { ApplicationsDependencyNoCycleValidator.class }) -@Documented -public @interface ApplicationsDependencyNoCycle { - - String message() default "{io.tackle.applicationinventory.validator.ApplicationsDependencyNoCycle.message}"; - - Class[] groups() default { }; - - Class[] payload() default { }; -} diff --git a/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycleValidator.java b/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycleValidator.java deleted file mode 100644 index 5b680c7..0000000 --- a/src/main/java/io/tackle/applicationinventory/validator/ApplicationsDependencyNoCycleValidator.java +++ /dev/null @@ -1,64 +0,0 @@ -package io.tackle.applicationinventory.validator; - -import io.quarkus.panache.common.Sort; -import io.tackle.applicationinventory.entities.Application; -import io.tackle.applicationinventory.entities.ApplicationsDependency; -import org.jgrapht.Graph; -import org.jgrapht.Graphs; -import org.jgrapht.alg.cycle.CycleDetector; -import org.jgrapht.graph.DefaultEdge; -import org.jgrapht.graph.builder.GraphTypeBuilder; - -import javax.enterprise.context.ApplicationScoped; -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; -import java.util.stream.Stream; - -@ApplicationScoped -public class ApplicationsDependencyNoCycleValidator implements ConstraintValidator { - - static Graph graph; - - @Override - public void initialize(ApplicationsDependencyNoCycle constraintAnnotation) { - - } - - @Override - public boolean isValid(ApplicationsDependency dependency, ConstraintValidatorContext context) { - if ( dependency == null ) { - return true; - } - - if (graph == null) - { - graph = GraphTypeBuilder. - directed() - .edgeClass(DefaultEdge.class) - .allowingSelfLoops(false) - .allowingMultipleEdges(false) - .weighted(false) - .buildGraph(); - - // load current data from DB that must be fine (i.e. without cycles) by definition - try (Stream dependencies = ApplicationsDependency.streamAll(Sort.by("id"))) { - dependencies.forEach(applicationsDependency -> Graphs.addEdgeWithVertices(graph, applicationsDependency.from, applicationsDependency.to)); - } - } - - // add the dependency - DefaultEdge edge = Graphs.addEdgeWithVertices(graph, dependency.from, dependency.to); - // check if it creates an cycle - CycleDetector cycleDetector = new CycleDetector<>(graph); - - if(cycleDetector.detectCycles()) - { - graph.removeEdge(edge); - return false; - } - else - { - return true; - } - } -} diff --git a/src/main/resources/ValidationMessages.properties b/src/main/resources/ValidationMessages.properties deleted file mode 100644 index 457c299..0000000 --- a/src/main/resources/ValidationMessages.properties +++ /dev/null @@ -1 +0,0 @@ -io.tackle.applicationinventory.validator.ApplicationsDependencyNoCycle.message=Dependency Cycle would be created \ No newline at end of file From 91ce99502b6c37403a9298ea13c0595f63d33ef4 Mon Sep 17 00:00:00 2001 From: m-brophy Date: Fri, 24 Sep 2021 15:55:50 +0100 Subject: [PATCH 09/10] TACKLE-312: code test coverage --- .../resources/ApplicationImportNullTest.java | 16 +++++++++++++++- .../resources/only_dependencies_imported.csv | 3 ++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java index ff6d51b..b96453c 100644 --- a/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java +++ b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportNullTest.java @@ -262,7 +262,21 @@ protected void testNullDependency() { apiMapper.map(appImport2, appImportParent2.id); - assertNull(appImport1.getDependency()); + assertNull(appImport2.getDependency()); + + ImportSummary appImportParent3 = new ImportSummary(); + appImportParent3.persistAndFlush(); + + ApplicationImport appImport3 = new ApplicationImport(); + appImport3.setApplicationName("Online Investments service"); + appImport3.importSummary = appImportParent; + appImport3.setRecordType1("2"); + appImport3.setDependency("Home Banking BU"); + appImport3.setDependencyDirection(null); + + apiMapper.map(appImport3, appImportParent3.id); + + assertNull(appImport3.getDependencyDirection()); } } diff --git a/src/test/resources/only_dependencies_imported.csv b/src/test/resources/only_dependencies_imported.csv index 8eac955..3687d73 100644 --- a/src/test/resources/only_dependencies_imported.csv +++ b/src/test/resources/only_dependencies_imported.csv @@ -1,4 +1,5 @@ Record Type 1,Application Name,Description,Comments,Business Service,Tag Type 1,Tag 1,Tag Type 2,Tag 2,Tag Type 3,Tag 3,Tag Type 4,Tag 4,Dependency,Dependency Direction 2,OrderHub,,,,,,,,,,,,,NORTHBOUND 2,,,,,,,,,,,,,OrderHubDependency,NORTHBOUND -2,OrderHub,,,,,,,,,,,,OrderHubDependency,NORTHBOUND \ No newline at end of file +2,OrderHub,,,,,,,,,,,,OrderHubDependency,NORTHBOUND +2,OrderHub,,,,,,,,,,,,OrderHubDependency, \ No newline at end of file From 9777d866925c43117b45fbb116112a7357c18a21 Mon Sep 17 00:00:00 2001 From: m-brophy Date: Wed, 29 Sep 2021 16:29:28 +0100 Subject: [PATCH 10/10] TACKLE-312: change instantiation of ApplicationDependencyAPIMapper --- .../tackle/applicationinventory/services/ImportService.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/io/tackle/applicationinventory/services/ImportService.java b/src/main/java/io/tackle/applicationinventory/services/ImportService.java index bc44099..3def4dd 100644 --- a/src/main/java/io/tackle/applicationinventory/services/ImportService.java +++ b/src/main/java/io/tackle/applicationinventory/services/ImportService.java @@ -61,9 +61,6 @@ public class ImportService { @Inject Validator validator; - @Inject - ApplicationDependencyAPIMapper dependencyMapper; - @POST @Path("/upload") @Consumes(MediaType.MULTIPART_FORM_DATA) @@ -218,7 +215,7 @@ public void mapImportsToApplication(List importList, Set public void mapImportsToDependency(List importList,ImportSummary parentRecord) { - + ApplicationMapper dependencyMapper = new ApplicationDependencyAPIMapper(); importList.forEach(importedApplication -> { Response response = dependencyMapper.map(importedApplication, parentRecord.id); if (response.getStatus() != Response.Status.OK.getStatusCode())