TSK-1085: Hateoas self-links which contain variables are not working for Paged representationmodels

* remove Aspect
* remove PageLinks
* remove AbstractRessourcesAssembler
* update TaskanaPagingAssembler
* update *Assembler implementing TaskanaPagingAssembler for self-links

* update unit-test and test "href-self" part of json
* fix checkstyl-finding
This commit is contained in:
Nikita Kolytschew 2020-08-05 15:48:10 +02:00 committed by Nik
parent 0df2a1e324
commit d0ae7f2150
11 changed files with 91 additions and 183 deletions

View File

@ -4,18 +4,23 @@ import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedModel.PageMetadata;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import pro.taskana.resource.rest.AbstractRessourcesAssembler;
import pro.taskana.simplehistory.rest.TaskHistoryEventController;
import pro.taskana.simplehistory.rest.models.TaskHistoryEventListResource;
import pro.taskana.simplehistory.rest.models.TaskHistoryEventRepresentationModel;
import pro.taskana.spi.history.api.events.task.TaskHistoryEvent;
/** Mapper to convert from a list of TaskHistoryEvent to a TaskHistoryEventResource. */
public class TaskHistoryEventListResourceAssembler extends AbstractRessourcesAssembler {
public class TaskHistoryEventListResourceAssembler {
public TaskHistoryEventListResource toResources(
List<TaskHistoryEvent> historyEvents, PageMetadata pageMetadata) {
@ -27,22 +32,22 @@ public class TaskHistoryEventListResourceAssembler extends AbstractRessourcesAss
TaskHistoryEventListResource pagedResources =
new TaskHistoryEventListResource(resources, pageMetadata);
pagedResources.add(Link.of(this.getOriginal().toUriString()).withSelfRel());
pagedResources.add(Link.of(getBaseUri().toUriString()).withSelfRel());
if (pageMetadata != null) {
pagedResources.add(linkTo(TaskHistoryEventController.class).withRel("allTaskHistoryEvent"));
pagedResources.add(
Link.of(this.getOriginal().replaceQueryParam("page", 1).toUriString())
Link.of(getBaseUri().replaceQueryParam("page", 1).toUriString())
.withRel(IanaLinkRelations.FIRST));
pagedResources.add(
Link.of(
this.getOriginal()
getBaseUri()
.replaceQueryParam("page", pageMetadata.getTotalPages())
.toUriString())
.withRel(IanaLinkRelations.LAST));
if (pageMetadata.getNumber() > 1) {
pagedResources.add(
Link.of(
this.getOriginal()
getBaseUri()
.replaceQueryParam("page", pageMetadata.getNumber() - 1)
.toUriString())
.withRel(IanaLinkRelations.PREV));
@ -50,7 +55,7 @@ public class TaskHistoryEventListResourceAssembler extends AbstractRessourcesAss
if (pageMetadata.getNumber() < pageMetadata.getTotalPages()) {
pagedResources.add(
Link.of(
this.getOriginal()
getBaseUri()
.replaceQueryParam("page", pageMetadata.getNumber() + 1)
.toUriString())
.withRel(IanaLinkRelations.NEXT));
@ -59,4 +64,19 @@ public class TaskHistoryEventListResourceAssembler extends AbstractRessourcesAss
return pagedResources;
}
private UriComponentsBuilder getBaseUri() {
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
UriComponentsBuilder baseUri =
ServletUriComponentsBuilder.fromServletMapping(request).path(request.getRequestURI());
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
for (String value : entry.getValue()) {
baseUri.queryParam(entry.getKey(), value);
}
}
return baseUri;
}
}

View File

@ -19,11 +19,9 @@ import pro.taskana.classification.api.ClassificationService;
import pro.taskana.classification.api.models.ClassificationSummary;
import pro.taskana.classification.internal.models.ClassificationSummaryImpl;
import pro.taskana.classification.rest.models.ClassificationSummaryRepresentationModel;
import pro.taskana.common.rest.Mapping;
import pro.taskana.common.rest.assembler.TaskanaPagingAssembler;
import pro.taskana.common.rest.models.TaskanaPagedModel;
import pro.taskana.common.rest.models.TaskanaPagedModelKeys;
import pro.taskana.resource.rest.PageLinks;
/** EntityModel assembler for {@link ClassificationSummaryRepresentationModel}. */
@Component
@ -98,9 +96,9 @@ public class ClassificationSummaryRepresentationModelAssembler
}
@Override
@PageLinks(Mapping.URL_CLASSIFICATIONS)
public TaskanaPagedModel<ClassificationSummaryRepresentationModel> toPageModel(
Iterable<ClassificationSummary> entities, PageMetadata pageMetadata) {
return TaskanaPagingAssembler.super.toPageModel(entities, pageMetadata);
return addLinksToPagedResource(
TaskanaPagingAssembler.super.toPageModel(entities, pageMetadata));
}
}

View File

@ -1,10 +1,18 @@
package pro.taskana.common.rest.assembler;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.servlet.http.HttpServletRequest;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedModel.PageMetadata;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.server.RepresentationModelAssembler;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import pro.taskana.common.rest.models.TaskanaPagedModel;
import pro.taskana.common.rest.models.TaskanaPagedModelKeys;
@ -25,4 +33,46 @@ public interface TaskanaPagingAssembler<T, D extends RepresentationModel<? super
default TaskanaPagedModel<D> toPageModel(Iterable<T> entities) {
return toPageModel(entities, null);
}
default TaskanaPagedModel<D> addLinksToPagedResource(TaskanaPagedModel<D> model) {
final UriComponentsBuilder original = getBaseUri();
final PageMetadata page = model.getMetadata();
model.add(Link.of(original.toUriString()).withSelfRel());
if (page != null) {
model.add(
Link.of(original.replaceQueryParam("page", 1).toUriString())
.withRel(IanaLinkRelations.FIRST));
model.add(
Link.of(original.replaceQueryParam("page", page.getTotalPages()).toUriString())
.withRel(IanaLinkRelations.LAST));
if (page.getNumber() > 1) {
model.add(
Link.of(original.replaceQueryParam("page", page.getNumber() - 1).toUriString())
.withRel(IanaLinkRelations.PREV));
}
if (page.getNumber() < page.getTotalPages()) {
model.add(
Link.of(original.replaceQueryParam("page", page.getNumber() + 1).toUriString())
.withRel(IanaLinkRelations.NEXT));
}
}
return model;
}
default UriComponentsBuilder getBaseUri() {
final HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
final UriComponentsBuilder baseUri =
ServletUriComponentsBuilder.fromServletMapping(request).path(request.getRequestURI());
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
for (String value : entry.getValue()) {
baseUri.queryParam(entry.getKey(), value);
}
}
return baseUri;
}
}

