Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paging, sorting, filtering #46

Merged
merged 4 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ static CorsConfigurationSource createCorsConfiguration(ConfigReader configReader
corsConfiguration.addExposedHeader(HttpHeaders.AUTHORIZATION);
corsConfiguration.addExposedHeader(HttpHeaders.LOCATION);
corsConfiguration.addExposedHeader(HttpHeaders.CONTENT_DISPOSITION);
corsConfiguration.addExposedHeader(HttpHeaders.LINK);

final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
Expand Down
36 changes: 35 additions & 1 deletion src/main/java/cz/cvut/kbss/study/model/RecordPhase.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,46 @@ public String getIri() {
* @return matching {@code RecordPhase}
* @throws IllegalArgumentException When no matching phase is found
*/
public static RecordPhase fromString(String iri) {
public static RecordPhase fromIri(String iri) {
for (RecordPhase p : values()) {
if (p.getIri().equals(iri)) {
return p;
}
}
throw new IllegalArgumentException("Unknown record phase identifier '" + iri + "'.");
}

/**
* Returns {@link RecordPhase} with the specified constant name.
*
* @param name record phase name
* @return matching {@code RecordPhase}
* @throws IllegalArgumentException When no matching phase is found
*/
public static RecordPhase fromName(String name) {
for (RecordPhase p : values()) {
if (p.name().equalsIgnoreCase(name)) {
return p;
}
}
throw new IllegalArgumentException("Unknown record phase '" + name + "'.");
}

/**
* Returns a {@link RecordPhase} with the specified IRI or constant name.
* <p>
* This function first tries to find the enum constant by IRI. If it is not found, constant name matching is
* attempted.
*
* @param identification Constant IRI or name to find match by
* @return matching {@code RecordPhase}
* @throws IllegalArgumentException When no matching phase is found
*/
public static RecordPhase fromIriOrName(String identification) {
try {
return fromIri(identification);
} catch (IllegalArgumentException e) {
return fromName(identification);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import cz.cvut.kbss.study.model.User;
import cz.cvut.kbss.study.model.Vocabulary;
import cz.cvut.kbss.study.util.Constants;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

import java.net.URI;
import java.util.List;
import java.util.Objects;

@Repository
Expand All @@ -35,7 +37,7 @@ public ActionHistory findByKey(String key) {
}
}

public List<ActionHistory> findAllWithParams(String typeFilter, User author, int pageNumber) {
public Page<ActionHistory> findAllWithParams(String typeFilter, User author, Pageable pageSpec) {
String params;
if (typeFilter == null && author == null) {
params = " } ";
Expand All @@ -52,10 +54,11 @@ public List<ActionHistory> findAllWithParams(String typeFilter, User author, int
params + "ORDER BY DESC(?timestamp)",
ActionHistory.class)
.setParameter("type", typeUri)
.setParameter("isCreated", URI.create(Vocabulary.s_p_created))
.setFirstResult((pageNumber - 1) * Constants.DEFAULT_PAGE_SIZE)
.setMaxResults(Constants.DEFAULT_PAGE_SIZE + 1);

.setParameter("isCreated", URI.create(Vocabulary.s_p_created));
if (pageSpec.isPaged()) {
q.setFirstResult((int) pageSpec.getOffset());
q.setMaxResults(pageSpec.getPageSize());
}
if (author != null) {
q.setParameter("hasOwner", URI.create(Vocabulary.s_p_has_owner))
.setParameter("author", author.getUri());
Expand All @@ -64,6 +67,6 @@ public List<ActionHistory> findAllWithParams(String typeFilter, User author, int
q.setParameter("typeFilter", typeFilter, Constants.PU_LANGUAGE)
.setParameter("isType", URI.create(Vocabulary.s_p_action_type));
}
return q.getResultList();
return new PageImpl<>(q.getResultList(), pageSpec, 0L);
}
}
29 changes: 24 additions & 5 deletions src/main/java/cz/cvut/kbss/study/rest/ActionHistoryController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@
import cz.cvut.kbss.study.exception.NotFoundException;
import cz.cvut.kbss.study.model.ActionHistory;
import cz.cvut.kbss.study.model.User;
import cz.cvut.kbss.study.rest.event.PaginatedResultRetrievedEvent;
import cz.cvut.kbss.study.rest.util.RestUtils;
import cz.cvut.kbss.study.security.SecurityConstants;
import cz.cvut.kbss.study.service.ActionHistoryService;
import cz.cvut.kbss.study.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Collections;
import java.util.List;
Expand All @@ -23,10 +36,13 @@ public class ActionHistoryController extends BaseController {

private final UserService userService;

private final ApplicationEventPublisher eventPublisher;

public ActionHistoryController(ActionHistoryService actionHistoryService,
UserService userService) {
UserService userService, ApplicationEventPublisher eventPublisher) {
this.actionHistoryService = actionHistoryService;
this.userService = userService;
this.eventPublisher = eventPublisher;
}

@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
Expand All @@ -42,7 +58,8 @@ public void create(@RequestBody ActionHistory actionHistory) {
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public List<ActionHistory> getActions(@RequestParam(value = "author", required = false) String authorUsername,
@RequestParam(value = "type", required = false) String type,
@RequestParam(value = "page") int pageNumber) {
@RequestParam MultiValueMap<String, String> params,
UriComponentsBuilder uriBuilder, HttpServletResponse response) {
User author = null;
if (authorUsername != null) {
try {
Expand All @@ -51,7 +68,9 @@ public List<ActionHistory> getActions(@RequestParam(value = "author", required =
return Collections.emptyList();
}
}
return actionHistoryService.findAllWithParams(type, author, pageNumber);
final Page<ActionHistory> result = actionHistoryService.findAllWithParams(type, author, RestUtils.resolvePaging(params));
eventPublisher.publishEvent(new PaginatedResultRetrievedEvent(this, uriBuilder, response, result));
return result.getContent();
}

@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "')")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ public ResponseEntity<Void> createRecord(@RequestBody PatientRecord record) {

@PostMapping(value = "/import", consumes = MediaType.APPLICATION_JSON_VALUE)
public RecordImportResult importRecords(@RequestBody List<PatientRecord> records,
@RequestParam(name = "phase", required = false) String phaseIri) {
@RequestParam(name = "phase", required = false) String phase) {
final RecordImportResult importResult;
if (phaseIri != null) {
final RecordPhase targetPhase = RecordPhase.fromString(phaseIri);
if (phase != null) {
final RecordPhase targetPhase = RecordPhase.fromIriOrName(phase);
importResult = recordService.importRecords(records, targetPhase);
} else {
importResult = recordService.importRecords(records);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ public class HateoasPagingListener implements ApplicationListener<PaginatedResul
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);
header.addLink(generateLastPageLink(page, event.getUriBuilder()), HttpPaginationLink.LAST);
}
if (page.hasPrevious()) {
header.addLink(generatePreviousPageLink(page, event.getUriBuilder()), HttpPaginationLink.PREVIOUS);
header.addLink(generateFirstPageLink(page, event.getUriBuilder()), HttpPaginationLink.FIRST);
}
if (header.hasLinks()) {
event.getResponse().addHeader(HttpHeaders.LINK, header.toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cz.cvut.kbss.study.rest.util;

import cz.cvut.kbss.study.model.RecordPhase;
import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams;
import cz.cvut.kbss.study.rest.exception.BadRequestException;
import org.slf4j.Logger;
Expand All @@ -10,11 +11,11 @@
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Maps query parameters to {@link RecordFilterParams} instances.
Expand Down Expand Up @@ -66,7 +67,8 @@ public static RecordFilterParams constructRecordFilter(MultiValueMap<String, Str
}
});
getSingleValue(INSTITUTION_KEY_PARAM, params).ifPresent(result::setInstitutionKey);
result.setPhaseIds(new HashSet<>(params.getOrDefault(PHASE_ID_PARAM, Collections.emptyList())));
result.setPhaseIds(params.getOrDefault(PHASE_ID_PARAM, Collections.emptyList()).stream()
.map(s -> RecordPhase.fromIriOrName(s).getIri()).collect(Collectors.toSet()));
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import cz.cvut.kbss.study.model.ActionHistory;
import cz.cvut.kbss.study.model.User;

import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface ActionHistoryService extends BaseService<ActionHistory> {

ActionHistory findByKey(String key);

List <ActionHistory> findAllWithParams(String type, User author, int pageNumber);
Page<ActionHistory> findAllWithParams(String type, User author, Pageable pageSpec);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import cz.cvut.kbss.study.persistence.dao.ActionHistoryDao;
import cz.cvut.kbss.study.persistence.dao.GenericDao;
import cz.cvut.kbss.study.service.ActionHistoryService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class RepositoryActionHistoryService extends BaseRepositoryService<ActionHistory> implements ActionHistoryService {

Expand All @@ -32,7 +32,7 @@ public ActionHistory findByKey(String key) {

@Transactional(readOnly = true)
@Override
public List<ActionHistory> findAllWithParams(String type, User author, int pageNumber) {
return actionHistoryDao.findAllWithParams(type, author, pageNumber);
public Page<ActionHistory> findAllWithParams(String type, User author, Pageable pageSpec) {
return actionHistoryDao.findAllWithParams(type, author, pageSpec);
}
}
44 changes: 38 additions & 6 deletions src/test/java/cz/cvut/kbss/study/model/RecordPhaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,51 @@
class RecordPhaseTest {

@Test
void fromStringReturnsMatchingRecordPhase() {
void fromIriReturnsMatchingRecordPhase() {
for (RecordPhase p : RecordPhase.values()) {
assertEquals(p, RecordPhase.fromString(p.getIri()));
assertEquals(p, RecordPhase.fromIri(p.getIri()));
}
}

@Test
void fromStringThrowsIllegalArgumentForUnknownPhaseIri() {
assertThrows(IllegalArgumentException.class, () -> RecordPhase.fromString(Generator.generateUri().toString()));
void fromIriThrowsIllegalArgumentForUnknownPhaseIri() {
assertThrows(IllegalArgumentException.class, () -> RecordPhase.fromIri(Generator.generateUri().toString()));
}

@Test
void fromStringThrowsIllegalArgumentForNullArgument() {
assertThrows(IllegalArgumentException.class, () -> RecordPhase.fromString(null));
void fromIriThrowsIllegalArgumentForNullArgument() {
assertThrows(IllegalArgumentException.class, () -> RecordPhase.fromIri(null));
}

@Test
void fromNameReturnsMatchingRecordPhase() {
for (RecordPhase p : RecordPhase.values()) {
assertEquals(p, RecordPhase.fromName(p.name()));
}
}

@Test
void fromNameMatchesIgnoringCase() {
for (RecordPhase p : RecordPhase.values()) {
assertEquals(p, RecordPhase.fromName(p.name().toUpperCase()));
}
}

@Test
void fromNameThrowsIllegalArgumentForUnknownPhaseIri() {
assertThrows(IllegalArgumentException.class, () -> RecordPhase.fromName("unknown"));
}

@Test
void fromNameThrowsIllegalArgumentForNullArgument() {
assertThrows(IllegalArgumentException.class, () -> RecordPhase.fromName(null));
}

@Test
void fromNameOrIriMatchesPhaseByIriAndName() {
for (RecordPhase p : RecordPhase.values()) {
assertEquals(p, RecordPhase.fromIriOrName(p.getIri()));
assertEquals(p, RecordPhase.fromIriOrName(p.name()));
}
}
}
Loading
Loading