diff --git a/rest/taskana-rest-spring-example/src/test/java/pro/taskana/rest/ClassificationDefinitionControllerIntTest.java b/rest/taskana-rest-spring-example/src/test/java/pro/taskana/rest/ClassificationDefinitionControllerIntTest.java new file mode 100644 index 000000000..3e246fb42 --- /dev/null +++ b/rest/taskana-rest-spring-example/src/test/java/pro/taskana/rest/ClassificationDefinitionControllerIntTest.java @@ -0,0 +1,391 @@ +package pro.taskana.rest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.env.Environment; +import org.springframework.core.io.FileSystemResource; +import org.springframework.hateoas.PagedResources; +import org.springframework.hateoas.hal.Jackson2HalModule; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import pro.taskana.rest.resource.ClassificationResource; +import pro.taskana.rest.resource.ClassificationSummaryResource; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = RestConfiguration.class, webEnvironment = WebEnvironment.RANDOM_PORT, properties = { + "devMode=true"}) +public class ClassificationDefinitionControllerIntTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClassificationController.class); + String server = "http://127.0.0.1:"; + RestTemplate template; + HttpEntity request; + HttpHeaders headers = new HttpHeaders(); + ObjectMapper objMapper = new ObjectMapper(); + @LocalServerPort + int port; + + @Autowired + Environment env; + + @Before + public void before() { + LOGGER.debug("before"); + template = getRestTemplate(); + headers.add("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x"); + request = new HttpEntity(headers); + } + + @Test + public void testExportClassifications() { + ResponseEntity response = template.exchange( + server + port + "/v1/classification-definitions?domain=DOMAIN_B", + HttpMethod.GET, request, new ParameterizedTypeReference() { + } + ); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response.getBody().length >= 5); + assertTrue(response.getBody().length <= 7); + } + + @Test + public void testExportClassificationsFromWrongDomain() { + ResponseEntity response = template.exchange( + server + port + "/v1/classification-definitions?domain=ADdfe", + HttpMethod.GET, request, new ParameterizedTypeReference() { + } + ); + assertEquals(0, response.getBody().length); + } + + @Test + public void testImportFilledClassification() throws IOException { + ClassificationResource classification = new ClassificationResource(); + classification.setClassificationId("classificationId_"); + classification.setKey("key drelf"); + classification.setParentId("CLI:100000000000000000000000000000000016"); + classification.setParentKey("T2000"); + classification.setCategory("MANUAL"); + classification.setType("TASK"); + classification.setDomain("DOMAIN_A"); + classification.setIsValidInDomain(true); + classification.setCreated("2016-05-12T10:12:12.12Z"); + classification.setModified("2018-05-12T10:12:12.12Z"); + classification.setName("name"); + classification.setDescription("description"); + classification.setPriority(4); + classification.setServiceLevel("P2D"); + classification.setApplicationEntryPoint("entry1"); + classification.setCustom1("custom"); + classification.setCustom2("custom"); + classification.setCustom3("custom"); + classification.setCustom4("custom"); + classification.setCustom5("custom"); + classification.setCustom6("custom"); + classification.setCustom7("custom"); + classification.setCustom8("custom"); + + List clList = new ArrayList<>(); + clList.add(objMapper.writeValueAsString(classification)); + + ResponseEntity response = importRequest(clList); + assertEquals(HttpStatus.OK, response.getStatusCode()); + } + + @Test + public void testFailureWhenKeyIsMissing() throws IOException { + ClassificationResource classification = new ClassificationResource(); + classification.setDomain("DOMAIN_A"); + List clList = new ArrayList<>(); + clList.add(objMapper.writeValueAsString(classification)); + + try { + importRequest(clList); + } catch (HttpClientErrorException e) { + assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode()); + } + } + + @Test + public void testFailureWhenDomainIsMissing() throws IOException { + ClassificationResource classification = new ClassificationResource(); + classification.setKey("one"); + List clList = new ArrayList<>(); + clList.add(objMapper.writeValueAsString(classification)); + + try { + importRequest(clList); + } catch(HttpClientErrorException e) { + assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode()); + } + } + + @Test + public void testFailureWhenUpdatingTypeOfExistingClassification() throws IOException { + ClassificationSummaryResource classification = this.getClassificationWithKeyAndDomain("T6310", ""); + classification.setType("DOCUMENT"); + List clList = new ArrayList<>(); + clList.add(objMapper.writeValueAsString(classification)); + + try { + importRequest(clList); + } catch(HttpClientErrorException e) { + assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode()); + } + } + + @Test + public void testImportMultipleClassifications() throws IOException, InterruptedException { + ClassificationResource classification1 = this.createClassification("id1", "ImportKey1", "DOMAIN_A", null, null); + String c1 = objMapper.writeValueAsString(classification1); + + ClassificationResource classification2 = this.createClassification("id2", "ImportKey2", "DOMAIN_A", "CLI:100000000000000000000000000000000016", "T2000"); + classification2.setCategory("MANUAL"); + classification2.setType("TASK"); + classification2.setIsValidInDomain(true); + classification2.setCreated("2016-05-12T10:12:12.12Z"); + classification2.setModified("2018-05-12T10:12:12.12Z"); + classification2.setName("name"); + classification2.setDescription("description"); + classification2.setPriority(4); + classification2.setServiceLevel("P2D"); + classification2.setApplicationEntryPoint("entry1"); + String c2 = objMapper.writeValueAsString(classification2); + + List clList = new ArrayList<>(); + clList.add(c1); + clList.add(c2); + + ResponseEntity response = importRequest(clList); + assertEquals(HttpStatus.OK, response.getStatusCode()); + } + + @Test + public void testImportDuplicateClassification() throws IOException, InterruptedException { + ClassificationResource classification1 = new ClassificationResource(); + classification1.setClassificationId("id1"); + classification1.setKey("ImportKey3"); + classification1.setDomain("DOMAIN_A"); + String c1 = objMapper.writeValueAsString(classification1); + + List clList = new ArrayList<>(); + clList.add(c1); + clList.add(c1); + + try { + importRequest(clList); + } catch (HttpClientErrorException e) { + assertEquals(HttpStatus.CONFLICT, e.getStatusCode()); + } + } + + @Test + public void testInsertExistingClassificationWithOlderTimestamp() throws IOException { + ClassificationSummaryResource existingClassification = getClassificationWithKeyAndDomain("L110107", "DOMAIN_A"); + existingClassification.setName("first new Name"); + List clList = new ArrayList<>(); + clList.add(objMapper.writeValueAsString(existingClassification)); + + ResponseEntity response = importRequest(clList); + assertEquals(HttpStatus.OK, response.getStatusCode()); + + existingClassification.setName("second new Name"); + clList = new ArrayList<>(); + clList.add(objMapper.writeValueAsString(existingClassification)); + + response = importRequest(clList); + assertEquals(HttpStatus.OK, response.getStatusCode()); + + ClassificationSummaryResource testClassification = this.getClassificationWithKeyAndDomain("L110107", "DOMAIN_A"); + assertEquals("second new Name", testClassification.getName()); + } + + @Test + public void testHookExistingChildToNewParent() throws IOException { + ClassificationResource newClassification = createClassification( + "new Classification", "newClass", "DOMAIN_A", null, "L11010"); + ClassificationSummaryResource existingClassification = getClassificationWithKeyAndDomain("L110102", "DOMAIN_A"); + existingClassification.setParentId("new Classification"); + existingClassification.setParentKey("newClass"); + + List clList = new ArrayList<>(); + clList.add(objMapper.writeValueAsString(existingClassification)); + clList.add(objMapper.writeValueAsString(newClassification)); + + ResponseEntity response = importRequest(clList); + assertEquals(HttpStatus.OK, response.getStatusCode()); + + ClassificationSummaryResource parentCl = getClassificationWithKeyAndDomain("L11010", "DOMAIN_A"); + ClassificationSummaryResource testNewCl = getClassificationWithKeyAndDomain("newClass", "DOMAIN_A"); + ClassificationSummaryResource testExistingCl = getClassificationWithKeyAndDomain("L110102", "DOMAIN_A"); + + assertNotNull(parentCl); + assertNotNull(testNewCl); + assertNotNull(testExistingCl); + assertEquals(testNewCl.getClassificationId(), testExistingCl.getParentId()); + assertEquals(parentCl.getClassificationId(), testNewCl.getParentId()); + } + + @Test + public void testImportParentAndChildClassification() throws IOException, InterruptedException { + ClassificationResource classification1 = this.createClassification("parentId", "ImportKey6", "DOMAIN_A", null, null); + String c1 = objMapper.writeValueAsString(classification1); + + ClassificationResource classification2 = this.createClassification("childId1", "ImportKey7", "DOMAIN_A", null, "ImportKey6"); + String c21 = objMapper.writeValueAsString(classification2); + classification2 = this.createClassification("childId2", "ImportKey8", "DOMAIN_A", "parentId", null); + String c22 = objMapper.writeValueAsString(classification2); + + ClassificationResource classification3 = this.createClassification("grandchildId1", "ImportKey9", "DOMAIN_A", "childId1", "ImportKey7"); + String c31 = objMapper.writeValueAsString(classification3); + classification3 = this.createClassification("grandchild2", "ImportKey10", "DOMAIN_A", null, "ImportKey7"); + String c32 = objMapper.writeValueAsString(classification3); + + List clList = new ArrayList<>(); + clList.add(c31); + clList.add(c32); + clList.add(c21); + clList.add(c22); + clList.add(c1); + + ResponseEntity response = importRequest(clList); + assertEquals(HttpStatus.OK, response.getStatusCode()); + + + ClassificationSummaryResource parentCl = getClassificationWithKeyAndDomain("ImportKey6", "DOMAIN_A"); + ClassificationSummaryResource childCl = getClassificationWithKeyAndDomain("ImportKey7", "DOMAIN_A"); + ClassificationSummaryResource grandchildCl = getClassificationWithKeyAndDomain("ImportKey9", "DOMAIN_A"); + + assertNotNull(parentCl); + assertNotNull(childCl); + assertNotNull(grandchildCl); + assertEquals(childCl.getClassificationId(), grandchildCl.getParentId()); + assertEquals(parentCl.getClassificationId(), childCl.getParentId()); + } + + @Test + public void testImportParentAndChildClassificationWithKey() throws IOException, InterruptedException { + ClassificationResource classification1 = createClassification("parent", "ImportKey11", "DOMAIN_A", null, null); + classification1.setCustom1("parent is correct"); + String parent = objMapper.writeValueAsString(classification1); + ClassificationResource classification2 = createClassification("wrongParent", "ImportKey11", "DOMAIN_B", null, null); + String wrongParent = objMapper.writeValueAsString(classification2); + ClassificationResource classification3 = createClassification("child", "ImportKey13", "DOMAIN_A", null, "ImportKey11"); + String child = objMapper.writeValueAsString(classification3); + + List clList = new ArrayList<>(); + clList.add(wrongParent); + clList.add(parent); + clList.add(child); + + ResponseEntity response = importRequest(clList); + assertEquals(HttpStatus.OK, response.getStatusCode()); + + ClassificationSummaryResource rightParentCl = getClassificationWithKeyAndDomain("ImportKey11", "DOMAIN_A"); + ClassificationSummaryResource wrongParentCl = getClassificationWithKeyAndDomain("ImportKey11", "DOMAIN_B"); + ClassificationSummaryResource childCl = getClassificationWithKeyAndDomain("ImportKey13", "DOMAIN_A"); + + assertNotNull(rightParentCl); + assertNotNull(wrongParentCl); + assertNotNull(childCl); + assertEquals(rightParentCl.getClassificationId(), childCl.getParentId()); + assertNotEquals(wrongParentCl.getClassificationId(), childCl.getParentId()); + } + + private ClassificationResource createClassification(String id, String key, String domain, String parentId, String parentKey) { + ClassificationResource classificationResource = new ClassificationResource(); + classificationResource.setClassificationId(id); + classificationResource.setKey(key); + classificationResource.setDomain(domain); + classificationResource.setParentId(parentId); + classificationResource.setParentKey(parentKey); + return classificationResource; + } + + private ClassificationSummaryResource getClassificationWithKeyAndDomain(String key, String domain) { + LOGGER.debug("Request classification with key={} in domain={}", key, domain); + RestTemplate template = getRestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x"); + HttpEntity request = new HttpEntity(headers); + ResponseEntity> response = template.exchange( + "http://127.0.0.1:" + port + "/v1/classifications?key=" + key + "&domain=" + domain, + HttpMethod.GET, + request, + new ParameterizedTypeReference>() { + + }); + return response.getBody().getContent().toArray(new ClassificationSummaryResource[1])[0]; + } + + private ResponseEntity importRequest (List clList) throws IOException { + LOGGER.debug("Start Import"); + File tmpFile = File.createTempFile("test", ".tmp"); + FileWriter writer = new FileWriter(tmpFile); + writer.write(clList.toString()); + writer.close(); + + MultiValueMap body = new LinkedMultiValueMap<>(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + body.add("file", new FileSystemResource(tmpFile)); + + HttpEntity> requestEntity = new HttpEntity<>(body, headers); + String serverUrl = server + port + "/v1/classification-definitions"; + RestTemplate restTemplate = new RestTemplate(); + + return restTemplate.postForEntity(serverUrl, requestEntity, String.class); + } + + /** + * Return a REST template which is capable of dealing with responses in HAL format + * + * @return RestTemplate + */ + private RestTemplate getRestTemplate() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.registerModule(new Jackson2HalModule()); + + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/haljson,*/*")); + converter.setObjectMapper(mapper); + + RestTemplate template = new RestTemplate(Collections.>singletonList(converter)); + return template; + } +} diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/ClassificationDefinitionController.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/ClassificationDefinitionController.java index 3ca59bf7c..7afbc3e04 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/ClassificationDefinitionController.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/ClassificationDefinitionController.java @@ -31,10 +31,15 @@ import pro.taskana.rest.resource.ClassificationResource; import pro.taskana.rest.resource.ClassificationResourceAssembler; import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Set; /** * Controller for Importing / Exporting classifications. @@ -75,27 +80,121 @@ public class ClassificationDefinitionController { @RequestParam("file") MultipartFile file) throws InvalidArgumentException, NotAuthorizedException, ConcurrencyException, ClassificationNotFoundException, ClassificationAlreadyExistException, DomainNotFoundException, IOException { + + Map systemIds = getSystemIds(); + List classificationsResources = extractClassificationResourcesFromFile(file); + + Map childsInFile = mapChildsToParentKeys(classificationsResources, systemIds); + insertOrUpdateClassificationsWithoutParent(classificationsResources, systemIds); + updateParentChildRelations(childsInFile); + + return new ResponseEntity<>(HttpStatus.OK); + + } + + private Map getSystemIds() { Map systemIds = classificationService.createClassificationQuery() .list() .stream() .collect(Collectors.toMap(i -> i.getKey() + "|" + i.getDomain(), ClassificationSummary::getId)); + return systemIds; + } + private List extractClassificationResourcesFromFile(MultipartFile file) + throws IOException, JsonParseException, JsonMappingException { ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); List classificationsDefinitions = mapper.readValue(file.getInputStream(), new TypeReference>() { }); + return classificationsDefinitions; + } - for (ClassificationResource classification : classificationsDefinitions) { - if (systemIds.containsKey(classification.getKey() + "|" + classification.getDomain())) { - classificationService.updateClassification(classificationResourceAssembler.toModel(classification)); - } else { - classificationService.createClassification(classificationResourceAssembler.toModel(classification)); + private Map mapChildsToParentKeys(List classificationResources, Map systemIds) { + Map childsInFile = new HashMap<>(); + Set newKeysWithDomain = new HashSet<>(); + classificationResources.forEach(cl -> newKeysWithDomain.add(cl.getKey() + "|" + cl.getDomain())); + + for (ClassificationResource cl : classificationResources) { + cl.parentId = cl.parentId == null ? "" : cl.parentId; + cl.parentKey = cl.parentKey == null ? "" : cl.parentKey; + + if (!cl.getParentId().equals("") && cl.getParentKey().equals("")) { + for (ClassificationResource parent : classificationResources) { + if (cl.getParentId().equals(parent.getClassificationId())) { + cl.setParentKey(parent.getKey()); + } + } + } + + String parentKeyAndDomain = cl.parentKey + "|" + cl.domain; + if (!cl.getParentKey().isEmpty() && !cl.getParentKey().equals("")) { + if (newKeysWithDomain.contains(parentKeyAndDomain) || systemIds.containsKey(parentKeyAndDomain)) { + childsInFile.put(classificationResourceAssembler.toModel(cl), cl.getParentKey()); + } } } - - return new ResponseEntity<>(HttpStatus.OK); - + return childsInFile; } + + private void insertOrUpdateClassificationsWithoutParent(List classificationResources, + Map systemIds) + throws ClassificationNotFoundException, NotAuthorizedException, InvalidArgumentException, + ClassificationAlreadyExistException, DomainNotFoundException, ConcurrencyException { + + for (ClassificationResource classificationResource : classificationResources) { + classificationResource.setParentKey(null); + classificationResource.setParentId(null); + classificationResource.setClassificationId(null); + + String systemId = systemIds.get(classificationResource.key + "|" + classificationResource.domain); + if (systemId != null) { + updateExistingClassification(classificationResource, systemId); + } else { + classificationService.createClassification( + classificationResourceAssembler.toModel(classificationResource)); + } + } + } + + private void updateParentChildRelations(Map childsInFile) + throws ClassificationNotFoundException, NotAuthorizedException, ConcurrencyException, + InvalidArgumentException { + for (Classification childRes : childsInFile.keySet()) { + Classification child = classificationService + .getClassification(childRes.getKey(), childRes.getDomain()); + String parentKey = childsInFile.get(childRes); + String parentId = classificationService.getClassification(parentKey, childRes.getDomain()).getId(); + child.setParentKey(parentKey); + child.setParentId(parentId); + classificationService.updateClassification(child); + } + } + + private void updateExistingClassification(ClassificationResource cl, + String systemId) throws ClassificationNotFoundException, NotAuthorizedException, + ConcurrencyException, InvalidArgumentException { + Classification currentClassification = classificationService.getClassification(systemId); + if (cl.getType() != null && !cl.getType().equals(currentClassification.getType())) { + throw new InvalidArgumentException("Can not change the type of a classification."); + } + currentClassification.setCategory(cl.category); + currentClassification.setIsValidInDomain(cl.isValidInDomain); + currentClassification.setName(cl.name); + currentClassification.setDescription(cl.description); + currentClassification.setPriority(cl.priority); + currentClassification.setServiceLevel(cl.serviceLevel); + currentClassification.setApplicationEntryPoint(cl.applicationEntryPoint); + currentClassification.setCustom1(cl.custom1); + currentClassification.setCustom2(cl.custom2); + currentClassification.setCustom3(cl.custom3); + currentClassification.setCustom4(cl.custom4); + currentClassification.setCustom5(cl.custom5); + currentClassification.setCustom6(cl.custom6); + currentClassification.setCustom7(cl.custom7); + currentClassification.setCustom8(cl.custom8); + classificationService.updateClassification(currentClassification); + } + }