View File

@ -1,68 +0,0 @@
package pro.taskana.resource.rest;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedModel.PageMetadata;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import pro.taskana.common.rest.models.PagedResources;
/**
* Abstract resources assembler for taskana REST controller with pageable resources. This method is
* deprecated, it can be removed after fixing taskana-simple-history references
*/
// TODO: @Deprecated
// TODO: please remove spring-webmvc dependency
public abstract class AbstractRessourcesAssembler {
UriComponentsBuilder original = getBuilderForOriginalUri();
public AbstractRessourcesAssembler() {}
public UriComponentsBuilder getOriginal() {
return original;
}
protected static UriComponentsBuilder getBuilderForOriginalUri() {
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
UriComponentsBuilder baseUri =
ServletUriComponentsBuilder.fromServletMapping(request).path(request.getRequestURI());
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
String[] var4 = entry.getValue();
for (String value : var4) {
baseUri.queryParam(entry.getKey(), value);
}
}
return baseUri;
}
protected PagedResources<?> addPageLinks(
PagedResources<?> pagedResources, PageMetadata pageMetadata) {
UriComponentsBuilder original = getBuilderForOriginalUri();
pagedResources.add(
(Link.of(original.replaceQueryParam("page", 1).toUriString())).withRel("first"));
pagedResources.add(
(Link.of(original.replaceQueryParam("page", pageMetadata.getTotalPages()).toUriString()))
.withRel("last"));
if (pageMetadata.getNumber() > 1L) {
pagedResources.add(
(Link.of(original.replaceQueryParam("page", pageMetadata.getNumber() - 1L).toUriString()))
.withRel("prev"));
}
if (pageMetadata.getNumber() < pageMetadata.getTotalPages()) {
pagedResources.add(
(Link.of(original.replaceQueryParam("page", pageMetadata.getNumber() + 1L).toUriString()))
.withRel("next"));
}
return pagedResources;
}
}

