Skip to content

Commit

Permalink
TACKLE-312: import dependencies (#136)
Browse files Browse the repository at this point in the history
* TACKLE-312: import dependencies

* TACKLE-312: add validation for dependency import

* TACKLE-312: dependency import change literals 'from' and 'to' to southbound and northbound

* TACKLE-312: code test coverage

* TACKLE-312: import dependencies make dependency direction case insensitive

* TACKLE-312: applicationsDependency hibernatevalidator first cut

* TACKLE-312: applications depependency cycle validation

* TACKLE-312: dependency import cycle check change of approach to validation

* TACKLE-312: code test coverage

* TACKLE-312: change instantiation of ApplicationDependencyAPIMapper
  • Loading branch information
m-brophy authored Sep 30, 2021
1 parent 43a47d7 commit 2af484e
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}


}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package io.tackle.applicationinventory.mapper;

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.ws.rs.core.Response;

@ApplicationScoped
public class ApplicationDependencyAPIMapper extends ApplicationMapper {

private static final String FROM_DIRECTION = "SOUTHBOUND";
private static final String TO_DIRECTION = "NORTHBOUND";

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();
}

ApplicationsDependency dependency = new ApplicationsDependency();

if (importApp.getDependencyDirection() != null && importApp.getDependencyDirection().equalsIgnoreCase(FROM_DIRECTION))
{
dependency.from = application;
dependency.to = applicationDependency;
}
else if (importApp.getDependencyDirection() != null && importApp.getDependencyDirection().equalsIgnoreCase(TO_DIRECTION))
{
dependency.from = applicationDependency;
dependency.to = application;
}
else
{
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();
}

try{
ApplicationsDependency.validate(dependency.from, dependency.to);
}catch(ApplicationsInventoryException aie)
{
importApp.setErrorMessage(aie.getMessage());
return aie.getResponse();
}

dependency.persistAndFlush();

return Response.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -69,43 +72,67 @@ public Response importFile(@MultipartForm MultipartImportBody data) {
parentRecord.filename = data.getFileName();
parentRecord.importStatus = IN_PROGRESS_STATUS;
parentRecord.persistAndFlush();
Set<Tag> 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<BusinessService> 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<ApplicationImport> importList = writeFile(data.getFile(), data.getFileName(), parentRecord);
//we're not allowed duplicate application names within the file
Set<String> discreteAppNames = new HashSet();
//make a list of all the duplicate app names
List<ApplicationImport> importListMinusDuplicates = importList;
List<ApplicationImport> 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<ApplicationImport> 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<Tag> 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<BusinessService> 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<String> discreteAppNames = new HashSet();
//make a list of all the duplicate app names
List<ApplicationImport> importListMinusDuplicates = applicationTypeImports;
List<ApplicationImport> 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);
}
mapImportsToApplication(importListMinusDuplicates, tags, businessServices, parentRecord);

List<ApplicationImport> dependencyTypeImports = importList.stream().filter(anImportRow ->
anImportRow.getRecordType1().equals(DEPENDENCY_IMPORT_TYPE)).collect(Collectors.toList());

if(!dependencyTypeImports.isEmpty()) {
mapImportsToDependency(dependencyTypeImports, parentRecord);
}

List<ApplicationImport> noTypeImports = importList.stream().filter(anImportRow ->
!anImportRow.getRecordType1().equals(APPLICATION_IMPORT_TYPE)
&& !anImportRow.getRecordType1().equals(DEPENDENCY_IMPORT_TYPE)).collect(Collectors.toList());

noTypeImports.forEach(noTypeImport -> {
noTypeImport.setErrorMessage("Invalid Record Type");
markFailedImportAsInvalid(noTypeImport);
});

parentRecord.importStatus = COMPLETED_STATUS;
parentRecord.flush();

Expand Down Expand Up @@ -186,6 +213,19 @@ public void mapImportsToApplication(List<ApplicationImport> importList, Set<Tag>
});
}

public void mapImportsToDependency(List<ApplicationImport> importList,ImportSummary parentRecord)
{
ApplicationMapper dependencyMapper = new ApplicationDependencyAPIMapper();
importList.forEach(importedApplication -> {
Response response = dependencyMapper.map(importedApplication, parentRecord.id);
if (response.getStatus() != Response.Status.OK.getStatusCode())
{
markFailedImportAsInvalid(importedApplication);
}

});
}

private void markFailedImportAsInvalid(ApplicationImport importFile)
{
importFile.setValid(Boolean.FALSE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
alter table if exists application_import
add column dependency varchar (255),
add column dependencyDirection varchar (50);
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -160,6 +161,8 @@ protected void testNullTagTypes() {
appImport1.setErrorMessage(null);
appImport1.setRecordType1(null);
appImport1.setComments(null);
appImport1.setDependency(null);
appImport1.setDependencyDirection(null);

Set<Tag> tags = new HashSet<>() ;
Tag.TagType tagType1 = new Tag.TagType();
Expand Down Expand Up @@ -225,9 +228,55 @@ protected void testNullTagTypes() {
assertNull(appImport1.getTagType19());
assertNull(appImport1.getTag20());
assertNull(appImport1.getTagType20());
}

@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(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());
}

}
Loading

0 comments on commit 2af484e

Please sign in to comment.