TSK-226: Enable HATEOAS on REST at least for Workbasket

This commit is contained in:
Marcel Lengl 2018-02-27 11:27:25 +01:00 committed by Holger Hagen
parent 0bdbf4700b
commit 42d8739a67
13 changed files with 186 additions and 78 deletions

View File

@ -22,6 +22,11 @@ import org.springframework.web.bind.annotation.RestController;
import pro.taskana.Classification;
import pro.taskana.ClassificationService;
import pro.taskana.ClassificationSummary;
import pro.taskana.exceptions.ClassificationAlreadyExistException;
import pro.taskana.exceptions.ClassificationNotFoundException;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.rest.resource.ClassificationResource;
import pro.taskana.rest.resource.mapper.ClassificationMapper;
@RestController
@RequestMapping(path = "/v1/classifications", produces = {MediaType.APPLICATION_JSON_VALUE})
@ -30,6 +35,9 @@ public class ClassificationController {
@Autowired
private ClassificationService classificationService;
@Autowired
private ClassificationMapper classificationMapper;
@GetMapping
@Transactional(readOnly = true, rollbackFor = Exception.class)
public ResponseEntity<List<ClassificationSummary>> getClassifications() {
@ -42,52 +50,61 @@ public class ClassificationController {
}
}
@GetMapping(path = "/{classificationKey}")
@GetMapping(path = "/{classificationId}")
@Transactional(readOnly = true, rollbackFor = Exception.class)
public ResponseEntity<Classification> getClassification(@PathVariable String classificationKey) {
public ResponseEntity<ClassificationResource> getClassification(@PathVariable String classificationId) {
try {
Classification classification = classificationService.getClassification(classificationKey, "");
return ResponseEntity.status(HttpStatus.OK).body(classification);
} catch (Exception e) {
Classification classification = classificationService.getClassification(classificationId);
return ResponseEntity.status(HttpStatus.OK).body(classificationMapper.toResource(classification));
} catch (ClassificationNotFoundException e) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
@GetMapping(path = "/{classificationKey}/{domain}")
@Transactional(readOnly = true, rollbackFor = Exception.class)
public ResponseEntity<Classification> getClassification(@PathVariable String classificationKey,
public ResponseEntity<ClassificationResource> getClassification(@PathVariable String classificationKey,
@PathVariable String domain) {
try {
Classification classification = classificationService.getClassification(classificationKey, domain);
return ResponseEntity.status(HttpStatus.OK).body(classification);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.OK).body(classificationMapper.toResource(classification));
} catch (ClassificationNotFoundException e) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
@PostMapping
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<Classification> createClassification(@RequestBody Classification classification) {
public ResponseEntity<ClassificationResource> createClassification(
@RequestBody ClassificationResource resource) {
try {
classificationService.createClassification(classification);
return ResponseEntity.status(HttpStatus.CREATED).body(classification);
} catch (Exception e) {
Classification classification = classificationMapper.toModel(resource);
classification = classificationService.createClassification(classification);
return ResponseEntity.status(HttpStatus.CREATED).body(classificationMapper.toResource(classification));
} catch (ClassificationAlreadyExistException e) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
return ResponseEntity.status(HttpStatus.CONFLICT).build();
} catch (NotAuthorizedException e) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
@PutMapping
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<Classification> updateClassification(@RequestBody Classification classification) {
public ResponseEntity<ClassificationResource> updateClassification(@RequestBody ClassificationResource resource) {
try {
classificationService.updateClassification(classification);
return ResponseEntity.status(HttpStatus.CREATED).body(classification);
} catch (Exception e) {
Classification classification = classificationMapper.toModel(resource);
classification = classificationService.updateClassification(classification);
return ResponseEntity.status(HttpStatus.OK).body(classificationMapper.toResource(classification));
} catch (ClassificationNotFoundException e) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (NotAuthorizedException e) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
}

View File

@ -35,7 +35,7 @@ import pro.taskana.sampledata.SampleDataGenerator;
@EnableTransactionManagement
public class RestConfiguration {
private static final Logger logger = LoggerFactory.getLogger(RestApplication.class);
private static final Logger LOGGER = LoggerFactory.getLogger(RestApplication.class);
@Bean
@Primary
@ -80,8 +80,8 @@ public class RestConfiguration {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public TaskanaEngineConfiguration taskanaEngineConfiguration(DataSource dataSource) throws SQLException {
TaskanaEngineConfiguration taskanaEngineConfiguration =
new SpringTaskanaEngineConfiguration(dataSource, true, true);
TaskanaEngineConfiguration taskanaEngineConfiguration = new SpringTaskanaEngineConfiguration(dataSource, true,
true);
new SampleDataGenerator(dataSource).generateSampleData();

View File

@ -18,7 +18,6 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@ -182,9 +181,16 @@ public class WorkbasketController {
public ResponseEntity<List<WorkbasketAccessItemResource>> getWorkbasketAuthorizations(
@PathVariable(value = "workbasketId") String workbasketId) {
List<WorkbasketAccessItem> wbAuthorizations = workbasketService.getWorkbasketAuthorizations(workbasketId);
return new ResponseEntity<>(wbAuthorizations.stream()
.map(accItem -> workbasketAccessItemMapper.toResource(accItem))
.collect(Collectors.toList()), HttpStatus.OK);
List<WorkbasketAccessItemResource> result = new ArrayList<>();
wbAuthorizations.stream()
.forEach(accItem -> {
try {
result.add(workbasketAccessItemMapper.toResource(accItem));
} catch (NotAuthorizedException e) {
e.printStackTrace();
}
});
return new ResponseEntity<>(result, HttpStatus.OK);
}
@PostMapping(path = "/authorizations")
@ -216,7 +222,7 @@ public class WorkbasketController {
}
}
@RequestMapping(value = "/{workbasketId}/authorizations/", method = RequestMethod.PUT)
@PutMapping(value = "/{workbasketId}/authorizations/")
public ResponseEntity<?> setWorkbasketAuthorizations(@PathVariable(value = "workbasketId") String workbasketId,
@RequestBody List<WorkbasketAccessItemResource> workbasketAccessResourceItems) {
try {
@ -263,7 +269,7 @@ public class WorkbasketController {
@PutMapping(path = "/{workbasketId}/distributiontargets")
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<?> setDistributionTargets(
public ResponseEntity<?> setDistributionTargetsForWorkbasketId(
@PathVariable(value = "workbasketId") String sourceWorkbasketId,
@RequestBody List<String> targetWorkbasketIds) {
ResponseEntity<?> result;

View File

@ -87,8 +87,7 @@ public class WorkbasketDefinitionController {
*/
@PostMapping(path = "/import")
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<String> importWorkbaskets(@RequestBody List<WorkbasketDefinition> definitions)
throws InvalidArgumentException {
public ResponseEntity<String> importWorkbaskets(@RequestBody List<WorkbasketDefinition> definitions) {
try {
// key: logical ID
// value: system ID (in database)
@ -141,9 +140,7 @@ public class WorkbasketDefinitionController {
// 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);
@ -153,6 +150,9 @@ public class WorkbasketDefinitionController {
} catch (NotAuthorizedException e) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
} catch (InvalidArgumentException e) {
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
}
}

View File

@ -8,7 +8,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import pro.taskana.ClassificationService;
import pro.taskana.TaskQuery;
import pro.taskana.TaskService;
import pro.taskana.TaskSummary;
@ -49,9 +48,6 @@ public class TaskFilter {
@Autowired
private TaskService taskService;
@Autowired
private ClassificationService classificationService;
public List<TaskSummary> getAll() throws NotAuthorizedException {
return taskService.createTaskQuery().list();
}

View File

@ -26,5 +26,4 @@ public class WorkbasketDefinition extends ResourceSupport {
this.distributionTargets = distributionTargets;
this.authorizations = authorizations;
}
}

View File

@ -1,5 +1,8 @@
package pro.taskana.rest.resource.mapper;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.time.Instant;
import org.springframework.beans.BeanUtils;
@ -8,8 +11,9 @@ import org.springframework.stereotype.Component;
import pro.taskana.Classification;
import pro.taskana.ClassificationService;
import pro.taskana.impl.ClassificationImpl;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.impl.ClassificationImpl;
import pro.taskana.rest.ClassificationController;
import pro.taskana.rest.resource.ClassificationResource;
@Component
@ -21,10 +25,10 @@ public class ClassificationMapper {
public ClassificationResource toResource(Classification classification) {
ClassificationResource resource = new ClassificationResource();
BeanUtils.copyProperties(classification, resource);
//need to be set by hand, because they are named different, or have different types
// need to be set by hand, because they are named different, or have different types
resource.setClassificationId(classification.getId());
resource.setCreated(classification.getCreated().toString());
return resource;
return addLinks(resource, classification);
}
public Classification toModel(ClassificationResource classificationResource) throws NotAuthorizedException {
@ -36,4 +40,23 @@ public class ClassificationMapper {
classification.setCreated(Instant.parse(classificationResource.getCreated()));
return classification;
}
private ClassificationResource addLinks(ClassificationResource resource, Classification classification) {
resource.add(
linkTo(methodOn(ClassificationController.class).getClassification(classification.getId()))
.withSelfRel());
resource.add(
linkTo(methodOn(ClassificationController.class).getClassification(classification.getKey(),
classification.getDomain()))
.withRel("getClassificationByKeyAndDomain"));
resource.add(
linkTo(methodOn(ClassificationController.class).getClassifications()).withRel("getAllClassifications"));
resource.add(
linkTo(methodOn(ClassificationController.class).createClassification(resource))
.withRel("createClassification"));
resource.add(
linkTo(methodOn(ClassificationController.class).updateClassification(resource))
.withRel("updateClassification"));
return resource;
}
}

View File

@ -3,12 +3,15 @@ package pro.taskana.rest.resource.mapper;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.Arrays;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pro.taskana.WorkbasketAccessItem;
import pro.taskana.WorkbasketService;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.impl.WorkbasketAccessItemImpl;
import pro.taskana.rest.WorkbasketController;
import pro.taskana.rest.resource.WorkbasketAccessItemResource;
@ -19,17 +22,13 @@ public class WorkbasketAccessItemMapper {
@Autowired
private WorkbasketService workbasketService;
public WorkbasketAccessItemResource toResource(WorkbasketAccessItem wbAccItem) {
public WorkbasketAccessItemResource toResource(WorkbasketAccessItem wbAccItem) throws NotAuthorizedException {
WorkbasketAccessItemResource resource = new WorkbasketAccessItemResource();
BeanUtils.copyProperties(wbAccItem, resource);
//property is named different, so it needs to be set by hand
// property is named different, so it needs to be set by hand
resource.setAccessItemId(wbAccItem.getId());
// Add self-decription link to hateoas
resource.add(
linkTo(methodOn(WorkbasketController.class).getWorkbasketAuthorizations(wbAccItem.getWorkbasketId()))
.withSelfRel());
return resource;
return addLinks(resource, wbAccItem);
}
public WorkbasketAccessItem toModel(WorkbasketAccessItemResource wbAccItemRecource) {
@ -41,4 +40,24 @@ public class WorkbasketAccessItemMapper {
return wbAccItemModel;
}
WorkbasketAccessItemResource addLinks(WorkbasketAccessItemResource resource, WorkbasketAccessItem wbAccItem)
throws NotAuthorizedException {
resource.add(
linkTo(methodOn(WorkbasketController.class).getWorkbasketAuthorizations(wbAccItem.getWorkbasketId()))
.withRel("getWorkbasketAuthorizations"));
resource.add(
linkTo(methodOn(WorkbasketController.class).createWorkbasketAuthorization(resource))
.withRel("createWorkbasketAuthorization"));
resource.add(
linkTo(methodOn(WorkbasketController.class).updateWorkbasketAuthorization(wbAccItem.getId(), resource))
.withRel("updateWorkbasketAuthorization"));
resource.add(
linkTo(methodOn(WorkbasketController.class).setWorkbasketAuthorizations(wbAccItem.getWorkbasketId(),
Arrays.asList(resource)))
.withRel("setWorkbasketAuthorizations"));
resource.add(
linkTo(methodOn(WorkbasketController.class).deleteWorkbasketAuthorization(wbAccItem.getId()))
.withRel("deleteWorkbasketAuthorization"));
return resource;
}
}

View File

@ -1,5 +1,10 @@
package pro.taskana.rest.resource.mapper;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -12,6 +17,7 @@ import pro.taskana.WorkbasketService;
import pro.taskana.WorkbasketSummary;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.exceptions.WorkbasketNotFoundException;
import pro.taskana.rest.WorkbasketDefinitionController;
import pro.taskana.rest.resource.WorkbasketAccessItemResource;
import pro.taskana.rest.resource.WorkbasketDefinition;
@ -29,21 +35,45 @@ public class WorkbasketDefinitionMapper {
/**
* maps the distro targets to their id to remove overhead.
* @param basket {@link Workbasket} which will be converted
* @return a {@link WorkbasketDefinition}, containing the {@code basket},
* its ditribution targets and its authorizations
* @throws NotAuthorizedException if the user is not authorized
* @throws WorkbasketNotFoundException if {@code basket} is an unknown workbasket
*
* @param basket
* {@link Workbasket} which will be converted
* @return a {@link WorkbasketDefinition}, containing the {@code basket}, its ditribution targets and its
* authorizations
* @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<WorkbasketAccessItemResource> authorizations = workbasketService.getWorkbasketAuthorizations(
basket.getKey()).stream()
.map(workbasketAccessItemMapper::toResource)
.collect(Collectors.toList());
Set<String> distroTargets = workbasketService.getDistributionTargets(basket.getId()).stream()
List<WorkbasketAccessItemResource> authorizations = new ArrayList<>();
workbasketService.getWorkbasketAuthorizations(
basket.getKey())
.stream()
.forEach(t -> {
try {
authorizations.add(workbasketAccessItemMapper.toResource(t));
} catch (NotAuthorizedException e) {
e.printStackTrace();
}
});
Set<String> distroTargets = workbasketService.getDistributionTargets(basket.getId())
.stream()
.map(WorkbasketSummary::getId)
.collect(Collectors.toSet());
return new WorkbasketDefinition(workbasketMapper.toResource(basket), distroTargets, authorizations);
WorkbasketDefinition resource = new WorkbasketDefinition(workbasketMapper.toResource(basket), distroTargets,
authorizations);
return addLinks(resource, basket);
}
private WorkbasketDefinition addLinks(WorkbasketDefinition resource, Workbasket workbasket) {
resource.add(
linkTo(methodOn(WorkbasketDefinitionController.class).exportWorkbaskets(workbasket.getDomain()))
.withRel("exportWorkbaskets"));
resource.add(
linkTo(methodOn(WorkbasketDefinitionController.class).importWorkbaskets(Arrays.asList(resource)))
.withRel("importWorkbaskets"));
return resource;
}
}

View File

@ -22,17 +22,15 @@ public class WorkbasketMapper {
@Autowired
private WorkbasketService workbasketService;
public WorkbasketResource toResource(Workbasket wb) {
public WorkbasketResource toResource(Workbasket wb) throws NotAuthorizedException {
WorkbasketResource resource = new WorkbasketResource();
BeanUtils.copyProperties(wb, resource);
//need to be set by hand, since name or type is different
// need to be set by hand, since name or type is different
resource.setWorkbasketId(wb.getId());
resource.setModified(wb.getModified().toString());
resource.setCreated(wb.getCreated().toString());
// Add self-decription link to hateoas
resource.add(linkTo(methodOn(WorkbasketController.class).getWorkbasket(wb.getId())).withSelfRel());
return resource;
return addLinks(resource, wb);
}
public Workbasket toModel(WorkbasketResource wbResource) throws NotAuthorizedException {
@ -44,4 +42,17 @@ public class WorkbasketMapper {
workbasket.setCreated(Instant.parse(wbResource.created));
return workbasket;
}
private WorkbasketResource addLinks(WorkbasketResource resource, Workbasket wb) throws NotAuthorizedException {
resource.add(linkTo(methodOn(WorkbasketController.class).getWorkbasket(wb.getId())).withSelfRel());
resource
.add(linkTo(methodOn(WorkbasketController.class).createWorkbasket(resource)).withRel("createWorkbasket"));
resource
.add(linkTo(methodOn(WorkbasketController.class).updateWorkbasket(wb.getId(), resource))
.withRel("updateWorkbasket"));
resource
.add(linkTo(methodOn(WorkbasketController.class).deleteWorkbasket(wb.getId()))
.withRel("deleteWorkbasket"));
return resource;
}
}

View File

@ -16,12 +16,16 @@ public class WorkbasketSummaryMapper {
public WorkbasketSummaryResource toResource(WorkbasketSummary summary) {
WorkbasketSummaryResource resource = new WorkbasketSummaryResource();
BeanUtils.copyProperties(summary, resource);
//named different so needs to be set by hand
// named different so needs to be set by hand
resource.setWorkbasketId(summary.getId());
// Add self reference
resource.add(linkTo(methodOn(WorkbasketController.class).getWorkbasket(summary.getId())).withSelfRel());
return resource;
return addLinks(resource, summary);
}
private WorkbasketSummaryResource addLinks(WorkbasketSummaryResource resource, WorkbasketSummary summary) {
resource.add(linkTo(WorkbasketController.class).slash(summary.getId()).withSelfRel());
resource.add(linkTo(methodOn(WorkbasketController.class).getDistributionTargetsForWorkbasketId(summary.getId()))
.withRel("getDistributionTargetsForWorkbasketId"));
return resource;
}
}

View File

@ -10,6 +10,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
import pro.taskana.WorkbasketAccessItem;
import pro.taskana.WorkbasketService;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.rest.RestApplication;
import pro.taskana.rest.resource.WorkbasketAccessItemResource;
@ -18,12 +19,14 @@ import pro.taskana.rest.resource.WorkbasketAccessItemResource;
@WebAppConfiguration
public class WorkbasketAccessItemMapperTest {
@Autowired WorkbasketAccessItemMapper workbasketAccessItemMapper;
@Autowired WorkbasketService workbasketService;
@Autowired
WorkbasketAccessItemMapper workbasketAccessItemMapper;
@Autowired
WorkbasketService workbasketService;
@Test
public void workBasketAccessItemToResourcePropertiesEqual() {
//given
public void workBasketAccessItemToResourcePropertiesEqual() throws NotAuthorizedException {
// given
WorkbasketAccessItem accessItem = workbasketService.newWorkbasketAccessItem("1", "2");
accessItem.setPermDistribute(false);
accessItem.setPermOpen(true);
@ -42,16 +45,16 @@ public class WorkbasketAccessItemMapperTest {
accessItem.setPermCustom10(true);
accessItem.setPermCustom11(true);
accessItem.setPermCustom12(true);
//when
// when
WorkbasketAccessItemResource resource = workbasketAccessItemMapper.toResource(
accessItem);
//then
// then
testEquality(accessItem, resource);
}
@Test
public void workBasketAccessItemToModelPropertiesEqual() {
//given
// given
WorkbasketAccessItemResource resource = new WorkbasketAccessItemResource();
resource.setAccessId("10");
resource.setAccessItemId("120");
@ -73,9 +76,9 @@ public class WorkbasketAccessItemMapperTest {
resource.setPermCustom10(false);
resource.setPermCustom11(true);
resource.setPermCustom12(false);
//when
// when
WorkbasketAccessItem accessItem = workbasketAccessItemMapper.toModel(resource);
//then
// then
testEquality(accessItem, resource);
}

View File

@ -29,7 +29,7 @@ public class WorkbasketMapperTest {
WorkbasketMapper workbasketMapper;
@Test
public void workbasketToResource() {
public void workbasketToResource() throws NotAuthorizedException {
// given
Workbasket workbasket = workbasketService.newWorkbasket("1", "DOMAIN_A");
((WorkbasketImpl) workbasket).setId("ID");