View File

@ -1,14 +0,0 @@
package pro.taskana.resource.rest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Annotation to generate HATEOAS Links for paged list resources. */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PageLinks {
String value();
}

View File

@ -1,67 +0,0 @@
package pro.taskana.resource.rest;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedModel.PageMetadata;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Implementation of the PageLinks annotation to generate HATEOAS Links for paged list resources.
*/
@Configuration
@Aspect
public class PageLinksAspect {
@SuppressWarnings("unchecked")
@Around("@annotation(pro.taskana.resource.rest.PageLinks) && args(data, page, ..)")
public <T extends RepresentationModel<? extends T> & ProceedingJoinPoint>
RepresentationModel<T> addLinksToPageResource(
ProceedingJoinPoint joinPoint, List<?> data, PageMetadata page) throws Throwable {
final UriComponentsBuilder original = originalUri();
RepresentationModel<T> resourceSupport = (RepresentationModel<T>) joinPoint.proceed();
resourceSupport.add(Link.of(original.toUriString()).withSelfRel());
if (page != null) {
resourceSupport.add(
Link.of(original.replaceQueryParam("page", 1).toUriString())
.withRel(IanaLinkRelations.FIRST));
resourceSupport.add(
Link.of(original.replaceQueryParam("page", page.getTotalPages()).toUriString())
.withRel(IanaLinkRelations.LAST));
if (page.getNumber() > 1) {
resourceSupport.add(
Link.of(original.replaceQueryParam("page", page.getNumber() - 1).toUriString())
.withRel(IanaLinkRelations.PREV));
}
if (page.getNumber() < page.getTotalPages()) {
resourceSupport.add(
Link.of(original.replaceQueryParam("page", page.getNumber() + 1).toUriString())
.withRel(IanaLinkRelations.NEXT));
}
}
return resourceSupport;
}
private UriComponentsBuilder originalUri() {
final HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
final UriComponentsBuilder baseUri =
ServletUriComponentsBuilder.fromServletMapping(request).path(request.getRequestURI());
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
for (String value : entry.getValue()) {
baseUri.queryParam(entry.getKey(), value);
}
}
return baseUri;
}
}

View File

@ -10,11 +10,9 @@ import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.rest.Mapping;
import pro.taskana.common.rest.assembler.TaskanaPagingAssembler;
import pro.taskana.common.rest.models.TaskanaPagedModel;
import pro.taskana.common.rest.models.TaskanaPagedModelKeys;
import pro.taskana.resource.rest.PageLinks;
import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.models.TaskComment;
import pro.taskana.task.internal.models.TaskCommentImpl;
@ -70,9 +68,9 @@ public class TaskCommentRepresentationModelAssembler
}
@Override
@PageLinks(Mapping.URL_TASK_COMMENTS)
public TaskanaPagedModel<TaskCommentRepresentationModel> toPageModel(
Iterable<TaskComment> taskComments, PageMetadata pageMetadata) {
return TaskanaPagingAssembler.super.toPageModel(taskComments, pageMetadata);
return addLinksToPagedResource(
TaskanaPagingAssembler.super.toPageModel(taskComments, pageMetadata));
}
}

View File

@ -10,11 +10,9 @@ import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import pro.taskana.classification.rest.assembler.ClassificationSummaryRepresentationModelAssembler;
import pro.taskana.common.rest.Mapping;
import pro.taskana.common.rest.assembler.TaskanaPagingAssembler;
import pro.taskana.common.rest.models.TaskanaPagedModel;
import pro.taskana.common.rest.models.TaskanaPagedModelKeys;
import pro.taskana.resource.rest.PageLinks;
import pro.taskana.task.api.TaskCustomField;
import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.models.TaskSummary;
@ -143,10 +141,10 @@ public class TaskSummaryRepresentationModelAssembler
return taskSummary;
}
@PageLinks(Mapping.URL_TASKS)
public TaskanaPagedModel<TaskSummaryRepresentationModel> toPageModel(
List<TaskSummary> taskSummaries, PageMetadata pageMetadata) {
return TaskanaPagingAssembler.super.toPageModel(taskSummaries, pageMetadata);
return addLinksToPagedResource(
TaskanaPagingAssembler.super.toPageModel(taskSummaries, pageMetadata));
}
@Override

View File

