diff --git a/rest/src/main/java/pro/taskana/rest/ClassificationDefinitionController.java b/rest/src/main/java/pro/taskana/rest/ClassificationDefinitionController.java index 28dbf4b8a..c45e31055 100644 --- a/rest/src/main/java/pro/taskana/rest/ClassificationDefinitionController.java +++ b/rest/src/main/java/pro/taskana/rest/ClassificationDefinitionController.java @@ -54,6 +54,7 @@ public class ClassificationDefinitionController { } return new ResponseEntity<>(export, HttpStatus.OK); } catch (ClassificationNotFoundException e) { + TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } @@ -64,15 +65,18 @@ public class ClassificationDefinitionController { @RequestBody List classificationResources) { try { Map systemIds = classificationService.createClassificationQuery().list().stream() - .collect(Collectors.toMap(i -> i.getKey() + i.getDomain(), ClassificationSummary::getId)); + .collect(Collectors.toMap(i -> i.getKey() + "|||" + i.getDomain(), ClassificationSummary::getId)); for (ClassificationResource classificationResource : classificationResources) { Classification classification = classificationMapper.toModel(classificationResource); - if (systemIds.containsKey(classificationResource.key + classificationResource.domain)) + if (systemIds.containsKey(classificationResource.key + "|||" + classificationResource.domain)) classificationService.updateClassification(classification); else classificationService.createClassification(classification); } + + return new ResponseEntity<>(HttpStatus.OK); + } catch (ClassificationNotFoundException e) { TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); return new ResponseEntity<>(HttpStatus.NOT_FOUND); @@ -83,7 +87,5 @@ public class ClassificationDefinitionController { TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } - - return new ResponseEntity<>(HttpStatus.OK); } } diff --git a/rest/src/main/java/pro/taskana/rest/WorkbasketDefinitionController.java b/rest/src/main/java/pro/taskana/rest/WorkbasketDefinitionController.java index b086c750a..b1547e05f 100644 --- a/rest/src/main/java/pro/taskana/rest/WorkbasketDefinitionController.java +++ b/rest/src/main/java/pro/taskana/rest/WorkbasketDefinitionController.java @@ -1,24 +1,37 @@ package pro.taskana.rest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.interceptor.TransactionInterceptor; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; + import pro.taskana.Workbasket; import pro.taskana.WorkbasketQuery; import pro.taskana.WorkbasketService; import pro.taskana.WorkbasketSummary; +import pro.taskana.exceptions.InvalidWorkbasketException; import pro.taskana.exceptions.NotAuthorizedException; import pro.taskana.exceptions.WorkbasketNotFoundException; +import pro.taskana.rest.resource.WorkbasketAccessItemResource; import pro.taskana.rest.resource.WorkbasketDefinition; +import pro.taskana.rest.resource.WorkbasketResource; +import pro.taskana.rest.resource.mapper.WorkbasketAccessItemMapper; import pro.taskana.rest.resource.mapper.WorkbasketDefinitionMapper; - -import java.util.ArrayList; -import java.util.List; +import pro.taskana.rest.resource.mapper.WorkbasketMapper; @RestController @RequestMapping(path = "/v1/workbasketdefinitions", produces = {MediaType.APPLICATION_JSON_VALUE}) @@ -30,7 +43,14 @@ public class WorkbasketDefinitionController { @Autowired private WorkbasketDefinitionMapper workbasketDefinitionMapper; + @Autowired + private WorkbasketMapper workbasketMapper; + + @Autowired + private WorkbasketAccessItemMapper workbasketAccessItemMapper; + @GetMapping + @Transactional(readOnly = true, rollbackFor = Exception.class) public ResponseEntity> exportWorkbaskets(@RequestParam(required = false) String domain) { try { WorkbasketQuery workbasketQuery = workbasketService.createWorkbasketQuery(); @@ -44,9 +64,106 @@ public class WorkbasketDefinitionController { } return new ResponseEntity<>(basketExports, HttpStatus.OK); } catch (WorkbasketNotFoundException e) { + TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); return new ResponseEntity<>(HttpStatus.NOT_FOUND); } catch (NotAuthorizedException e) { + TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); } } + + /** + * This method imports a list of {@link WorkbasketDefinition}. + * This does not exactly match the REST norm, but we want to have an option to import all settings at once. + * When a logical equal (key and domain are equal) workbasket already exists an update will be executed. + * Otherwise a new workbasket will be created. + * + * @param definitions the list of workbasket definitions which will be imported to the current system. + * @return TODO: what should we return? + */ + @PostMapping(path = "/import") + @Transactional(rollbackFor = Exception.class) + public ResponseEntity importWorkbaskets(@RequestBody List definitions) { + try { + // key: logical ID + // value: system ID (in database) + Map systemIds = workbasketService.createWorkbasketQuery().list().stream() + .collect(Collectors.toMap(this::logicalId, WorkbasketSummary::getId)); + + // key: old system ID + // value: system ID + Map idConversion = new HashMap<>(); + + // STEP 1: update or create workbaskets from the import + for (WorkbasketDefinition definition : definitions) { + WorkbasketResource res = definition.workbasketResource; + Workbasket workbasket; + if (systemIds.containsKey(logicalId(res))) { + String oldId = res.workbasketId; + res.workbasketId = systemIds.get(logicalId(res)); + workbasket = workbasketService.updateWorkbasket( + workbasketMapper.toModel(res) + ); + res.workbasketId = oldId; + } else { + workbasket = workbasketService.createWorkbasket( + workbasketMapper.toModel(res) + ); + } + for (WorkbasketAccessItemResource authorization : definition.authorizations) { + workbasketService.createWorkbasketAuthorization( + workbasketAccessItemMapper.toModel(authorization) + ); + } + idConversion.put(definition.workbasketResource.workbasketId, workbasket.getId()); + } + + // STEP 2: update distribution targets + // This can not be done in step 1 because the system IDs are only known after step 1 + for (WorkbasketDefinition definition : definitions) { + List distributionTargets = new ArrayList<>(); + for (String oldId : definition.distributionTargets) { + if (idConversion.containsKey(oldId)) { + distributionTargets.add(idConversion.get(oldId)); + } else { + throw new WorkbasketNotFoundException( + String.format( + "invalid import state: Workbasket '%s' does not exist in the given import list", + oldId) + ); + } + } + + workbasketService.setDistributionTargets( + // no verification necessary since the workbasket was already imported in step 1. + idConversion.get(definition.workbasketResource.workbasketId), distributionTargets + ); + } + + return new ResponseEntity<>(HttpStatus.OK); + + } catch (WorkbasketNotFoundException e) { + TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } catch (InvalidWorkbasketException e) { + // TODO: which status code? + TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } catch (NotAuthorizedException e) { + TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + } + + private String logicalId(WorkbasketSummary workbasket) { + return logicalId(workbasket.getKey(), workbasket.getDomain()); + } + + private String logicalId(WorkbasketResource resource) { + return logicalId(resource.key, resource.domain); + } + + private String logicalId(String key, String domain) { + return key + "|||" + domain; + } } diff --git a/rest/src/main/java/pro/taskana/rest/resource/WorkbasketDefinition.java b/rest/src/main/java/pro/taskana/rest/resource/WorkbasketDefinition.java index 731f7cab2..64c3e947f 100644 --- a/rest/src/main/java/pro/taskana/rest/resource/WorkbasketDefinition.java +++ b/rest/src/main/java/pro/taskana/rest/resource/WorkbasketDefinition.java @@ -1,44 +1,30 @@ package pro.taskana.rest.resource; -import pro.taskana.Workbasket; -import pro.taskana.WorkbasketAccessItem; - import java.util.List; import java.util.Set; +import org.springframework.hateoas.ResourceSupport; + /** * this class represents a workbasket including its distro targets and authorisations. */ -public class WorkbasketDefinition { +public class WorkbasketDefinition extends ResourceSupport { - private final Workbasket workbasket; - private final Set distributionTargets; - private final List authorizations; + public Set distributionTargets; + public List authorizations; + public WorkbasketResource workbasketResource; - public WorkbasketDefinition(Workbasket workbasket, Set distributionTargets, - List authorizations) { - this.workbasket = workbasket; + public WorkbasketDefinition() { + // necessary for de-serializing + } + + public WorkbasketDefinition(WorkbasketResource workbasketResource, + Set distributionTargets, + List authorizations) { + super(); + this.workbasketResource = workbasketResource; this.distributionTargets = distributionTargets; this.authorizations = authorizations; } - public Workbasket getWorkbasket() { - return workbasket; - } - - public Set getDistributionTargets() { - return distributionTargets; - } - - public List getAuthorizations() { - return authorizations; - } - - @Override public String toString() { - return "WorkbasketDefinition{" + - "workbasket=" + workbasket + - ", distributionTargets=" + distributionTargets + - ", authorizations=" + authorizations + - '}'; - } } diff --git a/rest/src/main/java/pro/taskana/rest/resource/WorkbasketResource.java b/rest/src/main/java/pro/taskana/rest/resource/WorkbasketResource.java index dae66e1b5..321d143bb 100644 --- a/rest/src/main/java/pro/taskana/rest/resource/WorkbasketResource.java +++ b/rest/src/main/java/pro/taskana/rest/resource/WorkbasketResource.java @@ -36,6 +36,7 @@ public class WorkbasketResource extends ResourceSupport { public String orgLevel4; public WorkbasketResource() { + //necessary for de-serializing } public WorkbasketResource(String workbasketId, String key, String name, String domain, WorkbasketType type, diff --git a/rest/src/main/java/pro/taskana/rest/resource/mapper/WorkbasketAccessItemMapper.java b/rest/src/main/java/pro/taskana/rest/resource/mapper/WorkbasketAccessItemMapper.java index 133f98e95..15049d78d 100644 --- a/rest/src/main/java/pro/taskana/rest/resource/mapper/WorkbasketAccessItemMapper.java +++ b/rest/src/main/java/pro/taskana/rest/resource/mapper/WorkbasketAccessItemMapper.java @@ -59,4 +59,5 @@ public class WorkbasketAccessItemMapper { wbAccItemModel.setPermCustom12(wbAccItemRecource.permCustom12); return wbAccItemModel; } + } diff --git a/rest/src/main/java/pro/taskana/rest/resource/mapper/WorkbasketDefinitionMapper.java b/rest/src/main/java/pro/taskana/rest/resource/mapper/WorkbasketDefinitionMapper.java index c799afcf2..0b81a4d8d 100644 --- a/rest/src/main/java/pro/taskana/rest/resource/mapper/WorkbasketDefinitionMapper.java +++ b/rest/src/main/java/pro/taskana/rest/resource/mapper/WorkbasketDefinitionMapper.java @@ -8,11 +8,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import pro.taskana.Workbasket; -import pro.taskana.WorkbasketAccessItem; import pro.taskana.WorkbasketService; import pro.taskana.WorkbasketSummary; import pro.taskana.exceptions.NotAuthorizedException; import pro.taskana.exceptions.WorkbasketNotFoundException; +import pro.taskana.rest.resource.WorkbasketAccessItemResource; import pro.taskana.rest.resource.WorkbasketDefinition; @Component @@ -21,6 +21,12 @@ public class WorkbasketDefinitionMapper { @Autowired private WorkbasketService workbasketService; + @Autowired + private WorkbasketMapper workbasketMapper; + + @Autowired + private WorkbasketAccessItemMapper workbasketAccessItemMapper; + /** * maps the distro targets to their id to remove overhead. * @param basket {@link Workbasket} which will be converted @@ -29,12 +35,15 @@ public class WorkbasketDefinitionMapper { * @throws NotAuthorizedException if the user is not authorized * @throws WorkbasketNotFoundException if {@code basket} is an unknown workbasket */ - public WorkbasketDefinition toResource(Workbasket basket) throws NotAuthorizedException, WorkbasketNotFoundException { - List authorizations = workbasketService.getWorkbasketAuthorizations(basket.getKey()); + public WorkbasketDefinition toResource(Workbasket basket) + throws NotAuthorizedException, WorkbasketNotFoundException { + List authorizations = workbasketService.getWorkbasketAuthorizations( + basket.getKey()).stream() + .map(workbasketAccessItemMapper::toResource) + .collect(Collectors.toList()); Set distroTargets = workbasketService.getDistributionTargets(basket.getId()).stream() - .map(WorkbasketSummary::getId) - .collect(Collectors.toSet()); - return new WorkbasketDefinition(basket,distroTargets,authorizations); - + .map(WorkbasketSummary::getId) + .collect(Collectors.toSet()); + return new WorkbasketDefinition(workbasketMapper.toResource(basket), distroTargets, authorizations); } }