-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #141 from kbss-cvut/feature/fta-fmea-ui-507-add-fa…
…ult-tree-summary-fulltext-search Add fulltext search and paging support for fault tree summary api
- Loading branch information
Showing
10 changed files
with
465 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
src/main/java/cz/cvut/kbss/analysis/controller/event/PaginatedResultRetrievedEvent.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package cz.cvut.kbss.analysis.controller.event; | ||
|
||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.springframework.context.ApplicationEvent; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.web.util.UriComponentsBuilder; | ||
|
||
/** | ||
* Fired when a paginated result is retrieved by a REST controller, so that HATEOAS headers can be added to the | ||
* response. | ||
*/ | ||
public class PaginatedResultRetrievedEvent extends ApplicationEvent { | ||
|
||
private final UriComponentsBuilder uriBuilder; | ||
private final HttpServletResponse response; | ||
private final Page<?> page; | ||
|
||
public PaginatedResultRetrievedEvent(Object source, UriComponentsBuilder uriBuilder, HttpServletResponse response, | ||
Page<?> page) { | ||
super(source); | ||
this.uriBuilder = uriBuilder; | ||
this.response = response; | ||
this.page = page; | ||
} | ||
|
||
public UriComponentsBuilder getUriBuilder() { | ||
return uriBuilder; | ||
} | ||
|
||
public HttpServletResponse getResponse() { | ||
return response; | ||
} | ||
|
||
public Page<?> getPage() { | ||
return page; | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
src/main/java/cz/cvut/kbss/analysis/controller/handler/HateoasPagingListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package cz.cvut.kbss.analysis.controller.handler; | ||
|
||
import cz.cvut.kbss.analysis.controller.event.PaginatedResultRetrievedEvent; | ||
import cz.cvut.kbss.analysis.controller.util.HttpPaginationLink; | ||
import cz.cvut.kbss.analysis.util.Constants; | ||
import org.springframework.context.ApplicationListener; | ||
import org.springframework.data.domain.Page; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.util.UriComponentsBuilder; | ||
|
||
/** | ||
* Generates HATEOAS paging headers based on the paginated result retrieved by a REST controller. | ||
*/ | ||
@Component | ||
public class HateoasPagingListener implements ApplicationListener<PaginatedResultRetrievedEvent> { | ||
|
||
@Override | ||
public void onApplicationEvent(PaginatedResultRetrievedEvent event) { | ||
final Page<?> page = event.getPage(); | ||
final LinkHeader header = new LinkHeader(); | ||
if (!page.isEmpty() || page.getTotalPages() > 0) { | ||
// Always add first and last links, even when there is just one page. This allows clients to know where the limits | ||
// are | ||
header.addLink(generateFirstPageLink(page, event.getUriBuilder()), HttpPaginationLink.FIRST); | ||
header.addLink(generateLastPageLink(page, event.getUriBuilder()), HttpPaginationLink.LAST); | ||
} | ||
if (page.hasNext()) { | ||
header.addLink(generateNextPageLink(page, event.getUriBuilder()), HttpPaginationLink.NEXT); | ||
} | ||
if (page.hasPrevious()) { | ||
header.addLink(generatePreviousPageLink(page, event.getUriBuilder()), HttpPaginationLink.PREVIOUS); | ||
} | ||
if (header.hasLinks()) { | ||
event.getResponse().addHeader(HttpHeaders.LINK, header.toString()); | ||
} | ||
event.getResponse().addHeader(Constants.X_TOTAL_COUNT_HEADER, Long.toString(page.getTotalElements())); | ||
} | ||
|
||
private String generateNextPageLink(Page<?> page, UriComponentsBuilder uriBuilder) { | ||
return uriBuilder.replaceQueryParam(Constants.PAGE_PARAM, page.getNumber() + 1) | ||
.replaceQueryParam(Constants.PAGE_SIZE_PARAM, page.getSize()) | ||
.build().encode().toUriString(); | ||
} | ||
|
||
private String generatePreviousPageLink(Page<?> page, UriComponentsBuilder uriBuilder) { | ||
return uriBuilder.replaceQueryParam(Constants.PAGE_PARAM, page.getNumber() - 1) | ||
.replaceQueryParam(Constants.PAGE_SIZE_PARAM, page.getSize()) | ||
.build().encode().toUriString(); | ||
} | ||
|
||
private String generateFirstPageLink(Page<?> page, UriComponentsBuilder uriBuilder) { | ||
return uriBuilder.replaceQueryParam(Constants.PAGE_PARAM, 0) | ||
.replaceQueryParam(Constants.PAGE_SIZE_PARAM, page.getSize()) | ||
.build().encode().toUriString(); | ||
} | ||
|
||
private String generateLastPageLink(Page<?> page, UriComponentsBuilder uriBuilder) { | ||
return uriBuilder.replaceQueryParam(Constants.PAGE_PARAM, page.getTotalPages() - 1) | ||
.replaceQueryParam(Constants.PAGE_SIZE_PARAM, page.getSize()) | ||
.build().encode().toUriString(); | ||
} | ||
|
||
private static class LinkHeader { | ||
|
||
private final StringBuilder linkBuilder = new StringBuilder(); | ||
|
||
private void addLink(String url, HttpPaginationLink type) { | ||
if (!linkBuilder.isEmpty()) { | ||
linkBuilder.append(", "); | ||
} | ||
linkBuilder.append('<').append(url).append('>').append("; ").append("rel=\"").append(type.getName()) | ||
.append('"'); | ||
} | ||
|
||
private boolean hasLinks() { | ||
return !linkBuilder.isEmpty(); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return linkBuilder.toString(); | ||
} | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
src/main/java/cz/cvut/kbss/analysis/controller/util/FaultTreeFilterMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package cz.cvut.kbss.analysis.controller.util; | ||
|
||
import cz.cvut.kbss.analysis.dao.util.FaultTreeFilterParams; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.util.LinkedMultiValueMap; | ||
import org.springframework.util.MultiValueMap; | ||
|
||
import java.util.*; | ||
|
||
/** | ||
* Maps query parameters to {@link FaultTreeFilterParams} instances. | ||
*/ | ||
@Slf4j | ||
public class FaultTreeFilterMapper { | ||
|
||
private static final String SNS_LABEL_PARAM = "snsLabel"; | ||
|
||
private static final String LABEL_PARAM = "label"; | ||
|
||
|
||
/** | ||
* Maps the specified single parameter and value to a new {@link FaultTreeFilterParams} instance. | ||
* | ||
* @param param Parameter name | ||
* @param value Parameter value | ||
* @return New {@code FaultTreeFilterParams} instance | ||
*/ | ||
public static FaultTreeFilterParams constructFaultTreeFilter(String param, String value) { | ||
return constructFaultTreeFilter(new LinkedMultiValueMap<>(Map.of(param, List.of(value)))); | ||
} | ||
|
||
public static FaultTreeFilterParams constructFaultTreeFilter(MultiValueMap<String, String> params) { | ||
final FaultTreeFilterParams result = new FaultTreeFilterParams(); | ||
return constructFaultTreeFilter(result, new LinkedMultiValueMap<>(params)); | ||
} | ||
|
||
/** | ||
* Maps the specified parameters to a new {@link FaultTreeFilterParams} instance. | ||
* | ||
* @param params Request parameters to map | ||
* @return New {@code FaultTreeFilterParams} instance | ||
*/ | ||
public static FaultTreeFilterParams constructFaultTreeFilter(final FaultTreeFilterParams result, MultiValueMap<String, String> params) { | ||
Objects.requireNonNull(params); | ||
getSingleValue(SNS_LABEL_PARAM, params).ifPresent(s -> result.setSnsLabel(s)); | ||
getSingleValue(LABEL_PARAM, params).ifPresent(s -> result.setLabel(s)); | ||
|
||
return result; | ||
} | ||
|
||
private static Optional<String> getSingleValue(String param, MultiValueMap<String, String> source) { | ||
final List<String> values = source.getOrDefault(param, Collections.emptyList()); | ||
if (values.isEmpty()) { | ||
return Optional.empty(); | ||
} | ||
if (values.size() > 1) { | ||
log.warn("Found multiple values of parameter '{}'. Using the first one - '{}'.", param, values.get(0)); | ||
} | ||
return Optional.of(values.get(0)); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/cz/cvut/kbss/analysis/controller/util/HttpPaginationLink.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package cz.cvut.kbss.analysis.controller.util; | ||
|
||
/** | ||
* Types of HTTP pagination links. | ||
*/ | ||
public enum HttpPaginationLink { | ||
NEXT("next"), PREVIOUS("prev"), FIRST("first"), LAST("last"); | ||
|
||
private final String name; | ||
|
||
HttpPaginationLink(String name) { | ||
this.name = name; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
} |
104 changes: 104 additions & 0 deletions
104
src/main/java/cz/cvut/kbss/analysis/controller/util/PagingUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package cz.cvut.kbss.analysis.controller.util; | ||
|
||
import cz.cvut.kbss.analysis.model.FaultTree; | ||
import cz.cvut.kbss.analysis.util.Constants; | ||
import org.springframework.data.domain.PageRequest; | ||
import org.springframework.data.domain.Pageable; | ||
import org.springframework.data.domain.Sort; | ||
import org.springframework.util.MultiValueMap; | ||
|
||
import java.util.Comparator; | ||
import java.util.Date; | ||
import java.util.Iterator; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
public class PagingUtils { | ||
|
||
/** | ||
* Prefix indicating ascending sort order. | ||
*/ | ||
public static final char SORT_ASC = '+'; | ||
|
||
/** | ||
* Prefix indicating descending sort order. | ||
*/ | ||
public static final char SORT_DESC = '-'; | ||
|
||
|
||
|
||
private PagingUtils() { | ||
throw new AssertionError(); | ||
} | ||
|
||
/** | ||
* Resolves paging and sorting configuration from the specified request parameters. | ||
* <p> | ||
* If no paging and filtering info is specified, an {@link Pageable#unpaged()} object is returned. | ||
* <p> | ||
* Note that for sorting, {@literal +} should be used before sorting property name to specify ascending order, | ||
* {@literal -} for descending order, for example, {@literal -date} indicates sorting by date in descending order. | ||
* | ||
* @param params Request parameters | ||
* @return {@code Pageable} containing values resolved from the params or defaults | ||
*/ | ||
public static Pageable resolvePaging(MultiValueMap<String, String> params) { | ||
Sort sort; | ||
if (params.containsKey(Constants.SORT_PARAM)) { | ||
sort = Sort.by(params.get(Constants.SORT_PARAM).stream().map(sp -> { | ||
if (sp.charAt(0) == SORT_ASC || sp.charAt(0) == SORT_DESC) { | ||
final String property = sp.substring(1); | ||
return sp.charAt(0) == SORT_DESC ? Sort.Order.desc(property) : Sort.Order.asc(property); | ||
} | ||
return Sort.Order.asc(sp); | ||
}).collect(Collectors.toList())); | ||
}else{ | ||
sort = Sort.by( | ||
Sort.Order.desc(Constants.SORT_BY_DATE_PARAM), | ||
Sort.Order.asc(Constants.SORT_BY_SNS_LABEL_PARAM), | ||
Sort.Order.asc(Constants.SORT_BY_LABEL_PARAM) | ||
); | ||
} | ||
|
||
if (params.getFirst(Constants.PAGE_PARAM) == null) | ||
return Pageable.unpaged(sort); | ||
|
||
final int page = Integer.parseInt(params.getFirst(Constants.PAGE_PARAM)); | ||
final int size = Optional.ofNullable(params.getFirst(Constants.PAGE_SIZE_PARAM)).map(Integer::parseInt) | ||
.orElse(Constants.DEFAULT_PAGE_SIZE); | ||
|
||
return PageRequest.of(page, size, sort); | ||
} | ||
|
||
public static Comparator<FaultTree> comparator(Sort sort){ | ||
Iterator<Sort.Order> orders = sort.iterator(); | ||
Comparator<FaultTree> c = null; | ||
while(orders.hasNext()){ | ||
Sort.Order order = orders.next(); | ||
if(c == null) | ||
c = getFunction(order); | ||
else | ||
c.thenComparing(getFunction(order)); | ||
} | ||
return c; | ||
} | ||
|
||
private static Comparator<FaultTree> getFunction(Sort.Order order){ | ||
Comparator<FaultTree> comp = switch (order.getProperty()){ | ||
case Constants.SORT_BY_DATE_PARAM -> Comparator.comparing((FaultTree t) -> | ||
Stream.of(t.getModified(), t.getCreated()) | ||
.filter(d -> d != null).findFirst().orElse(new Date(0))); | ||
case Constants.SORT_BY_LABEL_PARAM -> Comparator.comparing((FaultTree t) -> | ||
Optional.ofNullable(t.getSubsystem()).map(i -> i.getName()).orElse("")); | ||
case Constants.SORT_BY_SNS_LABEL_PARAM -> Comparator.comparing((FaultTree t) -> | ||
Optional.ofNullable(t.getName()).orElse("")); | ||
default -> null; | ||
}; | ||
|
||
return order.getDirection() == Sort.Direction.DESC | ||
? comp.reversed() | ||
: comp; | ||
} | ||
|
||
} |
Oops, something went wrong.