@ -220,11 +220,10 @@ public class WorkbasketController extends AbstractPagingController {
getWorkbasketAccessItems(@PathVariable(value = "workbasketId") String workbasketId)
throws NotAuthorizedException, WorkbasketNotFoundException {
LOGGER.debug("Entry to getWorkbasketAccessItems(workbasketId= {})", workbasketId);
ResponseEntity<TaskanaPagedModel<WorkbasketAccessItemRepresentationModel>> result;
List<WorkbasketAccessItem> accessItems =
workbasketService.getWorkbasketAccessItems(workbasketId);
result =
final ResponseEntity<TaskanaPagedModel<WorkbasketAccessItemRepresentationModel>> result =
ResponseEntity.ok(
workbasketAccessItemRepresentationModelAssembler.toPageModelForSingleWorkbasket(
workbasketId, accessItems, null));

View File

@ -11,11 +11,9 @@ import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import pro.taskana.common.api.exceptions.NotAuthorizedException;
import pro.taskana.common.rest.Mapping;
import pro.taskana.common.rest.assembler.TaskanaPagingAssembler;
import pro.taskana.common.rest.models.TaskanaPagedModel;
import pro.taskana.common.rest.models.TaskanaPagedModelKeys;
import pro.taskana.resource.rest.PageLinks;
import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.exceptions.WorkbasketNotFoundException;
@ -105,19 +103,16 @@ public class WorkbasketAccessItemRepresentationModelAssembler
throws NotAuthorizedException, WorkbasketNotFoundException {
TaskanaPagedModel<WorkbasketAccessItemRepresentationModel> pageModel =
toPageModel(workbasketAccessItems, pageMetadata);
pageModel.add(
linkTo(methodOn(WorkbasketController.class).getWorkbasketAccessItems(workbasketId))
.withSelfRel());
pageModel.add(
linkTo(methodOn(WorkbasketController.class).getWorkbasket(workbasketId))
.withRel("workbasket"));
return pageModel;
}
@PageLinks(Mapping.URL_WORKBASKET_ACCESS_ITEMS)
public TaskanaPagedModel<WorkbasketAccessItemRepresentationModel> toPageModel(
List<WorkbasketAccessItem> workbasketAccessItems, PageMetadata pageMetadata) {
return TaskanaPagingAssembler.super.toPageModel(workbasketAccessItems, pageMetadata);
return addLinksToPagedResource(
TaskanaPagingAssembler.super.toPageModel(workbasketAccessItems, pageMetadata));
}
@Override

View File

@ -10,11 +10,9 @@ import org.springframework.hateoas.PagedModel.PageMetadata;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import pro.taskana.common.rest.Mapping;
import pro.taskana.common.rest.assembler.TaskanaPagingAssembler;
import pro.taskana.common.rest.models.TaskanaPagedModel;
import pro.taskana.common.rest.models.TaskanaPagedModelKeys;
import pro.taskana.resource.rest.PageLinks;
import pro.taskana.workbasket.api.WorkbasketCustomField;
import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.models.WorkbasketSummary;
@ -86,13 +84,12 @@ public class WorkbasketSummaryRepresentationModelAssembler
}
@Override
@PageLinks(Mapping.URL_WORKBASKET)
public TaskanaPagedModel<WorkbasketSummaryRepresentationModel> toPageModel(
Iterable<WorkbasketSummary> entities, PageMetadata pageMetadata) {
return TaskanaPagingAssembler.super.toPageModel(entities, pageMetadata);
return addLinksToPagedResource(
TaskanaPagingAssembler.super.toPageModel(entities, pageMetadata));
}
@PageLinks(Mapping.URL_WORKBASKET_ID_DISTRIBUTION)
public TaskanaPagedModel<WorkbasketSummaryRepresentationModel> toDistributionTargetPageModel(
List<WorkbasketSummary> workbasketSummaries, PageMetadata pageMetadata) {
return workbasketSummaries.stream()
@ -100,6 +97,8 @@ public class WorkbasketSummaryRepresentationModelAssembler
.collect(
Collectors.collectingAndThen(
Collectors.toList(),
list -> new TaskanaPagedModel<>(DISTRIBUTION_TARGETS, list, pageMetadata)));
list ->
addLinksToPagedResource(
new TaskanaPagedModel<>(DISTRIBUTION_TARGETS, list, pageMetadata))));
}
}