diff --git a/USAGE.md b/USAGE.md
index 4fcf9c8..9f145af 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -123,3 +123,19 @@ the payload obtained will look like this :
]
```
+## `/file/upload` endpoint
+This endpoint will upload a csv file of the format of this file:
+`src/test/resources/sample_application_import.csv`
+
+The upload process stores each row from the file as an ApplicationImport entity.
+Each ApplicationImport is then used to attempt to create a new Application.
+
+The endpoint expects a Multipart Form with these params:
+`file`: the file to be imported, in json format.
+`fileName`: A text identifier for the file to be stored under.
+
+## `/application-import` endpoint
+This endpoint can be used to retrieve ApplicationImports. The results retrieved can be filtered by
+`isValid=false` or `isValid=true` to view those imports which have either failed or succeeded,
+and by `filename=your_filename` to view results for a specific import attempt.
+
diff --git a/pom.xml b/pom.xml
index f6d54e7..0910360 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,14 @@
io.quarkus
quarkus-resteasy-jackson
+
+ io.quarkus
+ quarkus-resteasy-multipart
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-csv
+
io.quarkus
quarkus-smallrye-openapi
@@ -105,6 +113,18 @@
io.quarkus
quarkus-oidc
+
+ io.quarkus
+ quarkus-oidc-token-propagation
+
+
+ io.quarkus
+ quarkus-rest-client
+
+
+ io.quarkus
+ quarkus-rest-client-jackson
+
io.quarkus
quarkus-keycloak-authorization
@@ -166,6 +186,17 @@
2.6.0
test
+
+ org.mockito
+ mockito-core
+ 3.8.0
+ test
+
+
+ io.quarkus
+ quarkus-junit5-mockito
+ test
+
@@ -199,6 +230,14 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 10
+
+
diff --git a/src/main/java/io/tackle/applicationinventory/BusinessService.java b/src/main/java/io/tackle/applicationinventory/BusinessService.java
new file mode 100644
index 0000000..386df35
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/BusinessService.java
@@ -0,0 +1,9 @@
+package io.tackle.applicationinventory;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class BusinessService {
+ public String id;
+ public String name;
+}
diff --git a/src/main/java/io/tackle/applicationinventory/MultipartImportBody.java b/src/main/java/io/tackle/applicationinventory/MultipartImportBody.java
new file mode 100644
index 0000000..ac73b3a
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/MultipartImportBody.java
@@ -0,0 +1,36 @@
+package io.tackle.applicationinventory;
+
+import javax.ws.rs.FormParam;
+import javax.ws.rs.core.MediaType;
+
+import org.jboss.resteasy.annotations.providers.multipart.PartType;
+
+public class MultipartImportBody {
+ @FormParam("file")
+ @PartType(MediaType.APPLICATION_JSON)
+ private String file;
+ @FormParam("fileName")
+ @PartType(MediaType.TEXT_PLAIN)
+ private String fileName;
+
+ public MultipartImportBody() {}
+
+
+ public void setFile(String file)
+ {
+ this.file = file;
+ }
+
+ public void setFilename(String fileName)
+ {
+ this.fileName = fileName;
+ }
+
+ public String getFile() {
+ return file;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+}
diff --git a/src/main/java/io/tackle/applicationinventory/Tag.java b/src/main/java/io/tackle/applicationinventory/Tag.java
new file mode 100644
index 0000000..da2924f
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/Tag.java
@@ -0,0 +1,17 @@
+package io.tackle.applicationinventory;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import java.util.List;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Tag {
+ public String id;
+ public String name;
+ public TagType tagType;
+
+ public static class TagType {
+ public String id;
+ public String name;
+ }
+}
diff --git a/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java
new file mode 100644
index 0000000..96c7b48
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImport.java
@@ -0,0 +1,515 @@
+package io.tackle.applicationinventory.entities;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import io.tackle.commons.annotations.CheckType;
+import io.tackle.commons.annotations.Filterable;
+import io.tackle.commons.entities.AbstractEntity;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "application_import")
+public class ApplicationImport extends AbstractEntity {
+ private String recordType1;
+ private String applicationName;
+ private String description;
+ private String comments;
+ private String businessService;
+ private String tagType1;
+ private String tag1;
+ private String tagType2;
+ private String tag2;
+ private String tagType3;
+ private String tag3;
+ private String tagType4;
+ private String tag4;
+ private String tagType5;
+ private String tag5;
+ private String tagType6;
+ private String tag6;
+ private String tagType7;
+ private String tag7;
+ private String tagType8;
+ private String tag8;
+ private String tagType9;
+ private String tag9;
+ private String tagType10;
+ private String tag10;
+ private String tagType11;
+ private String tag11;
+ private String tagType12;
+ private String tag12;
+ private String tagType13;
+ private String tag13;
+ private String tagType14;
+ private String tag14;
+ private String tagType15;
+ private String tag15;
+ private String tagType16;
+ private String tag16;
+ private String tagType17;
+ private String tag17;
+ private String tagType18;
+ private String tag18;
+ private String tagType19;
+ private String tag19;
+ private String tagType20;
+ private String tag20;
+ private String errorMessage;
+ @Filterable(check = CheckType.EQUAL)
+ public Boolean isValid = true;
+ @Filterable
+ public String filename;
+
+ private String status;
+ @ManyToOne(optional = false)
+ @JsonIgnore
+ @Filterable(filterName = "importSummary.id")
+ public ImportSummary importSummary;
+
+ public ApplicationImport() {
+
+ }
+
+ public String getRecordType1() {
+ return recordType1;
+ }
+
+ @JsonSetter("Record Type 1")
+ public void setRecordType1(String recordType1) {
+ this.recordType1 = recordType1;
+ }
+
+ public String getApplicationName() {
+ return applicationName;
+ }
+
+ @JsonSetter("Application Name")
+ public void setApplicationName(String applicationName) {
+ this.applicationName = applicationName;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ @JsonSetter("Description")
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getComments() {
+ return comments;
+ }
+
+ @JsonSetter("Comments")
+ public void setComments(String comments) {
+ this.comments = comments;
+ }
+
+ public String getBusinessService() {
+ return businessService;
+ }
+
+ @JsonSetter("Business Service")
+ public void setBusinessService(String businessService) {
+ this.businessService = businessService;
+ }
+
+ public String getTagType1() {
+ return tagType1;
+ }
+
+ @JsonSetter("Tag Type 1")
+ public void setTagType1(String tagType1) {
+ this.tagType1 = tagType1;
+ }
+
+ public String getTag1() {
+ return tag1;
+ }
+
+ @JsonSetter("Tag 1")
+ public void setTag1(String tag1) {
+ this.tag1 = tag1;
+ }
+
+ public String getTagType2() {
+ return tagType2;
+ }
+
+ @JsonSetter("Tag Type 2")
+ public void setTagType2(String tagType2) {
+ this.tagType2 = tagType2;
+ }
+
+ public String getTag2() {
+ return tag2;
+ }
+
+ @JsonSetter("Tag 2")
+ public void setTag2(String tag2) {
+ this.tag2 = tag2;
+ }
+
+ public String getTagType3() {
+ return tagType3;
+ }
+
+ @JsonSetter("Tag Type 3")
+ public void setTagType3(String tagType3) {
+ this.tagType3 = tagType3;
+ }
+
+ public String getTag3() {
+ return tag3;
+ }
+
+ @JsonSetter("Tag 3")
+ public void setTag3(String tag3) {
+ this.tag3 = tag3;
+ }
+
+ public String getTagType4() {
+ return tagType4;
+ }
+
+ @JsonSetter("Tag Type 4")
+ public void setTagType4(String tagType4) {
+ this.tagType4 = tagType4;
+ }
+
+ public String getTag4() {
+ return tag4;
+ }
+
+ @JsonSetter("Tag 4")
+ public void setTag4(String tag4) {
+ this.tag4 = tag4;
+ }
+
+ public String getTagType5() {
+ return tagType5;
+ }
+
+ @JsonSetter("Tag Type 5")
+ public void setTagType5(String tagType5) {
+ this.tagType5 = tagType5;
+ }
+
+ public String getTag5() {
+ return tag5;
+ }
+
+ @JsonSetter("Tag 5")
+ public void setTag5(String tag5) {
+ this.tag5 = tag5;
+ }
+
+ public String getTagType6() {
+ return tagType6;
+ }
+
+ @JsonSetter("Tag Type 6")
+ public void setTagType6(String tagType6) {
+ this.tagType6 = tagType6;
+ }
+
+ public String getTag6() {
+ return tag6;
+ }
+
+ @JsonSetter("Tag 6")
+ public void setTag6(String tag6) {
+ this.tag6 = tag6;
+ }
+
+ public String getTagType7() {
+ return tagType7;
+ }
+
+ @JsonSetter("Tag Type 7")
+ public void setTagType7(String tagType7) {
+ this.tagType7 = tagType7;
+ }
+
+ public String getTag7() {
+ return tag7;
+ }
+
+ @JsonSetter("Tag 7")
+ public void setTag7(String tag7) {
+ this.tag7 = tag7;
+ }
+
+ public String getTagType8() {
+ return tagType8;
+ }
+
+ @JsonSetter("Tag Type 8")
+ public void setTagType8(String tagType8) {
+ this.tagType8 = tagType8;
+ }
+
+ public String getTag8() {
+ return tag8;
+ }
+
+ @JsonSetter("Tag 8")
+ public void setTag8(String tag8) {
+ this.tag8 = tag8;
+ }
+
+ public String getTagType9() {
+ return tagType9;
+ }
+
+ @JsonSetter("Tag Type 9")
+ public void setTagType9(String tagType9) {
+ this.tagType9 = tagType9;
+ }
+
+ public String getTag9() {
+ return tag9;
+ }
+
+ @JsonSetter("Tag 9")
+ public void setTag9(String tag9) {
+ this.tag9 = tag9;
+ }
+
+ public String getTagType10() {
+ return tagType10;
+ }
+
+ @JsonSetter("Tag Type 10")
+ public void setTagType10(String tagType10) {
+ this.tagType10 = tagType10;
+ }
+
+ public String getTag10() {
+ return tag10;
+ }
+
+ @JsonSetter("Tag 10")
+ public void setTag10(String tag10) {
+ this.tag10 = tag10;
+ }
+
+ public String getTagType11() {
+ return tagType11;
+ }
+
+ @JsonSetter("Tag Type 11")
+ public void setTagType11(String tagType11) {
+ this.tagType11 = tagType11;
+ }
+
+ public String getTag11() {
+ return tag11;
+ }
+
+ @JsonSetter("Tag 11")
+ public void setTag11(String tag11) {
+ this.tag11 = tag11;
+ }
+
+ public String getTagType12() {
+ return tagType12;
+ }
+
+ @JsonSetter("Tag Type 12")
+ public void setTagType12(String tagType12) {
+ this.tagType12 = tagType12;
+ }
+
+ public String getTag12() {
+ return tag12;
+ }
+
+ @JsonSetter("Tag 12")
+ public void setTag12(String tag12) {
+ this.tag12 = tag12;
+ }
+
+ public String getTagType13() {
+ return tagType13;
+ }
+
+ @JsonSetter("Tag Type 13")
+ public void setTagType13(String tagType13) {
+ this.tagType13 = tagType13;
+ }
+
+ public String getTag13() {
+ return tag13;
+ }
+
+ @JsonSetter("Tag 13")
+ public void setTag13(String tag13) {
+ this.tag13 = tag13;
+ }
+
+ public String getTagType14() {
+ return tagType14;
+ }
+
+ @JsonSetter("Tag Type 14")
+ public void setTagType14(String tagType14) {
+ this.tagType14 = tagType14;
+ }
+
+ public String getTag14() {
+ return tag14;
+ }
+
+ @JsonSetter("Tag 14")
+ public void setTag14(String tag14) {
+ this.tag14 = tag14;
+ }
+
+ public String getTagType15() {
+ return tagType15;
+ }
+
+ @JsonSetter("Tag Type 15")
+ public void setTagType15(String tagType15) {
+ this.tagType15 = tagType15;
+ }
+
+ public String getTag15() {
+ return tag15;
+ }
+
+ @JsonSetter("Tag 15")
+ public void setTag15(String tag15) {
+ this.tag15 = tag15;
+ }
+
+ public String getTagType16() {
+ return tagType16;
+ }
+
+ @JsonSetter("Tag Type 16")
+ public void setTagType16(String tagType16) {
+ this.tagType16 = tagType16;
+ }
+
+ public String getTag16() {
+ return tag16;
+ }
+
+ @JsonSetter("Tag 16")
+ public void setTag16(String tag16) {
+ this.tag16 = tag16;
+ }
+
+ public String getTagType17() {
+ return tagType17;
+ }
+
+ @JsonSetter("Tag Type 17")
+ public void setTagType17(String tagType17) {
+ this.tagType17 = tagType17;
+ }
+
+ public String getTag17() {
+ return tag17;
+ }
+
+ @JsonSetter("Tag 17")
+ public void setTag17(String tag17) {
+ this.tag17 = tag17;
+ }
+
+ public String getTagType18() {
+ return tagType18;
+ }
+
+ @JsonSetter("Tag Type 18")
+ public void setTagType18(String tagType18) {
+ this.tagType18 = tagType18;
+ }
+
+ public String getTag18() {
+ return tag18;
+ }
+
+ @JsonSetter("Tag 18")
+ public void setTag18(String tag18) {
+ this.tag18 = tag18;
+ }
+
+ public String getTagType19() {
+ return tagType19;
+ }
+
+ @JsonSetter("Tag Type 19")
+ public void setTagType19(String tagType19) {
+ this.tagType19 = tagType19;
+ }
+
+ public String getTag19() {
+ return tag19;
+ }
+
+ @JsonSetter("Tag 19")
+ public void setTag19(String tag19) {
+ this.tag19 = tag19;
+ }
+
+ public String getTagType20() {
+ return tagType20;
+ }
+
+ @JsonSetter("Tag Type 20")
+ public void setTagType20(String tagType20) {
+ this.tagType20 = tagType20;
+ }
+
+ public String getTag20() {
+ return tag20;
+ }
+
+ @JsonSetter("Tag 20")
+ public void setTag20(String tag20) {
+ this.tag20 = tag20;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ public Boolean getValid() {
+ return isValid;
+ }
+
+ public void setValid(Boolean valid) {
+ isValid = valid;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+}
+
+
+
diff --git a/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java
new file mode 100644
index 0000000..66db63a
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/entities/ApplicationImportForCsv.java
@@ -0,0 +1,103 @@
+package io.tackle.applicationinventory.entities;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+@RegisterForReflection
+@JsonIgnoreProperties({ "createUser", "updateUser", "errorMessage", "valid", "isValid", "filename", "status", "id"})
+public abstract class ApplicationImportForCsv {
+ @JsonProperty("Record Type 1")
+ private String recordType1;
+ @JsonProperty("Application Name")
+ private String applicationName;
+ @JsonProperty("Description")
+ private String description;
+ @JsonProperty("Comments")
+ private String comments;
+ @JsonProperty("Business Service")
+ private String businessService;
+ @JsonProperty("Tag Type 1")
+ private String tagType1;
+ @JsonProperty("Tag 1")
+ private String tag1;
+ @JsonProperty("Tag Type 2")
+ private String tagType2;
+ @JsonProperty("Tag 2")
+ private String tag2;
+ @JsonProperty("Tag Type 3")
+ private String tagType3;
+ @JsonProperty("Tag 3")
+ private String tag3;
+ @JsonProperty("Tag Type 4")
+ private String tagType4;
+ @JsonProperty("Tag 4")
+ private String tag4;
+ @JsonProperty("Tag Type 5")
+ private String tagType5;
+ @JsonProperty("Tag 5")
+ private String tag5;
+ @JsonProperty("Tag Type 6")
+ private String tagType6;
+ @JsonProperty("Tag 6")
+ private String tag6;
+ @JsonProperty("Tag Type 7")
+ private String tagType7;
+ @JsonProperty("Tag 7")
+ private String tag7;
+ @JsonProperty("Tag Type 8")
+ private String tagType8;
+ @JsonProperty("Tag 8")
+ private String tag8;
+ @JsonProperty("Tag Type 9")
+ private String tagType9;
+ @JsonProperty("Tag 9")
+ private String tag9;
+ @JsonProperty("Tag Type 10")
+ private String tagType10;
+ @JsonProperty("Tag 10")
+ private String tag10;
+ @JsonProperty("Tag Type 11")
+ private String tagType11;
+ @JsonProperty("Tag 11")
+ private String tag11;
+ @JsonProperty("Tag Type 12")
+ private String tagType12;
+ @JsonProperty("Tag 12")
+ private String tag12;
+ @JsonProperty("Tag Type 13")
+ private String tagType13;
+ @JsonProperty("Tag 13")
+ private String tag13;
+ @JsonProperty("Tag Type 14")
+ private String tagType14;
+ @JsonProperty("Tag 14")
+ private String tag14;
+ @JsonProperty("Tag Type 15")
+ private String tagType15;
+ @JsonProperty("Tag 15")
+ private String tag15;
+ @JsonProperty("Tag Type 16")
+ private String tagType16;
+ @JsonProperty("Tag 16")
+ private String tag16;
+ @JsonProperty("Tag Type 17")
+ private String tagType17;
+ @JsonProperty("Tag 17")
+ private String tag17;
+ @JsonProperty("Tag Type 18")
+ private String tagType18;
+ @JsonProperty("Tag 18")
+ private String tag18;
+ @JsonProperty("Tag Type 19")
+ private String tagType19;
+ @JsonProperty("Tag 19")
+ private String tag19;
+ @JsonProperty("Tag Type 20")
+ private String tagType20;
+ @JsonProperty("Tag 20")
+ private String tag20;
+}
+
+
+
diff --git a/src/main/java/io/tackle/applicationinventory/entities/ImportSummary.java b/src/main/java/io/tackle/applicationinventory/entities/ImportSummary.java
new file mode 100644
index 0000000..4c00048
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/entities/ImportSummary.java
@@ -0,0 +1,47 @@
+package io.tackle.applicationinventory.entities;
+
+import com.fasterxml.jackson.annotation.JsonBackReference;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.tackle.commons.annotations.Filterable;
+import io.tackle.commons.entities.AbstractEntity;
+import org.hibernate.annotations.CreationTimestamp;
+
+import javax.persistence.*;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Entity
+@Table(
+ name = "import_summary"
+)
+public class ImportSummary extends AbstractEntity {
+ @Filterable
+ public String filename;
+ @Filterable
+ public String importStatus;
+ public String errorMessage;
+ @CreationTimestamp
+ @Column(updatable=false)
+ public Timestamp importTime;
+ @OneToMany(mappedBy = "importSummary", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
+ @JsonIgnore
+ public List applicationImports = new ArrayList<>();
+
+ @Transient
+ public int invalidCount;
+
+ @Transient
+ public int validCount;
+
+ @PostLoad
+ private void setCounts()
+ {
+ invalidCount = applicationImports.stream().filter(applicationImport -> applicationImport.isValid.equals(Boolean.FALSE)).collect(Collectors.toList()).size();
+ validCount = applicationImports.stream().filter(applicationImport -> applicationImport.isValid.equals(Boolean.TRUE)).collect(Collectors.toList()).size();
+ }
+
+
+
+}
diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationInventoryAPIMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationInventoryAPIMapper.java
new file mode 100644
index 0000000..025051f
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationInventoryAPIMapper.java
@@ -0,0 +1,130 @@
+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 javax.ws.rs.core.Response;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.NoSuchElementException;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+
+public class ApplicationInventoryAPIMapper extends ApplicationMapper{
+
+ public ApplicationInventoryAPIMapper( Set tags, Set businessServices) {
+ super(tags, businessServices);
+ }
+
+ @Override
+ public Response map(ApplicationImport importApp, Long parentId)
+ {
+ //importApp.setParentId(parentId);
+ Application newApp = new Application();
+ Set tags = new HashSet<>();
+
+ try {
+ if (importApp.getBusinessService() != null && !importApp.getBusinessService().isEmpty()) {
+ newApp.businessService = addBusinessService(importApp.getBusinessService());
+ } else {
+ importApp.setErrorMessage("Business Service is Mandatory");
+ return Response.serverError().build();
+ }
+
+ }
+ catch(NoSuchElementException nsee1){
+ nsee1.printStackTrace();
+ importApp.setErrorMessage("Business Service: " + importApp.getBusinessService() + " does not exist");
+ return Response.serverError().build();
+ }
+ newApp.comments = importApp.getComments();
+
+ if (importApp.getDescription() != null && !importApp.getDescription().isEmpty()) {
+ newApp.description = importApp.getDescription();
+ } else {
+ importApp.setErrorMessage("Description is Mandatory");
+ return Response.serverError().build();
+ }
+
+ // check for duplicates on table
+ long count = Application.count("name",importApp.getApplicationName());
+ if(count>0)
+ {
+ importApp.setErrorMessage("Duplicate ApplicationName in table: " + importApp.getApplicationName());
+ return Response.serverError().build();
+ }
+ newApp.name = importApp.getApplicationName();
+ String currentTag = "";
+ String currentTagType = "";
+ try{
+ for(int i=1;i<=20;i++)
+ {
+ String tagMethodName = "getTag" + i;
+ String tagTypeMethodName = "getTagType" + i;
+ java.lang.reflect.Method tagMethod;
+ java.lang.reflect.Method tagTypeMethod;
+ tagMethod = importApp.getClass().getMethod(tagMethodName);
+ tagTypeMethod = importApp.getClass().getMethod(tagTypeMethodName);
+ currentTag = (String)tagMethod.invoke(importApp);
+ currentTagType = (String)tagTypeMethod.invoke(importApp);
+ if ((currentTag == null || currentTag.isEmpty())
+ && (currentTagType == null || currentTagType.isEmpty())) {
+ //don't validate and add tag/tagtype if both aren't present
+ }
+ else{
+ tags.add(addTag(currentTag, currentTagType));
+ }
+
+ }
+
+ }
+ catch(NoSuchElementException nsee3)
+ {
+ nsee3.printStackTrace();
+ importApp.setErrorMessage("Tag Type " + currentTagType + " and Tag " + currentTag + " combination does not exist");
+ return Response.serverError().build();
+ }
+ catch (SecurityException |NoSuchMethodException | IllegalAccessException | InvocationTargetException e)
+ {
+ e.printStackTrace();
+ importApp.setErrorMessage("Tag Type " + currentTagType + " and Tag " + currentTag + " unable to perform validation");
+ return Response.serverError().build();
+ }
+ // try {
+ newApp.tags = tags;
+ newApp.persistAndFlush();
+ /** }
+ catch(Exception e){
+ e.printStackTrace();
+ importApp.setErrorMessage("Duplicate ApplicationName in table: " + importApp.getApplicationName());
+ return Response.serverError().build();
+ }*/
+ return Response.ok().build();
+ }
+
+
+ private String addBusinessService(String businessServiceName) throws NoSuchElementException
+ {
+ Optional businessServiceOptional = businessServices.stream().filter(businessServiceControls -> businessServiceControls.name.equals(businessServiceName))
+ .findFirst();
+
+
+ return businessServiceOptional.orElseThrow().id;
+
+ }
+
+ private String addTag(String tagName, String tagTypeName) throws NoSuchElementException
+ {
+ List tagList = tags.stream().filter(controlsTag -> controlsTag.name.equals(tagName)).collect(Collectors.toList());
+
+ Optional tagOptional = tagList.stream().filter(tagControls -> tagControls.tagType.name.equals(tagTypeName))
+ .findFirst();
+
+ return tagOptional.orElseThrow().id;
+ }
+}
diff --git a/src/main/java/io/tackle/applicationinventory/mapper/ApplicationMapper.java b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationMapper.java
new file mode 100644
index 0000000..897ffff
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/mapper/ApplicationMapper.java
@@ -0,0 +1,24 @@
+package io.tackle.applicationinventory.mapper;
+
+import io.tackle.applicationinventory.BusinessService;
+import io.tackle.applicationinventory.Tag;
+import io.tackle.applicationinventory.entities.ApplicationImport;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.core.Response;
+import java.util.Set;
+
+public abstract class ApplicationMapper {
+ @ApplicationScoped
+ Set tags;
+ @ApplicationScoped
+ Set businessServices;
+
+ public ApplicationMapper(Set tags, Set businessServices)
+ {
+ this.tags = tags;
+ this.businessServices = businessServices;
+ }
+
+ public abstract Response map(ApplicationImport importApp, Long parentId);
+}
diff --git a/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportListFilteredResource.java b/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportListFilteredResource.java
new file mode 100644
index 0000000..89f71cc
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportListFilteredResource.java
@@ -0,0 +1,49 @@
+package io.tackle.applicationinventory.resources;
+
+import io.tackle.applicationinventory.entities.ApplicationImport;
+import io.tackle.commons.resources.ListFilteredResource;
+import org.jboss.resteasy.links.LinkResource;
+
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.List;
+
+@Path("application-import")
+public class ApplicationImportListFilteredResource implements ListFilteredResource {
+
+ @Override
+ public Class getPanacheEntityType() {
+ return ApplicationImport.class;
+ }
+
+ @GET
+ @Path("")
+ @Produces({"application/json"})
+ @LinkResource(
+ entityClassName = "io.tackle.applicationinventory.entities.ApplicationImport",
+ rel = "list"
+ )
+ public Response list(@QueryParam(QUERY_PARAM_SORT) @DefaultValue(DEFAULT_VALUE_SORT) List var1,
+ @QueryParam(QUERY_PARAM_PAGE) @DefaultValue(DEFAULT_VALUE_PAGE) int var2,
+ @QueryParam(QUERY_PARAM_SIZE) @DefaultValue(DEFAULT_VALUE_SIZE) int var3,
+ @Context UriInfo var4) throws Exception {
+ return ListFilteredResource.super.list(var1, var2, var3, var4, false);
+ }
+
+ @Path("")
+ @GET
+ @Produces({"application/hal+json"})
+ public Response listHal(@QueryParam(QUERY_PARAM_SORT) @DefaultValue(DEFAULT_VALUE_SORT) List var1,
+ @QueryParam(QUERY_PARAM_PAGE) @DefaultValue(DEFAULT_VALUE_PAGE) int var2,
+ @QueryParam(QUERY_PARAM_SIZE) @DefaultValue(DEFAULT_VALUE_SIZE) int var3,
+ @Context UriInfo var4) throws Exception {
+ return ListFilteredResource.super.list(var1, var2, var3, var4, true);
+ }
+}
+
diff --git a/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportResource.java b/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportResource.java
new file mode 100644
index 0000000..02571ba
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/resources/ApplicationImportResource.java
@@ -0,0 +1,16 @@
+package io.tackle.applicationinventory.resources;
+
+import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;
+import io.quarkus.panache.common.Page;
+import io.quarkus.panache.common.Sort;
+import io.quarkus.rest.data.panache.MethodProperties;
+import io.quarkus.rest.data.panache.ResourceProperties;
+import io.tackle.applicationinventory.entities.ApplicationImport;
+
+import java.util.List;
+
+@ResourceProperties(hal = true)
+public interface ApplicationImportResource extends PanacheEntityResource {
+ @MethodProperties(exposed = false)
+ List list(Page page, Sort sort);
+}
diff --git a/src/main/java/io/tackle/applicationinventory/resources/CsvExportResource.java b/src/main/java/io/tackle/applicationinventory/resources/CsvExportResource.java
new file mode 100644
index 0000000..2614609
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/resources/CsvExportResource.java
@@ -0,0 +1,23 @@
+package io.tackle.applicationinventory.resources;
+
+
+import io.tackle.applicationinventory.services.CsvExportService;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.ws.rs.*;
+
+@Path("csv-export")
+@ApplicationScoped
+public class CsvExportResource {
+ @Inject
+ CsvExportService csvExportService;
+
+
+ @Produces("text/csv")
+ @Consumes("application/json")
+ @GET
+ public String getCsvExportForImportSummaryId(@QueryParam("importSummaryId") Long id) {
+ return csvExportService.getCsvExportForImportSummaryId(id);
+ }
+}
diff --git a/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryListFilteredResource.java b/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryListFilteredResource.java
new file mode 100644
index 0000000..9bb1b4c
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryListFilteredResource.java
@@ -0,0 +1,45 @@
+package io.tackle.applicationinventory.resources;
+
+import io.tackle.applicationinventory.entities.ApplicationImport;
+import io.tackle.applicationinventory.entities.ImportSummary;
+import io.tackle.commons.resources.ListFilteredResource;
+import org.jboss.resteasy.links.LinkResource;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.List;
+
+@Path("import-summary")
+public class ImportSummaryListFilteredResource implements ListFilteredResource {
+
+ @Override
+ public Class getPanacheEntityType() {
+ return ImportSummary.class;
+ }
+
+ @GET
+ @Path("")
+ @Produces({"application/json"})
+ @LinkResource(
+ entityClassName = "io.tackle.applicationinventory.entities.ImportSummary",
+ rel = "list"
+ )
+ public Response list(@QueryParam(QUERY_PARAM_SORT) @DefaultValue(DEFAULT_VALUE_SORT) List var1,
+ @QueryParam(QUERY_PARAM_PAGE) @DefaultValue(DEFAULT_VALUE_PAGE) int var2,
+ @QueryParam(QUERY_PARAM_SIZE) @DefaultValue(DEFAULT_VALUE_SIZE) int var3,
+ @Context UriInfo var4) throws Exception {
+ return ListFilteredResource.super.list(var1, var2, var3, var4, false);
+ }
+
+ @Path("")
+ @GET
+ @Produces({"application/hal+json"})
+ public Response listHal(@QueryParam(QUERY_PARAM_SORT) @DefaultValue(DEFAULT_VALUE_SORT) List var1,
+ @QueryParam(QUERY_PARAM_PAGE) @DefaultValue(DEFAULT_VALUE_PAGE) int var2,
+ @QueryParam(QUERY_PARAM_SIZE) @DefaultValue(DEFAULT_VALUE_SIZE) int var3,
+ @Context UriInfo var4) throws Exception {
+ return ListFilteredResource.super.list(var1, var2, var3, var4, true);
+ }
+}
diff --git a/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryResource.java b/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryResource.java
new file mode 100644
index 0000000..637c20a
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/resources/ImportSummaryResource.java
@@ -0,0 +1,16 @@
+package io.tackle.applicationinventory.resources;
+
+import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;
+import io.quarkus.panache.common.Page;
+import io.quarkus.panache.common.Sort;
+import io.quarkus.rest.data.panache.MethodProperties;
+import io.quarkus.rest.data.panache.ResourceProperties;
+import io.tackle.applicationinventory.entities.ImportSummary;
+
+import java.util.List;
+
+@ResourceProperties(hal = true)
+public interface ImportSummaryResource extends PanacheEntityResource {
+ @MethodProperties(exposed = false)
+ List list(Page page, Sort sort);
+}
diff --git a/src/main/java/io/tackle/applicationinventory/services/BusinessServiceService.java b/src/main/java/io/tackle/applicationinventory/services/BusinessServiceService.java
new file mode 100644
index 0000000..6d18553
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/services/BusinessServiceService.java
@@ -0,0 +1,21 @@
+package io.tackle.applicationinventory.services;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.GET;
+
+import io.quarkus.oidc.token.propagation.AccessToken;
+import io.tackle.applicationinventory.BusinessService;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+import java.util.Set;
+
+
+
+@RegisterRestClient()
+@AccessToken
+@ApplicationScoped
+public interface BusinessServiceService {
+
+ @GET
+ Set getListOfBusinessServices();
+}
diff --git a/src/main/java/io/tackle/applicationinventory/services/CsvExportService.java b/src/main/java/io/tackle/applicationinventory/services/CsvExportService.java
new file mode 100644
index 0000000..ab4d479
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/services/CsvExportService.java
@@ -0,0 +1,33 @@
+package io.tackle.applicationinventory.services;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.dataformat.csv.CsvMapper;
+import com.fasterxml.jackson.dataformat.csv.CsvSchema;
+import io.tackle.applicationinventory.entities.ApplicationImport;
+import io.tackle.applicationinventory.entities.ApplicationImportForCsv;
+
+import javax.enterprise.context.ApplicationScoped;
+import java.util.List;
+
+@ApplicationScoped
+public class CsvExportService {
+
+ public String getCsvExportForImportSummaryId(Long importSummaryId)
+ {
+ List importList = ApplicationImport.list("importSummary_id=?1 and isValid=?2", importSummaryId, false);
+ final CsvMapper mapper = new CsvMapper();
+ mapper.disable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
+ final CsvSchema schema = mapper.schemaFor(ApplicationImportForCsv.class);
+ mapper.addMixIn(ApplicationImport.class, ApplicationImportForCsv.class);
+
+ String csv = null;
+ try {
+ csv = mapper.writer(schema.withUseHeader(true)).writeValueAsString(importList);
+ }catch(JsonProcessingException jpe){
+ jpe.printStackTrace();
+ throw new RuntimeException(jpe);
+ }
+ return csv;
+ }
+}
diff --git a/src/main/java/io/tackle/applicationinventory/services/ImportService.java b/src/main/java/io/tackle/applicationinventory/services/ImportService.java
new file mode 100644
index 0000000..b1815e4
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/services/ImportService.java
@@ -0,0 +1,176 @@
+package io.tackle.applicationinventory.services;
+
+import com.fasterxml.jackson.databind.MappingIterator;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.dataformat.csv.CsvMapper;
+import com.fasterxml.jackson.dataformat.csv.CsvSchema;
+import io.tackle.applicationinventory.BusinessService;
+import io.tackle.applicationinventory.MultipartImportBody;
+import io.tackle.applicationinventory.Tag;
+import io.tackle.applicationinventory.entities.ApplicationImport;
+import io.tackle.applicationinventory.entities.ImportSummary;
+import io.tackle.applicationinventory.mapper.ApplicationInventoryAPIMapper;
+import io.tackle.applicationinventory.mapper.ApplicationMapper;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.transaction.Transactional;
+import javax.transaction.UserTransaction;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.*;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static javax.transaction.Transactional.TxType.REQUIRED;
+
+@Path("/file")
+@ApplicationScoped
+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";
+
+ @Inject
+ EntityManager entityManager;
+
+ @Inject
+ UserTransaction usrTransaction;
+
+ @Inject
+ @RestClient
+ TagService tagService;
+
+ @Inject
+ @RestClient
+ BusinessServiceService businessServiceService;
+
+ @POST
+ @Path("/upload")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ @Transactional(REQUIRED)
+ public Response importFile(@MultipartForm MultipartImportBody data) {
+ ImportSummary parentRecord = new ImportSummary();
+
+ try {
+ parentRecord.filename = data.getFileName();
+ parentRecord.importStatus = IN_PROGRESS_STATUS;
+ parentRecord.persistAndFlush();
+ Set tags = tagService.getListOfTags();
+ if (tags == null)
+ {
+ String msg = "Unable to retrieve TagTypes from remote resource";
+ parentRecord.errorMessage = msg;
+ throw new Exception(msg);
+ }
+ Set businessServices =businessServiceService.getListOfBusinessServices();
+ if (businessServices == null)
+ {
+ String msg = "Unable to retrieve BusinessServices from remote resource";
+ parentRecord.errorMessage = msg;
+ throw new Exception(msg);
+ }
+
+ 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);
+ });
+
+ });
+ }
+ mapImportsToApplication(importListMinusDuplicates, tags, businessServices, parentRecord);
+ parentRecord.importStatus = COMPLETED_STATUS;
+ parentRecord.flush();
+
+ } catch (Exception e) {
+
+ e.printStackTrace();
+ parentRecord.importStatus = FAILED_STATUS;
+ parentRecord.flush();
+
+ }
+ return Response.ok().build();
+
+
+
+ }
+
+
+ private List writeFile(String content, String filename, ImportSummary parentObject) throws IOException {
+
+ MappingIterator iter = decode(content);
+ List importList = new ArrayList();
+ while (iter.hasNext())
+ {
+ ApplicationImport importedApplication = iter.next();
+ importedApplication.setFilename(filename);
+ importedApplication.importSummary = parentObject;
+ importList.add(importedApplication);
+ importedApplication.persistAndFlush();
+ }
+ return importList;
+ }
+
+
+
+ private MappingIterator decode(String inputFileContent) throws IOException{
+ CsvMapper mapper = new CsvMapper();
+
+ CsvSchema csvSchema = CsvSchema.emptySchema().withHeader();
+ String columnSeparator = ",";
+
+ csvSchema = csvSchema.withColumnSeparator(columnSeparator.charAt(0))
+ .withLineSeparator("\r\n")
+ .withUseHeader(true);
+
+ ObjectReader reader = mapper.readerFor(ApplicationImport.class)
+ .with(csvSchema);
+
+ return reader.readValues(inputFileContent);
+
+ }
+
+ public void mapImportsToApplication(List importList, Set tags, Set businessServices, ImportSummary parentRecord)
+ {
+ ApplicationMapper mapper = new ApplicationInventoryAPIMapper(tags, businessServices);
+ 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);
+ importFile.flush();
+ }
+
+
+
+
+}
diff --git a/src/main/java/io/tackle/applicationinventory/services/TagService.java b/src/main/java/io/tackle/applicationinventory/services/TagService.java
new file mode 100644
index 0000000..c91f3fd
--- /dev/null
+++ b/src/main/java/io/tackle/applicationinventory/services/TagService.java
@@ -0,0 +1,23 @@
+package io.tackle.applicationinventory.services;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.GET;
+
+import io.tackle.applicationinventory.Tag;
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import io.quarkus.oidc.token.propagation.AccessToken;
+
+import java.util.Set;
+
+
+
+@RegisterRestClient()
+@AccessToken
+@ApplicationScoped
+public interface TagService {
+
+ @GET
+ Set getListOfTags();
+}
+
+
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 171c45c..59385b7 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -81,3 +81,6 @@ quarkus.openshift.part-of=${quarkus.kubernetes.part-of}
#quarkus.openshift.expose=${quarkus.kubernetes.expose}
quarkus.kubernetes.labels."app.kubernetes.io/component"=rest
quarkus.openshift.labels."app.kubernetes.io/component"=rest
+
+io.tackle.applicationinventory.services.TagService/mp-rest/uri=http://tackle-controls:8080/controls/tag?page=0&size=1000
+io.tackle.applicationinventory.services.BusinessServiceService/mp-rest/uri=http://tackle-controls:8080/controls/business-service?page=0&size=1000
diff --git a/src/main/resources/db/migration/V20210610.1__create_application_import.sql b/src/main/resources/db/migration/V20210610.1__create_application_import.sql
new file mode 100644
index 0000000..7614f5b
--- /dev/null
+++ b/src/main/resources/db/migration/V20210610.1__create_application_import.sql
@@ -0,0 +1,24 @@
+create table application_import (
+ id int8 not null,
+ createTime timestamp,
+ createUser varchar(255),
+ deleted boolean,
+ updateTime timestamp,
+ updateUser varchar(255),
+ businessService varchar(255),
+ description varchar(255),
+ applicationname varchar(255),
+ comments varchar(255),
+ recordType1 varchar(255),
+ tag1 varchar(255),
+ tagType1 varchar(255),
+ tag2 varchar(255),
+ tagType2 varchar(255),
+ tag3 varchar(255),
+ tagType3 varchar(255),
+ tag4 varchar(255),
+ tagType4 varchar(255),
+ errorMessage varchar(255),
+ isValid boolean,
+ primary key (id)
+)
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V20210610.2__alter_application_import.sql b/src/main/resources/db/migration/V20210610.2__alter_application_import.sql
new file mode 100644
index 0000000..229b32c
--- /dev/null
+++ b/src/main/resources/db/migration/V20210610.2__alter_application_import.sql
@@ -0,0 +1,2 @@
+alter table if exists application_import
+ add column filename varchar (255);
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V20210615.1__alter_application_import.sql b/src/main/resources/db/migration/V20210615.1__alter_application_import.sql
new file mode 100644
index 0000000..a950535
--- /dev/null
+++ b/src/main/resources/db/migration/V20210615.1__alter_application_import.sql
@@ -0,0 +1,33 @@
+alter table if exists application_import
+ add column tagType5 varchar (255),
+ add column tag5 varchar (255),
+ add column tagType6 varchar (255),
+ add column tag6 varchar (255),
+ add column tagType7 varchar (255),
+ add column tag7 varchar (255),
+ add column tagType8 varchar (255),
+ add column tag8 varchar (255),
+ add column tagType9 varchar (255),
+ add column tag9 varchar (255),
+ add column tagType10 varchar (255),
+ add column tag10 varchar (255),
+ add column tagType11 varchar (255),
+ add column tag11 varchar (255),
+ add column tagType12 varchar (255),
+ add column tag12 varchar (255),
+ add column tagType13 varchar (255),
+ add column tag13 varchar (255),
+ add column tagType14 varchar (255),
+ add column tag14 varchar (255),
+ add column tagType15 varchar (255),
+ add column tag15 varchar (255),
+ add column tagType16 varchar (255),
+ add column tag16 varchar (255),
+ add column tagType17 varchar (255),
+ add column tag17 varchar (255),
+ add column tagType18 varchar (255),
+ add column tag18 varchar (255),
+ add column tagType19 varchar (255),
+ add column tag19 varchar (255),
+ add column tagType20 varchar (255),
+ add column tag20 varchar (255);
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V20210618.1__alter_application_import.sql b/src/main/resources/db/migration/V20210618.1__alter_application_import.sql
new file mode 100644
index 0000000..2c8a087
--- /dev/null
+++ b/src/main/resources/db/migration/V20210618.1__alter_application_import.sql
@@ -0,0 +1,3 @@
+alter table if exists application_import
+ add column status varchar (255),
+ add column parentid int8;
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V20210621.1__alter_application_import.sql b/src/main/resources/db/migration/V20210621.1__alter_application_import.sql
new file mode 100644
index 0000000..10a3eae
--- /dev/null
+++ b/src/main/resources/db/migration/V20210621.1__alter_application_import.sql
@@ -0,0 +1,3 @@
+alter table if exists application_import
+ drop column parentid,
+ add column importSummary_id int8;
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V20210621.2__create_import_summary.sql b/src/main/resources/db/migration/V20210621.2__create_import_summary.sql
new file mode 100644
index 0000000..ccc54e0
--- /dev/null
+++ b/src/main/resources/db/migration/V20210621.2__create_import_summary.sql
@@ -0,0 +1,12 @@
+create table import_summary (
+ id int8 not null,
+ createTime timestamp,
+ createUser varchar(255),
+ deleted boolean,
+ updateTime timestamp,
+ updateUser varchar(255),
+ importStatus varchar(255),
+ filename varchar(255),
+ errorMessage varchar(255),
+ primary key (id)
+)
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V20210621.3__alter_application_import.sql b/src/main/resources/db/migration/V20210621.3__alter_application_import.sql
new file mode 100644
index 0000000..bbc0164
--- /dev/null
+++ b/src/main/resources/db/migration/V20210621.3__alter_application_import.sql
@@ -0,0 +1,6 @@
+alter table if exists application_import
+ drop column importSummary_id,
+ add column importSummary_id int8 not null,
+ add constraint FKp755h0bv2vgcmrsj1p4ebqjn6
+ foreign key (importSummary_id)
+ references import_summary;
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V20210622.1__alter_import_summary.sql b/src/main/resources/db/migration/V20210622.1__alter_import_summary.sql
new file mode 100644
index 0000000..e61514c
--- /dev/null
+++ b/src/main/resources/db/migration/V20210622.1__alter_import_summary.sql
@@ -0,0 +1,2 @@
+alter table if exists import_summary
+ add column importTime time default current_time ;
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V20210622.2__alter_import_summary.sql b/src/main/resources/db/migration/V20210622.2__alter_import_summary.sql
new file mode 100644
index 0000000..32c5cf3
--- /dev/null
+++ b/src/main/resources/db/migration/V20210622.2__alter_import_summary.sql
@@ -0,0 +1,3 @@
+alter table if exists import_summary
+ drop column importtime,
+ add column importTime timestamp default current_timestamp;
\ No newline at end of file
diff --git a/src/test/java/io/tackle/applicationimporter/ImportServiceTest.java b/src/test/java/io/tackle/applicationimporter/ImportServiceTest.java
new file mode 100644
index 0000000..bb2b673
--- /dev/null
+++ b/src/test/java/io/tackle/applicationimporter/ImportServiceTest.java
@@ -0,0 +1,687 @@
+package io.tackle.applicationimporter;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.common.ResourceArg;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.mockito.InjectMock;
+import io.restassured.RestAssured;
+import io.restassured.config.EncoderConfig;
+import io.restassured.http.ContentType;
+import io.restassured.response.Response;
+import io.tackle.applicationinventory.BusinessService;
+import io.tackle.applicationinventory.MultipartImportBody;
+import io.tackle.applicationinventory.Tag;
+import io.tackle.applicationinventory.entities.Application;
+import io.tackle.applicationinventory.entities.ApplicationImport;
+import io.tackle.applicationinventory.entities.ImportSummary;
+import io.tackle.applicationinventory.services.BusinessServiceService;
+import io.tackle.applicationinventory.services.ImportService;
+import io.tackle.applicationinventory.services.TagService;
+import io.tackle.commons.testcontainers.KeycloakTestResource;
+import io.tackle.commons.testcontainers.PostgreSQLDatabaseTestResource;
+import io.tackle.commons.tests.SecuredResourceTest;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.junit.jupiter.api.*;
+import org.mockito.Mockito;
+
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.transaction.*;
+import javax.ws.rs.core.MediaType;
+import java.io.File;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.containsInRelativeOrder;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@QuarkusTest
+@QuarkusTestResource(value = PostgreSQLDatabaseTestResource.class,
+ initArgs = {
+ @ResourceArg(name = PostgreSQLDatabaseTestResource.DB_NAME, value = "application_inventory_db"),
+ @ResourceArg(name = PostgreSQLDatabaseTestResource.USER, value = "application_inventory"),
+ @ResourceArg(name = PostgreSQLDatabaseTestResource.PASSWORD, value = "application_inventory")
+ }
+)
+@QuarkusTestResource(value = KeycloakTestResource.class,
+ initArgs = {
+ @ResourceArg(name = KeycloakTestResource.IMPORT_REALM_JSON_PATH, value = "keycloak/quarkus-realm.json"),
+ @ResourceArg(name = KeycloakTestResource.REALM_NAME, value = "quarkus")
+ }
+)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class ImportServiceTest extends SecuredResourceTest {
+ @Inject
+ EntityManager entityManager;
+
+ @Inject
+ UserTransaction userTransaction;
+
+ @InjectMock
+ @RestClient
+ TagService mockTagService;
+
+ @InjectMock
+ @RestClient
+ BusinessServiceService mockBusinessServiceService;
+
+ @BeforeAll
+ public static void init() {
+ PATH = "/file/upload";
+ }
+
+
+ @Test
+ @Order(1)
+ protected void testImportServicePost() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException {
+
+ userTransaction.begin();
+ Set tags = new HashSet<>() ;
+ Tag.TagType tagType1 = new Tag.TagType();
+ tagType1.id = "1";
+ tagType1.name = "Operating System";
+ Tag tag = new Tag();
+ tag.id = "1";
+ tag.name = "RHEL 8";
+ tag.tagType = tagType1;
+ tags.add(tag);
+ Tag.TagType tagType2 = new Tag.TagType();
+ tagType2.id = "2";
+ tagType2.name = "Database";
+ Tag tag1 = new Tag();
+ tag1.id = "2";
+ tag1.name = "Oracle";
+ tag1.tagType = tagType2;
+ tags.add(tag1);
+ Tag.TagType tagType3 = new Tag.TagType();
+ tagType3.id = "3";
+ tagType3.name = "Language";
+ Tag tag2 = new Tag();
+ tag2.id = "3";
+ tag2.name = "Java EE";
+ tag2.tagType = tagType3;
+ tags.add(tag2);
+ Tag.TagType tagType4 = new Tag.TagType();
+ tagType4.id = "4";
+ tagType4.name = "Runtime";
+ Tag tag3 = new Tag();
+ tag3.id = "3";
+ tag3.name = "Tomcat";
+ tag3.tagType = tagType4;
+ tags.add(tag3);
+ Mockito.when(mockTagService.getListOfTags()).thenReturn(tags);
+
+
+ Set businessServices = new HashSet<>() ;
+ BusinessService businessService = new BusinessService();
+ businessService.id = "1";
+ businessService.name = "Food2Go";
+ businessServices.add(businessService);
+ Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(businessServices);
+
+ ClassLoader classLoader = getClass().getClassLoader();
+ File importFile = new File(classLoader.getResource("sample_application_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","sample_application_import.csv")
+ .when().post(PATH)
+ .then()
+ .log().all()
+ .statusCode(200).extract().response();
+
+ assertEquals(200, response.getStatusCode());
+ //check the correct number of application imports have been persisted
+ assertEquals(7, ApplicationImport.listAll().size());
+ userTransaction.commit();
+
+ given()
+ .accept("application/hal+json")
+ .queryParam("isValid", Boolean.TRUE)
+ .when()
+ .get("/application-import")
+ .then()
+ .statusCode(200)
+ .log().body()
+ .body("_embedded.'application-import'.size()", is(1));
+
+ userTransaction.begin();
+
+ Response response2 = 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","sample_application_import.csv")
+ .when().post(PATH)
+ .then()
+ .log().all()
+ .statusCode(200).extract().response();
+
+ assertEquals(200, response2.getStatusCode());
+ //check the correct number of application imports have been persisted
+ assertEquals(14, ApplicationImport.listAll().size());
+ userTransaction.commit();
+
+ given()
+ .accept("application/hal+json")
+ .queryParam("isValid", Boolean.TRUE)
+ .when()
+ .get("/application-import")
+ .then()
+ .statusCode(200)
+ .log().body()
+ .body("_embedded.'application-import'.size()", is(1));
+
+ ApplicationImport successful = ApplicationImport.find("isValid",true).firstResult();
+ Application newOne = Application.find("name",successful.getApplicationName()).firstResult();
+
+ userTransaction.begin();
+ ApplicationImport.deleteAll();
+ ImportSummary.deleteAll();
+ Application.deleteById(newOne.id);
+ userTransaction.commit();
+
+ }
+
+ @Test
+ @Order(2)
+ protected void testMapToApplicationRejected() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException {
+ userTransaction.begin();
+ ImportService svc = new ImportService();
+ ImportSummary appImportParent = new ImportSummary();
+ appImportParent.persistAndFlush();
+
+ ApplicationImport appImport1 = new ApplicationImport();
+ appImport1.setBusinessService("BS 1");
+ appImport1.importSummary = appImportParent;
+ appImport1.setDescription("hello");
+ appImport1.persistAndFlush();
+ ApplicationImport appImport2 = new ApplicationImport();
+ appImport2.setBusinessService("BS 2");
+ appImport2.importSummary = appImportParent;
+ appImport2.setDescription("this");
+ appImport2.setTag5("tag 1");
+ appImport2.setTagType5("tag type 1");
+ appImport2.setTag6("tag 1");
+ appImport2.setTagType6("tag type 1");
+ appImport2.setTag7("tag 1");
+ appImport2.setTagType7("tag type 1");
+ appImport2.setTag8("tag 1");
+ appImport2.setTagType8("tag type 1");
+ appImport2.setTag9("tag 1");
+ appImport2.setTagType9("tag type 1");
+ appImport2.setTag10("tag 1");
+ appImport2.setTagType10("tag type 1");
+ appImport2.setTag11("tag 1");
+ appImport2.setTagType11("tag type 1");
+ appImport2.setTag12("tag 1");
+ appImport2.setTagType12("tag type 1");
+ appImport2.setTag13("tag 1");
+ appImport2.setTagType13("tag type 1");
+ appImport2.setTag14("tag 1");
+ appImport2.setTagType14("tag type 1");
+ appImport2.setTag15("tag 1");
+ appImport2.setTagType15("tag type 1");
+ appImport2.setTag16("tag 1");
+ appImport2.setTagType16("tag type 1");
+ appImport2.setTag17("tag 1");
+ appImport2.setTagType17("tag type 1");
+ appImport2.setTag18("tag 1");
+ appImport2.setTagType18("tag type 1");
+ appImport2.setTag19("tag 1");
+ appImport2.setTagType19("tag type 1");
+ appImport2.setTag20("tag 1");
+ appImport2.setTagType20("tag type 1");
+ appImport2.persistAndFlush();
+ ApplicationImport appImport3 = new ApplicationImport();
+ appImport3.setBusinessService("BS 2");
+ appImport3.importSummary = appImportParent;
+ appImport3.setDescription("and this");
+ appImport3.setTag1("");
+ appImport3.setTag2("");
+ appImport3.setTagType2("");
+ appImport3.setTag3("mystery tag");
+ appImport3.setTagType3("");
+ appImport3.setTag4("");
+ appImport3.setTagType4("");
+ appImport3.persistAndFlush();
+ ApplicationImport appImport4 = new ApplicationImport();
+ appImport4.setBusinessService("BS 2");
+ appImport4.importSummary = appImportParent;
+ appImport4.setDescription("and this");
+ appImport4.setTagType1("");
+ appImport4.setTagType2("mystery tag type");
+ appImport4.persistAndFlush();
+ ApplicationImport appImport5 = new ApplicationImport();
+ appImport5.setBusinessService("BS 2");
+ appImport5.importSummary = appImportParent;
+ appImport5.setDescription("and this");
+ appImport5.setTag1("yes");
+ appImport5.persistAndFlush();
+
+ List appList = new ArrayList();
+
+
+ appList.add(appImport1);
+ appList.add(appImport2);
+ appList.add(appImport3);
+ appList.add(appImport4);
+ appList.add(appImport5);
+
+
+ Long id1 = appImport1.id;
+ Long id2 = appImport2.id;
+ Long id3 = appImport3.id;
+ Long id4 = appImport4.id;
+ Long id5 = appImport5.id;
+
+ Set tags = new HashSet<>() ;
+ Tag.TagType tagType1 = new Tag.TagType();
+ tagType1.id = "1";
+ tagType1.name = "Unknown tag type";
+ Tag tag = new Tag();
+ tag.id = "1";
+ tag.name = "Unknown OS";
+ tag.tagType = tagType1;
+ tags.add(tag);
+
+ Set businessServices = new HashSet<>() ;
+ BusinessService businessService = new BusinessService();
+ businessService.id = "1";
+ businessService.name = "BS 2";
+ businessServices.add(businessService);
+ svc.mapImportsToApplication(appList, tags, businessServices, appImportParent);
+
+
+ userTransaction.commit();
+
+ Long summaryId = appImportParent.id;
+
+
+
+ ApplicationImport refusedImport = ApplicationImport.findById(id1);
+ assertEquals(Boolean.FALSE, refusedImport.getValid());
+ assertEquals(summaryId, refusedImport.importSummary.id);
+ ApplicationImport refusedImport2 = ApplicationImport.findById(id2);
+ assertEquals(Boolean.FALSE, refusedImport2.getValid());
+ assertEquals(summaryId, refusedImport2.importSummary.id);
+ ApplicationImport refusedImport3 = ApplicationImport.findById(id3);
+ assertEquals(Boolean.FALSE, refusedImport3.getValid());
+ assertEquals(summaryId, refusedImport3.importSummary.id);
+ ApplicationImport refusedImport4 = ApplicationImport.findById(id4);
+ assertEquals(Boolean.FALSE, refusedImport4.getValid());
+ assertEquals(summaryId, refusedImport4.importSummary.id);
+ ApplicationImport refusedImport5 = ApplicationImport.findById(id5);
+ assertEquals(Boolean.FALSE, refusedImport5.getValid());
+ assertEquals(summaryId, refusedImport5.importSummary.id);
+
+ given()
+ .accept("application/hal+json")
+ .when()
+ .get("/import-summary")
+ .then()
+ .statusCode(200)
+ .body("_embedded.import-summary.size()", is(1),
+ "_embedded.import-summary.invalidCount", containsInRelativeOrder(5),
+ "total_count", is(1));
+
+
+ userTransaction.begin();
+ ApplicationImport.deleteAll();
+ ImportSummary.deleteAll();
+ userTransaction.commit();
+
+ }
+
+ @Test
+ @Order(2)
+ protected void testMultipartImport() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException {
+ userTransaction.begin();
+ ImportService svc = new ImportService();
+ MultipartImportBody multipartImport = new MultipartImportBody();
+ ClassLoader classLoader = getClass().getClassLoader();
+ File importFile = new File(classLoader.getResource("sample_application_import.csv").getFile());
+ multipartImport.setFilename("testImport");
+ multipartImport.setFile(importFile.toString());
+
+ javax.ws.rs.core.Response response = svc.importFile(multipartImport);
+ assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(),response.getStatus());
+
+
+
+
+
+ userTransaction.commit();
+
+
+
+ userTransaction.begin();
+ ApplicationImport.deleteAll();
+ ImportSummary.deleteAll();
+ userTransaction.commit();
+
+ }
+
+ @Test
+ @Order(2)
+ protected void testMapToApplicationMissingFields() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException {
+ userTransaction.begin();
+ ImportService svc = new ImportService();
+
+ ImportSummary appImportParent = new ImportSummary();
+ appImportParent.persistAndFlush();
+ Long parentId = appImportParent.id;
+
+ ApplicationImport appImport1 = new ApplicationImport();
+ appImport1.setApplicationName("Test App 1");
+ appImport1.importSummary = appImportParent;
+ appImport1.persistAndFlush();
+ ApplicationImport appImport2 = new ApplicationImport();
+ appImport2.setApplicationName("Test App 2");
+ appImport2.importSummary = appImportParent;
+ appImport2.setBusinessService((""));
+ appImport2.persistAndFlush();
+ ApplicationImport appImport3= new ApplicationImport();
+ appImport3.setApplicationName("Test App 3");
+ appImport3.importSummary = appImportParent;
+ appImport3.setBusinessService(("BS 2"));
+ appImport3.persistAndFlush();
+ ApplicationImport appImport4= new ApplicationImport();
+ appImport4.setApplicationName("Test App 4");
+ appImport4.importSummary = appImportParent;
+ appImport4.setBusinessService(("BS 2"));
+ appImport4.setDescription("");
+ appImport4.persistAndFlush();
+
+
+ List appList = new ArrayList();
+
+
+ appList.add(appImport1);
+ appList.add(appImport2);
+ appList.add(appImport3);
+ appList.add(appImport4);
+
+
+ Long id = appImport1.id;
+ Long id2 = appImport2.id;
+ Long id3 = appImport3.id;
+ Long id4 = appImport4.id;
+
+ Set tags = new HashSet<>();
+ Set businessServices = new HashSet<>() ;
+ BusinessService businessService = new BusinessService();
+ businessService.id = "1";
+ businessService.name = "BS 2";
+ businessServices.add(businessService);
+ svc.mapImportsToApplication(appList, tags, businessServices, appImportParent);
+
+
+ userTransaction.commit();
+
+ ApplicationImport refusedImport1 = ApplicationImport.findById(id);
+ assertEquals(Boolean.FALSE, refusedImport1.getValid());
+ assertEquals("Business Service is Mandatory",refusedImport1.getErrorMessage());
+
+ ApplicationImport refusedImport2 = ApplicationImport.findById(id2);
+ assertEquals(Boolean.FALSE, refusedImport2.getValid());
+ assertEquals("Business Service is Mandatory",refusedImport2.getErrorMessage());
+
+ ApplicationImport refusedImport3 = ApplicationImport.findById(id3);
+ assertEquals(Boolean.FALSE, refusedImport3.getValid());
+ assertEquals("Description is Mandatory",refusedImport3.getErrorMessage());
+
+ ApplicationImport refusedImport4 = ApplicationImport.findById(id4);
+ assertEquals(Boolean.FALSE, refusedImport4.getValid());
+ assertEquals("Description is Mandatory",refusedImport4.getErrorMessage());
+
+ userTransaction.begin();
+ ApplicationImport.deleteAll();
+ ImportSummary.deleteAll();
+ userTransaction.commit();
+
+ }
+
+ @Test
+ @Order(3)
+ protected void testImportServiceNoMatchingTag() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException {
+
+
+ Set tags = new HashSet<>() ;
+ Tag.TagType tagType1 = new Tag.TagType();
+ tagType1.id = "1";
+ tagType1.name = "Unknown tag type";
+ Tag tag = new Tag();
+ tag.id = "1";
+ tag.name = "Unknown OS";
+ tag.tagType = tagType1;
+ tags.add(tag);
+ Mockito.when(mockTagService.getListOfTags()).thenReturn(tags);
+
+
+ Set businessServices = new HashSet<>() ;
+ BusinessService businessService = new BusinessService();
+ businessService.id = "1";
+ businessService.name = "Foot2Go";
+ businessServices.add(businessService);
+
+ BusinessService businessService2 = new BusinessService();
+ businessService2.id = "2";
+ businessService2.name = "Food2Go";
+ businessServices.add(businessService2);
+ Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(businessServices);
+
+ ClassLoader classLoader = getClass().getClassLoader();
+ File importFile = new File(classLoader.getResource("sample_application_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","sample_application_import.csv")
+ .when().post(PATH)
+ .then()
+ .log().all()
+ .statusCode(200).extract().response();
+
+ assertEquals(200, response.getStatusCode());
+
+ userTransaction.begin();
+ ApplicationImport.deleteAll();
+ ImportSummary.deleteAll();
+ userTransaction.commit();
+ }
+
+ @Test
+ @Order(4)
+ protected void testImportServiceDuplicatesInFile() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException {
+
+ Set tags = new HashSet<>() ;
+ Tag.TagType tagType1 = new Tag.TagType();
+ tagType1.id = "1";
+ tagType1.name = "Operating System";
+ Tag tag = new Tag();
+ tag.id = "1";
+ tag.name = "RHEL";
+ tag.tagType = tagType1;
+ tags.add(tag);
+ Mockito.when(mockTagService.getListOfTags()).thenReturn(tags);
+
+
+ Set businessServices = new HashSet<>() ;
+ BusinessService businessService = new BusinessService();
+ businessService.id = "1";
+ businessService.name = "Food2Go";
+ businessServices.add(businessService);
+ Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(businessServices);
+
+ ClassLoader classLoader = getClass().getClassLoader();
+ File importFile = new File(classLoader.getResource("duplicate_application_names.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","duplicate_application_names.csv")
+ .when().post(PATH)
+ .then()
+ .log().all()
+ .statusCode(200).extract().response();
+
+ assertEquals(200, response.getStatusCode());
+
+ given()
+ .accept("application/hal+json")
+ .queryParam("isValid", Boolean.FALSE)
+ .when()
+ .get("/application-import")
+ .then()
+ .statusCode(200)
+ .log().body()
+ .body("_embedded.'application-import'[0].'errorMessage'", is("Duplicate Application Name within file: OrderHub"));
+
+ given()
+ .accept("application/hal+json")
+ .when()
+ .get("/import-summary")
+ .then()
+ .statusCode(200)
+ .log().body()
+ .body("_embedded.'import-summary'[0].'importStatus'", is("Completed"));
+
+ given()
+ .accept("application/json")
+ .when()
+ .get("/import-summary")
+ .then()
+ .statusCode(200)
+ .log().body()
+ .body("size()", is(1));
+
+ ImportSummary summary = ImportSummary.findAll().firstResult();
+
+ Response r =
+ given()
+ .accept("text/csv")
+ .when()
+ .get("/csv-export?importSummaryId=" + summary.id);
+ /** .then()
+ .statusCode(200)
+ .log().body();
+ .body("", is("Completed"));*/
+
+
+ String csv = r.body().print();
+ String[] csvFields = csv.split(",");
+ List found = Arrays.stream(csvFields).filter("Comments"::equals).collect(Collectors.toList());
+ assertEquals(1,found.size());
+
+
+ userTransaction.begin();
+ ApplicationImport.deleteAll();
+ ImportSummary.deleteAll();
+ userTransaction.commit();
+
+ }
+
+ @Test
+ @Order(5)
+ protected void testImportServiceNoTagsRetrieved() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException {
+
+
+ Mockito.when(mockTagService.getListOfTags()).thenReturn(null);
+
+
+ Set businessServices = new HashSet<>() ;
+ BusinessService businessService = new BusinessService();
+ businessService.id = "1";
+ businessService.name = "Food2Go";
+ businessServices.add(businessService);
+ Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(businessServices);
+
+ ClassLoader classLoader = getClass().getClassLoader();
+ File importFile = new File(classLoader.getResource("duplicate_application_names.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","sample_application_import.csv")
+ .when().post(PATH)
+ .then()
+ .log().all()
+ .statusCode(200).extract().response();
+
+ assertEquals(200, response.getStatusCode());
+
+
+
+ userTransaction.begin();
+ ApplicationImport.deleteAll();
+ ImportSummary.deleteAll();
+ userTransaction.commit();
+
+ }
+
+ @Test
+ @Order(5)
+ protected void testImportServiceNoBSRetrieved() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException {
+
+
+ Mockito.when(mockTagService.getListOfTags()).thenReturn(null);
+
+
+ Set tags = new HashSet<>() ;
+ Tag.TagType tagType1 = new Tag.TagType();
+ tagType1.id = "1";
+ tagType1.name = "Operating System";
+ Tag tag = new Tag();
+ tag.id = "1";
+ tag.name = "RHEL";
+ tag.tagType = tagType1;
+ tags.add(tag);
+ Mockito.when(mockTagService.getListOfTags()).thenReturn(tags);
+ Mockito.when(mockBusinessServiceService.getListOfBusinessServices()).thenReturn(null);
+
+ ClassLoader classLoader = getClass().getClassLoader();
+ File importFile = new File(classLoader.getResource("duplicate_application_names.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","sample_application_import.csv")
+ .when().post(PATH)
+ .then()
+ .log().all()
+ .statusCode(200).extract().response();
+
+ assertEquals(200, response.getStatusCode());
+
+
+
+ userTransaction.begin();
+ ApplicationImport.deleteAll();
+ ImportSummary.deleteAll();
+ userTransaction.commit();
+
+ }
+
+
+}
+
diff --git a/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java b/src/test/java/io/tackle/applicationinventory/flyway/FlywayMigrationTest.java
index b4dccb0..0aa31a0 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(7, flyway.info().applied().length);
+ assertEquals(16, flyway.info().applied().length);
// check the current migration version is the one from the last file in resources/db/migration folder
- assertEquals("20210603", flyway.info().current().getVersion().toString());
+ assertEquals("20210622.2", 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/ApplicationImportTest.java b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportTest.java
new file mode 100644
index 0000000..cfcb25d
--- /dev/null
+++ b/src/test/java/io/tackle/applicationinventory/resources/ApplicationImportTest.java
@@ -0,0 +1,109 @@
+package io.tackle.applicationinventory.resources;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.common.ResourceArg;
+import io.quarkus.test.junit.QuarkusTest;
+import io.tackle.applicationinventory.entities.ApplicationImport;
+import io.tackle.applicationinventory.entities.ImportSummary;
+import io.tackle.commons.testcontainers.KeycloakTestResource;
+import io.tackle.commons.testcontainers.PostgreSQLDatabaseTestResource;
+import io.tackle.commons.tests.SecuredResourceTest;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.transaction.*;
+
+import static io.restassured.RestAssured.given;
+import static javax.transaction.Transactional.TxType.REQUIRED;
+import static org.hamcrest.Matchers.is;
+
+@QuarkusTest
+@QuarkusTestResource(value = PostgreSQLDatabaseTestResource.class,
+ initArgs = {
+ @ResourceArg(name = PostgreSQLDatabaseTestResource.DB_NAME, value = "application_inventory_db"),
+ @ResourceArg(name = PostgreSQLDatabaseTestResource.USER, value = "application_inventory"),
+ @ResourceArg(name = PostgreSQLDatabaseTestResource.PASSWORD, value = "application_inventory")
+ }
+)
+@QuarkusTestResource(value = KeycloakTestResource.class,
+ initArgs = {
+ @ResourceArg(name = KeycloakTestResource.IMPORT_REALM_JSON_PATH, value = "keycloak/quarkus-realm.json"),
+ @ResourceArg(name = KeycloakTestResource.REALM_NAME, value = "quarkus")
+ }
+)
+public class ApplicationImportTest extends SecuredResourceTest {
+ @Inject
+ EntityManager entityManager;
+
+ @Inject
+ UserTransaction userTransaction;
+
+
+ @BeforeAll
+ public static void init() {
+
+ PATH = "/application-import";
+
+ }
+
+ @Test
+ public void testFilterByIsValid() throws HeuristicRollbackException, SystemException, HeuristicMixedException, RollbackException, NotSupportedException {
+
+ userTransaction.begin();
+
+ ImportSummary appImportParent = new ImportSummary();
+ appImportParent.persistAndFlush();
+
+ ApplicationImport appImport1 = new ApplicationImport();
+ appImport1.setBusinessService("BS 1");
+ appImport1.importSummary = appImportParent;
+ appImport1.setFilename("File1");
+ appImport1.persistAndFlush();
+ ApplicationImport appImport2 = new ApplicationImport();
+ appImport2.setBusinessService("BS 2");
+ appImport2.importSummary = appImportParent;
+ appImport2.setFilename("File1");
+ appImport2.setTag1("tag 1");
+ appImport2.setTagType1("tag type 1");
+ appImport2.setValid(Boolean.FALSE);
+ appImport2.persistAndFlush();
+ ApplicationImport appImport3 = new ApplicationImport();
+ appImport3.setBusinessService("BS 3");
+ appImport3.importSummary = appImportParent;
+ appImport3.setFilename("File2");
+ appImport3.setValid(Boolean.FALSE);
+ appImport3.persistAndFlush();
+
+ userTransaction.commit();
+
+ given()
+ .accept("application/hal+json")
+ .queryParam("isValid", Boolean.FALSE)
+ .queryParam("filename","File1")
+ .when()
+ .get(PATH)
+ .then()
+ .statusCode(200)
+ .log().body()
+ .body("_embedded.'application-import'.size()", is(1))
+ .body("_embedded.'application-import'[0].'Business Service'", is("BS 2"))
+ .body("_embedded.'application-import'[0].'Tag Type 1'", is("tag type 1"));
+
+ given()
+ .accept("application/json")
+ .queryParam("isValid", Boolean.FALSE)
+ .when()
+ .get(PATH)
+ .then()
+ .statusCode(200)
+ .log().body()
+ .body("size()", is(2));
+
+ userTransaction.begin();
+ ApplicationImport.deleteAll();
+ ImportSummary.deleteAll();
+ userTransaction.commit();
+ }
+}
diff --git a/src/test/resources/duplicate_application_names.csv b/src/test/resources/duplicate_application_names.csv
new file mode 100644
index 0000000..0d58aab
--- /dev/null
+++ b/src/test/resources/duplicate_application_names.csv
@@ -0,0 +1,8 @@
+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
+1,OrderHub,"Create, amend and cancel food orders",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat
+1,MenuManager,Food vendors maintain their menu and price data which is curated and presented to customers via Orderhub,,Food2Go,Operating System,Windows Server 2016,Database,SQL Server,Language,C# ASP .Net,,
+1,HeadChef,Allocation of orders to restaurants for fulfillment then despatch,,Foot2Go,Operating System,RHEL 8,Database,Postgresql,Language,Java EE,Runtime,Spring Boot
+1,OrderHub,Delivery service,,Foot2Go,Operating System,RHEL 8,Database,Postgresql,Language,Python,,
+1,PocketMoney,Payment service,Incoming from orders and outgoing to delivery drivers,Payment Management,Operating System,Z/OS,Database,DB2,Language,COBOL,,
+1,BeanCounter,Accounting Service,COTS ERP system,Accounting,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Application Type,COTS
+1,SmokeSignal,Notification Service,Updates customers regarding the progress of their orders. Allocates deliveries to despatch drivers,Foot2Go,Operating System,RHEL 8,Language,Python,DataCentre,London (UK),,
\ No newline at end of file
diff --git a/src/test/resources/sample_application_import.csv b/src/test/resources/sample_application_import.csv
new file mode 100644
index 0000000..a7aabb7
--- /dev/null
+++ b/src/test/resources/sample_application_import.csv
@@ -0,0 +1,8 @@
+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
+1,OrderHub,"Create, amend and cancel food orders",,Food2Go,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Runtime,Tomcat
+1,MenuManager,Food vendors maintain their menu and price data which is curated and presented to customers via Orderhub,,Food2Go,Operating System,Windows Server 2016,Database,SQL Server,Language,C# ASP .Net,,
+1,HeadChef,Allocation of orders to restaurants for fulfillment then despatch,,Foot2Go,Operating System,RHEL 8,Database,Postgresql,Language,Java EE,Runtime,Spring Boot
+1,Despatcher,Delivery service,,Foot2Go,Operating System,RHEL 8,Database,Postgresql,Language,Python,,
+1,PocketMoney,Payment service,Incoming from orders and outgoing to delivery drivers,Payment Management,Operating System,Z/OS,Database,DB2,Language,COBOL,,
+1,BeanCounter,Accounting Service,COTS ERP system,Accounting,Operating System,RHEL 8,Database,Oracle,Language,Java EE,Application Type,COTS
+1,SmokeSignal,Notification Service,Updates customers regarding the progress of their orders. Allocates deliveries to despatch drivers,Foot2Go,Operating System,RHEL 8,Language,Python,DataCentre,London (UK),,
\ No newline at end of file