org.apache.httpcomponents.client5
@@ -98,7 +101,7 @@
com.github.ledsoft
jopa-spring-transaction
- 0.2.0
+ 0.3.0-SNAPSHOT
diff --git a/src/main/java/cz/cvut/kbss/study/config/SecurityConfig.java b/src/main/java/cz/cvut/kbss/study/config/SecurityConfig.java
index 148aade2..ded8b3bf 100644
--- a/src/main/java/cz/cvut/kbss/study/config/SecurityConfig.java
+++ b/src/main/java/cz/cvut/kbss/study/config/SecurityConfig.java
@@ -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);
diff --git a/src/main/java/cz/cvut/kbss/study/config/WebAppConfig.java b/src/main/java/cz/cvut/kbss/study/config/WebAppConfig.java
index df53da8b..8034cf95 100644
--- a/src/main/java/cz/cvut/kbss/study/config/WebAppConfig.java
+++ b/src/main/java/cz/cvut/kbss/study/config/WebAppConfig.java
@@ -44,7 +44,7 @@ public ObjectMapper objectMapper() {
*/
public static ObjectMapper createJsonObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
- objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Ignore UoW references injected into entities
objectMapper.addMixIn(UnitOfWorkImpl.class, ManageableIgnoreMixin.class);
diff --git a/src/main/java/cz/cvut/kbss/study/dto/PatientRecordDto.java b/src/main/java/cz/cvut/kbss/study/dto/PatientRecordDto.java
index 1489461b..8dddf25e 100644
--- a/src/main/java/cz/cvut/kbss/study/dto/PatientRecordDto.java
+++ b/src/main/java/cz/cvut/kbss/study/dto/PatientRecordDto.java
@@ -4,6 +4,7 @@
import cz.cvut.kbss.study.model.*;
import cz.cvut.kbss.study.model.util.HasOwlKey;
+import java.net.URI;
import java.util.Date;
@OWLClass(iri = Vocabulary.s_c_patient_record)
@@ -21,8 +22,8 @@ public class PatientRecordDto extends AbstractEntity implements HasOwlKey {
private String localName;
@ParticipationConstraints(nonEmpty = true)
- @OWLObjectProperty(iri = Vocabulary.s_p_has_author, fetch = FetchType.EAGER)
- private User author;
+ @OWLObjectProperty(iri = Vocabulary.s_p_has_author)
+ private URI author;
@OWLDataProperty(iri = Vocabulary.s_p_created)
private Date dateCreated;
@@ -30,8 +31,8 @@ public class PatientRecordDto extends AbstractEntity implements HasOwlKey {
@OWLDataProperty(iri = Vocabulary.s_p_modified)
private Date lastModified;
- @OWLObjectProperty(iri = Vocabulary.s_p_has_last_editor, fetch = FetchType.EAGER)
- private User lastModifiedBy;
+ @OWLObjectProperty(iri = Vocabulary.s_p_has_last_editor)
+ private URI lastModifiedBy;
@OWLObjectProperty(iri = Vocabulary.s_p_was_treated_at, fetch = FetchType.EAGER)
private Institution institution;
@@ -58,11 +59,11 @@ public void setLocalName(String localName) {
this.localName = localName;
}
- public User getAuthor() {
+ public URI getAuthor() {
return author;
}
- public void setAuthor(User author) {
+ public void setAuthor(URI author) {
this.author = author;
}
@@ -82,11 +83,11 @@ public void setLastModified(Date lastModified) {
this.lastModified = lastModified;
}
- public User getLastModifiedBy() {
+ public URI getLastModifiedBy() {
return lastModifiedBy;
}
- public void setLastModifiedBy(User lastModifiedBy) {
+ public void setLastModifiedBy(URI lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
diff --git a/src/main/java/cz/cvut/kbss/study/model/RecordPhase.java b/src/main/java/cz/cvut/kbss/study/model/RecordPhase.java
index 20a09a20..666b602b 100644
--- a/src/main/java/cz/cvut/kbss/study/model/RecordPhase.java
+++ b/src/main/java/cz/cvut/kbss/study/model/RecordPhase.java
@@ -31,7 +31,7 @@ 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;
@@ -39,4 +39,38 @@ public static RecordPhase fromString(String iri) {
}
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.
+ *
+ * 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);
+ }
+ }
}
diff --git a/src/main/java/cz/cvut/kbss/study/persistence/dao/ActionHistoryDao.java b/src/main/java/cz/cvut/kbss/study/persistence/dao/ActionHistoryDao.java
index ad377150..928126bb 100644
--- a/src/main/java/cz/cvut/kbss/study/persistence/dao/ActionHistoryDao.java
+++ b/src/main/java/cz/cvut/kbss/study/persistence/dao/ActionHistoryDao.java
@@ -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
@@ -35,7 +37,7 @@ public ActionHistory findByKey(String key) {
}
}
- public List findAllWithParams(String typeFilter, User author, int pageNumber) {
+ public Page findAllWithParams(String typeFilter, User author, Pageable pageSpec) {
String params;
if (typeFilter == null && author == null) {
params = " } ";
@@ -52,10 +54,11 @@ public List 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.ACTIONS_PER_PAGE)
- .setMaxResults(Constants.ACTIONS_PER_PAGE + 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());
@@ -64,6 +67,6 @@ public List 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);
}
}
diff --git a/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java b/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java
index e185f217..cb72d921 100644
--- a/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java
+++ b/src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java
@@ -16,8 +16,13 @@
import cz.cvut.kbss.study.model.Vocabulary;
import cz.cvut.kbss.study.persistence.dao.util.QuestionSaver;
import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams;
+import cz.cvut.kbss.study.persistence.dao.util.RecordSort;
import cz.cvut.kbss.study.util.Constants;
import cz.cvut.kbss.study.util.IdentificationUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import java.math.BigInteger;
@@ -177,40 +182,85 @@ public void requireUniqueNonEmptyLocalName(PatientRecord entity) {
em.clear();
}
+ /**
+ * Retrieves DTOs of records matching the specified filtering criteria.
+ *
+ * Note that since the record modification is tracked by a timestamp and the filter uses dates, this method uses
+ * beginning of the min date and end of the max date.
+ *
+ * The returned page contains also information about total number of matching records.
+ *
+ * @param filters Record filtering criteria
+ * @param pageSpec Specification of page and sorting
+ * @return Page with matching records
+ * @see #findAllRecordsFull(RecordFilterParams, Pageable)
+ */
+ public Page findAllRecords(RecordFilterParams filters, Pageable pageSpec) {
+ Objects.requireNonNull(filters);
+ Objects.requireNonNull(pageSpec);
+ return findRecords(filters, pageSpec, PatientRecordDto.class);
+ }
+
/**
* Retrieves records matching the specified filtering criteria.
*
* Note that since the record modification is tracked by a timestamp and the filter uses dates, this method uses
* beginning of the min date and end of the max date.
+ *
+ * The returned page contains also information about total number of matching records.
*
- * @param filterParams Record filtering criteria
- * @return List of matching records
+ * @param filters Record filtering criteria
+ * @param pageSpec Specification of page and sorting
+ * @return Page with matching records
+ * @see #findAllRecords(RecordFilterParams, Pageable)
*/
- public List findAllFull(RecordFilterParams filterParams) {
- Objects.requireNonNull(filterParams);
+ public Page findAllRecordsFull(RecordFilterParams filters, Pageable pageSpec) {
+ Objects.requireNonNull(filters);
+ Objects.requireNonNull(pageSpec);
+ return findRecords(filters, pageSpec, PatientRecord.class);
+ }
+
+ private Page findRecords(RecordFilterParams filters, Pageable pageSpec, Class resultClass) {
+ final Map queryParams = new HashMap<>();
+ final String whereClause = constructWhereClause(filters, queryParams);
+ final String queryString = "SELECT ?r WHERE " + whereClause + resolveOrderBy(pageSpec.getSortOr(RecordSort.defaultSort()));
+ final TypedQuery query = em.createNativeQuery(queryString, resultClass);
+ setQueryParameters(query, queryParams);
+ if (pageSpec.isPaged()) {
+ query.setFirstResult((int) pageSpec.getOffset());
+ query.setMaxResults(pageSpec.getPageSize());
+ }
+ final List records = query.getResultList();
+ final TypedQuery countQuery = em.createNativeQuery("SELECT (COUNT(?r) as ?cnt) WHERE " + whereClause, Integer.class);
+ setQueryParameters(countQuery, queryParams);
+ final Integer totalCount = countQuery.getSingleResult();
+ return new PageImpl<>(records, pageSpec, totalCount);
+ }
+
+ private void setQueryParameters(TypedQuery> query, Map queryParams) {
+ query.setParameter("type", typeUri)
+ .setParameter("hasPhase", URI.create(Vocabulary.s_p_has_phase))
+ .setParameter("hasInstitution",
+ URI.create(Vocabulary.s_p_was_treated_at))
+ .setParameter("hasKey", URI.create(Vocabulary.s_p_key))
+ .setParameter("hasCreatedDate", URI.create(Vocabulary.s_p_created))
+ .setParameter("hasLastModified", URI.create(Vocabulary.s_p_modified));
+ queryParams.forEach(query::setParameter);
+ }
+
+ private static String constructWhereClause(RecordFilterParams filters, Map queryParams) {
// Could not use Criteria API because it does not support OPTIONAL
- String queryString = "SELECT ?r WHERE {" +
+ String whereClause = "{" +
"?r a ?type ; " +
"?hasCreatedDate ?created ; " +
"?hasInstitution ?institution . " +
"?institution ?hasKey ?institutionKey ." +
"OPTIONAL { ?r ?hasPhase ?phase . } " +
"OPTIONAL { ?r ?hasLastModified ?lastModified . } " +
- "BIND (IF (BOUND(?lastModified), ?lastModified, ?created) AS ?edited) ";
- final Map queryParams = new HashMap<>();
- queryString += mapParamsToQuery(filterParams, queryParams);
- queryString += "} ORDER BY ?edited";
-
- final TypedQuery query = em.createNativeQuery(queryString, PatientRecord.class)
- .setParameter("type", typeUri)
- .setParameter("hasPhase", URI.create(Vocabulary.s_p_has_phase))
- .setParameter("hasInstitution",
- URI.create(Vocabulary.s_p_was_treated_at))
- .setParameter("hasKey", URI.create(Vocabulary.s_p_key))
- .setParameter("hasCreatedDate", URI.create(Vocabulary.s_p_created))
- .setParameter("hasLastModified", URI.create(Vocabulary.s_p_modified));
- queryParams.forEach(query::setParameter);
- return query.getResultList();
+ "BIND (COALESCE(?lastModified, ?created) AS ?date) ";
+ whereClause += mapParamsToQuery(filters, queryParams);
+ whereClause += "}";
+ return whereClause;
}
private static String mapParamsToQuery(RecordFilterParams filterParams, Map queryParams) {
@@ -218,11 +268,11 @@ private static String mapParamsToQuery(RecordFilterParams filterParams, Map queryParams.put("institutionKey", new LangString(key, Constants.PU_LANGUAGE)));
filterParams.getMinModifiedDate().ifPresent(date -> {
- filters.add("FILTER (?edited >= ?minDate)");
+ filters.add("FILTER (?date >= ?minDate)");
queryParams.put("minDate", date.atStartOfDay(ZoneOffset.UTC).toInstant());
});
filterParams.getMaxModifiedDate().ifPresent(date -> {
- filters.add("FILTER (?edited < ?maxDate)");
+ filters.add("FILTER (?date < ?maxDate)");
queryParams.put("maxDate", date.plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant());
});
if (!filterParams.getPhaseIds().isEmpty()) {
@@ -232,4 +282,20 @@ private static String mapParamsToQuery(RecordFilterParams filterParams, Map phaseIds) {
diff --git a/src/main/java/cz/cvut/kbss/study/persistence/dao/util/RecordSort.java b/src/main/java/cz/cvut/kbss/study/persistence/dao/util/RecordSort.java
new file mode 100644
index 00000000..aa2e0f8c
--- /dev/null
+++ b/src/main/java/cz/cvut/kbss/study/persistence/dao/util/RecordSort.java
@@ -0,0 +1,36 @@
+package cz.cvut.kbss.study.persistence.dao.util;
+
+import org.springframework.data.domain.Sort;
+
+import java.util.Set;
+
+/**
+ * Provides constants for sorting records.
+ */
+public class RecordSort {
+
+ /**
+ * Property used to sort records by date of last modification (if available) or creation.
+ */
+ public static final String SORT_DATE_PROPERTY = "date";
+
+ /**
+ * Supported sorting properties.
+ */
+ public static final Set SORTING_PROPERTIES = Set.of(SORT_DATE_PROPERTY);
+
+ private RecordSort() {
+ throw new AssertionError();
+ }
+
+ /**
+ * Returns the default sort for retrieving records.
+ *
+ * By default, records are sorted by date of last modification/creation in descending order.
+ *
+ * @return Default sort
+ */
+ public static Sort defaultSort() {
+ return Sort.by(Sort.Order.desc("date"));
+ }
+}
diff --git a/src/main/java/cz/cvut/kbss/study/rest/ActionHistoryController.java b/src/main/java/cz/cvut/kbss/study/rest/ActionHistoryController.java
index af56bfb8..026d7f92 100644
--- a/src/main/java/cz/cvut/kbss/study/rest/ActionHistoryController.java
+++ b/src/main/java/cz/cvut/kbss/study/rest/ActionHistoryController.java
@@ -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;
@@ -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)
@@ -42,7 +58,8 @@ public void create(@RequestBody ActionHistory actionHistory) {
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public List getActions(@RequestParam(value = "author", required = false) String authorUsername,
@RequestParam(value = "type", required = false) String type,
- @RequestParam(value = "page") int pageNumber) {
+ @RequestParam MultiValueMap params,
+ UriComponentsBuilder uriBuilder, HttpServletResponse response) {
User author = null;
if (authorUsername != null) {
try {
@@ -51,7 +68,9 @@ public List getActions(@RequestParam(value = "author", required =
return Collections.emptyList();
}
}
- return actionHistoryService.findAllWithParams(type, author, pageNumber);
+ final Page result = actionHistoryService.findAllWithParams(type, author, RestUtils.resolvePaging(params));
+ eventPublisher.publishEvent(new PaginatedResultRetrievedEvent(this, uriBuilder, response, result));
+ return result.getContent();
}
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "')")
diff --git a/src/main/java/cz/cvut/kbss/study/rest/InstitutionController.java b/src/main/java/cz/cvut/kbss/study/rest/InstitutionController.java
index 2ac7fc0f..d96e4bae 100644
--- a/src/main/java/cz/cvut/kbss/study/rest/InstitutionController.java
+++ b/src/main/java/cz/cvut/kbss/study/rest/InstitutionController.java
@@ -8,17 +8,27 @@
import cz.cvut.kbss.study.security.SecurityConstants;
import cz.cvut.kbss.study.service.InstitutionService;
import cz.cvut.kbss.study.service.PatientRecordService;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.DeleteMapping;
+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.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
-import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import static cz.cvut.kbss.study.rest.util.RecordFilterMapper.constructRecordFilter;
+
@RestController
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_USER + "')")
@RequestMapping("/institutions")
@@ -38,7 +48,7 @@ public InstitutionController(InstitutionService institutionService,
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public List getAllInstitutions() {
final List institutions = institutionService.findAll();
- Collections.sort(institutions, (a, b) -> a.getName().compareTo(b.getName()));
+ institutions.sort(Comparator.comparing(Institution::getName));
return institutions;
}
@@ -60,8 +70,9 @@ private Institution findInternal(String key) {
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isRecordInUsersInstitution(#key)")
@GetMapping(value = "/{key}/patients", produces = MediaType.APPLICATION_JSON_VALUE)
public List getTreatedPatientRecords(@PathVariable("key") String key) {
- final Institution institution = findInternal(key);
- return recordService.findByInstitution(institution);
+ final Institution inst = findInternal(key);
+ assert inst != null;
+ return recordService.findAll(constructRecordFilter("institution", key), Pageable.unpaged()).getContent();
}
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "')")
@@ -86,6 +97,7 @@ public void updateInstitution(@PathVariable("key") String key, @RequestBody Inst
}
final Institution original = findInternal(key);
assert original != null;
+
institutionService.update(institution);
if (LOG.isTraceEnabled()) {
LOG.trace("Institution {} successfully updated.", institution);
diff --git a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java
index fa0c4bbe..5bc4d9ff 100644
--- a/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java
+++ b/src/main/java/cz/cvut/kbss/study/rest/PatientRecordController.java
@@ -3,15 +3,17 @@
import cz.cvut.kbss.study.dto.PatientRecordDto;
import cz.cvut.kbss.study.dto.RecordImportResult;
import cz.cvut.kbss.study.exception.NotFoundException;
-import cz.cvut.kbss.study.model.Institution;
import cz.cvut.kbss.study.model.PatientRecord;
import cz.cvut.kbss.study.model.RecordPhase;
+import cz.cvut.kbss.study.rest.event.PaginatedResultRetrievedEvent;
import cz.cvut.kbss.study.rest.exception.BadRequestException;
import cz.cvut.kbss.study.rest.util.RecordFilterMapper;
import cz.cvut.kbss.study.rest.util.RestUtils;
import cz.cvut.kbss.study.security.SecurityConstants;
-import cz.cvut.kbss.study.service.InstitutionService;
import cz.cvut.kbss.study.service.PatientRecordService;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.domain.Page;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -28,6 +30,7 @@
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.List;
@@ -38,36 +41,36 @@ public class PatientRecordController extends BaseController {
private final PatientRecordService recordService;
- private final InstitutionService institutionService;
+ private final ApplicationEventPublisher eventPublisher;
- public PatientRecordController(PatientRecordService recordService, InstitutionService institutionService) {
+ public PatientRecordController(PatientRecordService recordService, ApplicationEventPublisher eventPublisher) {
this.recordService = recordService;
- this.institutionService = institutionService;
+ this.eventPublisher = eventPublisher;
}
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isMemberOfInstitution(#institutionKey)")
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public List getRecords(
- @RequestParam(value = "institution", required = false) String institutionKey) {
- return institutionKey != null ? recordService.findByInstitution(getInstitution(institutionKey)) :
- recordService.findAllRecords();
- }
-
- private Institution getInstitution(String institutionKey) {
- final Institution institution = institutionService.findByKey(institutionKey);
- if (institution == null) {
- throw NotFoundException.create("Institution", institutionKey);
- }
- return institution;
+ @RequestParam(value = "institution", required = false) String institutionKey,
+ @RequestParam MultiValueMap params,
+ UriComponentsBuilder uriBuilder, HttpServletResponse response) {
+ final Page result = recordService.findAll(RecordFilterMapper.constructRecordFilter(params),
+ RestUtils.resolvePaging(params));
+ eventPublisher.publishEvent(new PaginatedResultRetrievedEvent(this, uriBuilder, response, result));
+ return result.getContent();
}
@PreAuthorize(
"hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isMemberOfInstitution(#institutionKey)")
@GetMapping(value = "/export", produces = MediaType.APPLICATION_JSON_VALUE)
public List exportRecords(
- @RequestParam(name = "institutionKey", required = false) String institutionKey,
- @RequestParam MultiValueMap params) {
- return recordService.findAllFull(RecordFilterMapper.constructRecordFilter(params));
+ @RequestParam(name = "institution", required = false) String institutionKey,
+ @RequestParam MultiValueMap params,
+ UriComponentsBuilder uriBuilder, HttpServletResponse response) {
+ final Page result = recordService.findAllFull(RecordFilterMapper.constructRecordFilter(params),
+ RestUtils.resolvePaging(params));
+ eventPublisher.publishEvent(new PaginatedResultRetrievedEvent(this, uriBuilder, response, result));
+ return result.getContent();
}
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or @securityUtils.isRecordInUsersInstitution(#key)")
@@ -98,10 +101,10 @@ public ResponseEntity createRecord(@RequestBody PatientRecord record) {
@PostMapping(value = "/import", consumes = MediaType.APPLICATION_JSON_VALUE)
public RecordImportResult importRecords(@RequestBody List 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);
diff --git a/src/main/java/cz/cvut/kbss/study/rest/StatisticsController.java b/src/main/java/cz/cvut/kbss/study/rest/StatisticsController.java
index 86fcf065..ca1b70d5 100644
--- a/src/main/java/cz/cvut/kbss/study/rest/StatisticsController.java
+++ b/src/main/java/cz/cvut/kbss/study/rest/StatisticsController.java
@@ -2,7 +2,6 @@
import cz.cvut.kbss.study.security.SecurityConstants;
import cz.cvut.kbss.study.service.StatisticsService;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
diff --git a/src/main/java/cz/cvut/kbss/study/rest/event/PaginatedResultRetrievedEvent.java b/src/main/java/cz/cvut/kbss/study/rest/event/PaginatedResultRetrievedEvent.java
new file mode 100644
index 00000000..8311c834
--- /dev/null
+++ b/src/main/java/cz/cvut/kbss/study/rest/event/PaginatedResultRetrievedEvent.java
@@ -0,0 +1,37 @@
+package cz.cvut.kbss.study.rest.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;
+ }
+}
diff --git a/src/main/java/cz/cvut/kbss/study/rest/handler/HateoasPagingListener.java b/src/main/java/cz/cvut/kbss/study/rest/handler/HateoasPagingListener.java
new file mode 100644
index 00000000..7dd87f15
--- /dev/null
+++ b/src/main/java/cz/cvut/kbss/study/rest/handler/HateoasPagingListener.java
@@ -0,0 +1,84 @@
+package cz.cvut.kbss.study.rest.handler;
+
+import cz.cvut.kbss.study.rest.event.PaginatedResultRetrievedEvent;
+import cz.cvut.kbss.study.rest.util.HttpPaginationLink;
+import cz.cvut.kbss.study.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 {
+
+ @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());
+ }
+ }
+
+ 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();
+ }
+ }
+}
diff --git a/src/main/java/cz/cvut/kbss/study/rest/util/HttpPaginationLink.java b/src/main/java/cz/cvut/kbss/study/rest/util/HttpPaginationLink.java
new file mode 100644
index 00000000..abdd1a95
--- /dev/null
+++ b/src/main/java/cz/cvut/kbss/study/rest/util/HttpPaginationLink.java
@@ -0,0 +1,18 @@
+package cz.cvut.kbss.study.rest.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;
+ }
+}
diff --git a/src/main/java/cz/cvut/kbss/study/rest/util/RecordFilterMapper.java b/src/main/java/cz/cvut/kbss/study/rest/util/RecordFilterMapper.java
index f6c0abb9..d565f059 100644
--- a/src/main/java/cz/cvut/kbss/study/rest/util/RecordFilterMapper.java
+++ b/src/main/java/cz/cvut/kbss/study/rest/util/RecordFilterMapper.java
@@ -1,18 +1,21 @@
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;
import org.slf4j.LoggerFactory;
+import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
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.
@@ -29,11 +32,22 @@ public class RecordFilterMapper {
private static final String PHASE_ID_PARAM = "phase";
+ /**
+ * Maps the specified single parameter and value to a new {@link RecordFilterParams} instance.
+ *
+ * @param param Parameter name
+ * @param value Parameter value
+ * @return New {@code RecordFilterParams} instance
+ */
+ public static RecordFilterParams constructRecordFilter(String param, String value) {
+ return constructRecordFilter(new LinkedMultiValueMap<>(Map.of(param, List.of(value))));
+ }
+
/**
* Maps the specified parameters to a new {@link RecordFilterParams} instance.
*
* @param params Request parameters to map
- * @return New {@code RecordFilter} instance
+ * @return New {@code RecordFilterParams} instance
*/
public static RecordFilterParams constructRecordFilter(MultiValueMap params) {
Objects.requireNonNull(params);
@@ -53,7 +67,8 @@ public static RecordFilterParams constructRecordFilter(MultiValueMap(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;
}
diff --git a/src/main/java/cz/cvut/kbss/study/rest/util/RestUtils.java b/src/main/java/cz/cvut/kbss/study/rest/util/RestUtils.java
index 0ebc2892..6848f18c 100644
--- a/src/main/java/cz/cvut/kbss/study/rest/util/RestUtils.java
+++ b/src/main/java/cz/cvut/kbss/study/rest/util/RestUtils.java
@@ -1,9 +1,14 @@
package cz.cvut.kbss.study.rest.util;
+import cz.cvut.kbss.study.util.Constants;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
+import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
@@ -12,9 +17,21 @@
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
+import java.util.Optional;
+import java.util.stream.Collectors;
public class RestUtils {
+ /**
+ * Prefix indicating ascending sort order.
+ */
+ public static final char SORT_ASC = '+';
+
+ /**
+ * Prefix indicating descending sort order.
+ */
+ public static final char SORT_DESC = '-';
+
private RestUtils() {
throw new AssertionError();
}
@@ -94,8 +111,39 @@ public static LocalDate parseDate(String dateStr) {
try {
return LocalDate.parse(dateStr);
} catch (DateTimeParseException | NullPointerException e) {
- throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Value '" + dateStr + "' is not a valid date in ISO format.");
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
+ "Value '" + dateStr + "' is not a valid date in ISO format.");
}
}
+ /**
+ * Resolves paging and sorting configuration from the specified request parameters.
+ *
+ * If no paging and filtering info is specified, an {@link Pageable#unpaged()} object is returned.
+ *
+ * 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 params) {
+ if (params.getFirst(Constants.PAGE_PARAM) == null) {
+ return Pageable.unpaged();
+ }
+ 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);
+ if (params.containsKey(Constants.SORT_PARAM)) {
+ final Sort 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()));
+ return PageRequest.of(page, size, sort);
+ }
+ return PageRequest.of(page, size);
+ }
}
diff --git a/src/main/java/cz/cvut/kbss/study/service/ActionHistoryService.java b/src/main/java/cz/cvut/kbss/study/service/ActionHistoryService.java
index 90f360a8..5bd83524 100644
--- a/src/main/java/cz/cvut/kbss/study/service/ActionHistoryService.java
+++ b/src/main/java/cz/cvut/kbss/study/service/ActionHistoryService.java
@@ -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 findByKey(String key);
- List findAllWithParams(String type, User author, int pageNumber);
+ Page findAllWithParams(String type, User author, Pageable pageSpec);
}
diff --git a/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java b/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java
index 6e57840f..91322af1 100644
--- a/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java
+++ b/src/main/java/cz/cvut/kbss/study/service/PatientRecordService.java
@@ -2,11 +2,11 @@
import cz.cvut.kbss.study.dto.PatientRecordDto;
import cz.cvut.kbss.study.dto.RecordImportResult;
-import cz.cvut.kbss.study.model.Institution;
import cz.cvut.kbss.study.model.PatientRecord;
import cz.cvut.kbss.study.model.RecordPhase;
-import cz.cvut.kbss.study.model.User;
import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import java.util.List;
@@ -21,38 +21,24 @@ public interface PatientRecordService extends BaseService {
PatientRecord findByKey(String key);
/**
- * Gets records of patients treated at the specified institution.
+ * Gets records corresponding to the specified filtering, paging, and sorting criteria.
*
- * @param institution The institution to filter by
- * @return Records of matching patients
+ * @param filters Record filtering criteria
+ * @param pageSpec Specification of page and sorting to retrieve
+ * @return List of matching record DTOs
+ * @see #findAllFull(RecordFilterParams, Pageable)
*/
- List findByInstitution(Institution institution);
+ Page findAll(RecordFilterParams filters, Pageable pageSpec);
/**
- * Gets records of patients created by specified author.
+ * Gets records corresponding to the specified filtering, paging, and sorting criteria.
*
- * @param author The author to filter by
- * @return Records of matching patients
- */
- List findByAuthor(User author);
-
- /**
- * Gets records of all patients.
- *
- * @return Records of matching patients
- */
- List findAllRecords();
-
- /**
- * Finds all records that match the specified parameters.
- *
- * In contrast to {@link #findAll()}, this method returns full records, not DTOs.
- *
- * @param filterParams Record filtering criteria
+ * @param filters Record filtering criteria
+ * @param pageSpec Specification of page and sorting to retrieve
* @return List of matching records
- * @see #findAllRecords()
+ * @see #findAll(RecordFilterParams, Pageable)
*/
- List findAllFull(RecordFilterParams filterParams);
+ Page findAllFull(RecordFilterParams filters, Pageable pageSpec);
/**
* Imports the specified records.
@@ -64,8 +50,8 @@ public interface PatientRecordService extends BaseService {
* records.
*
* If the current user is an admin, the import procedure retains provenance data of the record. Otherwise, the
- * current user is set as the record's author. Also, if the current user is not an admin, the phase of all
- * the imported records is set to {@link RecordPhase#open}, for admin, the phase of the records is retained.
+ * current user is set as the record's author. Also, if the current user is not an admin, the phase of all the
+ * imported records is set to {@link RecordPhase#open}, for admin, the phase of the records is retained.
*
* @param records Records to import
* @return Instance representing the import result
diff --git a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryActionHistoryService.java b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryActionHistoryService.java
index 82522b0b..bbde2b61 100644
--- a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryActionHistoryService.java
+++ b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryActionHistoryService.java
@@ -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 implements ActionHistoryService {
@@ -32,7 +32,7 @@ public ActionHistory findByKey(String key) {
@Transactional(readOnly = true)
@Override
- public List findAllWithParams(String type, User author, int pageNumber) {
- return actionHistoryDao.findAllWithParams(type, author, pageNumber);
+ public Page findAllWithParams(String type, User author, Pageable pageSpec) {
+ return actionHistoryDao.findAllWithParams(type, author, pageSpec);
}
}
diff --git a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java
index cc20e7ba..8aaf2c9c 100644
--- a/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java
+++ b/src/main/java/cz/cvut/kbss/study/service/repository/RepositoryPatientRecordService.java
@@ -3,7 +3,6 @@
import cz.cvut.kbss.study.dto.PatientRecordDto;
import cz.cvut.kbss.study.dto.RecordImportResult;
import cz.cvut.kbss.study.exception.RecordAuthorNotFoundException;
-import cz.cvut.kbss.study.model.Institution;
import cz.cvut.kbss.study.model.PatientRecord;
import cz.cvut.kbss.study.model.RecordPhase;
import cz.cvut.kbss.study.model.User;
@@ -16,6 +15,8 @@
import cz.cvut.kbss.study.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -50,26 +51,14 @@ protected OwlKeySupportingDao getPrimaryDao() {
@Transactional(readOnly = true)
@Override
- public List findByInstitution(Institution institution) {
- return recordDao.findByInstitution(institution);
+ public Page findAll(RecordFilterParams filters, Pageable pageSpec) {
+ return recordDao.findAllRecords(filters, pageSpec);
}
@Transactional(readOnly = true)
@Override
- public List findByAuthor(User user) {
- return recordDao.findByAuthor(user);
- }
-
- @Transactional(readOnly = true)
- @Override
- public List findAllRecords() {
- return recordDao.findAllRecords();
- }
-
- @Transactional(readOnly = true)
- @Override
- public List findAllFull(RecordFilterParams filterParams) {
- return recordDao.findAllFull(filterParams);
+ public Page findAllFull(RecordFilterParams filters, Pageable pageSpec) {
+ return recordDao.findAllRecordsFull(filters, pageSpec);
}
@Override
diff --git a/src/main/java/cz/cvut/kbss/study/util/Constants.java b/src/main/java/cz/cvut/kbss/study/util/Constants.java
index 0a22c590..e49f7f38 100644
--- a/src/main/java/cz/cvut/kbss/study/util/Constants.java
+++ b/src/main/java/cz/cvut/kbss/study/util/Constants.java
@@ -28,7 +28,7 @@ private Constants() {
/**
* Number of history actions fetched from database. Needs to be changes also in front-end.
*/
- public static final int ACTIONS_PER_PAGE = 25;
+ public static final int DEFAULT_PAGE_SIZE = 25;
/**
* Path to directory containing queries used by the system.
@@ -37,4 +37,19 @@ private Constants() {
* ClassLoader#getResourceAsStream(String)}.
*/
public static final String QUERY_DIRECTORY = "query";
+
+ /**
+ * Name of the request parameter specifying page number.
+ */
+ public static final String PAGE_PARAM = "page";
+
+ /**
+ * Name of the request parameter specifying page size.
+ */
+ public static final String PAGE_SIZE_PARAM = "size";
+
+ /**
+ * Name of the request parameter specifying sorting.
+ */
+ public static final String SORT_PARAM = "sort";
}
diff --git a/src/test/java/cz/cvut/kbss/study/environment/generator/Generator.java b/src/test/java/cz/cvut/kbss/study/environment/generator/Generator.java
index 2a8b5693..93f8a21c 100644
--- a/src/test/java/cz/cvut/kbss/study/environment/generator/Generator.java
+++ b/src/test/java/cz/cvut/kbss/study/environment/generator/Generator.java
@@ -26,7 +26,7 @@ private Generator() {
* @return Random URI
*/
public static URI generateUri() {
- return URI.create(Vocabulary.ONTOLOGY_IRI_record_manager + "/randomInstance" + randomInt());
+ return URI.create(Vocabulary.ONTOLOGY_IRI_RECORD_MANAGER + "/randomInstance" + randomInt());
}
/**
@@ -183,7 +183,7 @@ public static PatientRecord generatePatientRecord(User author) {
public static PatientRecordDto generatePatientRecordDto(User author) {
final PatientRecordDto rec = new PatientRecordDto();
rec.setLocalName("RandomRecordDto" + randomInt());
- rec.setAuthor(author);
+ rec.setAuthor(author.getUri());
rec.setUri(generateUri());
rec.setInstitution(author.getInstitution());
return rec;
diff --git a/src/test/java/cz/cvut/kbss/study/model/RecordPhaseTest.java b/src/test/java/cz/cvut/kbss/study/model/RecordPhaseTest.java
index 59ad29af..ce3d204a 100644
--- a/src/test/java/cz/cvut/kbss/study/model/RecordPhaseTest.java
+++ b/src/test/java/cz/cvut/kbss/study/model/RecordPhaseTest.java
@@ -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()));
+ }
}
}
\ No newline at end of file
diff --git a/src/test/java/cz/cvut/kbss/study/model/UserTest.java b/src/test/java/cz/cvut/kbss/study/model/UserTest.java
index 759cfce6..3b384e27 100644
--- a/src/test/java/cz/cvut/kbss/study/model/UserTest.java
+++ b/src/test/java/cz/cvut/kbss/study/model/UserTest.java
@@ -82,7 +82,7 @@ public void generateUriThrowsIllegalStateForEmptyLastName() {
@Test
public void generateUriDoesNothingIfTheUriIsAlreadySet() {
- final String uri = Vocabulary.ONTOLOGY_IRI_record_manager + "/test";
+ final String uri = Vocabulary.ONTOLOGY_IRI_RECORD_MANAGER + "/test";
user.setUri(URI.create(uri));
user.generateUri();
assertEquals(uri, user.getUri().toString());
diff --git a/src/test/java/cz/cvut/kbss/study/model/qam/AnswerTest.java b/src/test/java/cz/cvut/kbss/study/model/qam/AnswerTest.java
index fff4d364..8659c130 100644
--- a/src/test/java/cz/cvut/kbss/study/model/qam/AnswerTest.java
+++ b/src/test/java/cz/cvut/kbss/study/model/qam/AnswerTest.java
@@ -14,7 +14,7 @@ public void copyConstructorsCopiesValuesAndTypesNoUri() {
final Answer a = new Answer();
a.setTextValue("Cough");
a.setCodeValue(Generator.generateUri());
- a.getTypes().add(Vocabulary.ONTOLOGY_IRI_record_manager + "/infectious-disease/");
+ a.getTypes().add(Vocabulary.ONTOLOGY_IRI_RECORD_MANAGER + "/infectious-disease/");
final Answer res = new Answer(a);
assertNull(res.getUri());
diff --git a/src/test/java/cz/cvut/kbss/study/model/qam/QuestionTest.java b/src/test/java/cz/cvut/kbss/study/model/qam/QuestionTest.java
index 7980c937..3059e75a 100644
--- a/src/test/java/cz/cvut/kbss/study/model/qam/QuestionTest.java
+++ b/src/test/java/cz/cvut/kbss/study/model/qam/QuestionTest.java
@@ -13,7 +13,7 @@ public class QuestionTest {
public void copyConstructorCopiesSubQuestions() {
final Question q = new Question();
q.setUri(Generator.generateUri());
- q.getTypes().add(Vocabulary.ONTOLOGY_IRI_record_manager + "/infectious-disease/");
+ q.getTypes().add(Vocabulary.ONTOLOGY_IRI_RECORD_MANAGER + "/infectious-disease/");
for (int i = 0; i < Generator.randomInt(10); i++) {
final Question child = new Question();
child.setUri(Generator.generateUri());
diff --git a/src/test/java/cz/cvut/kbss/study/persistence/dao/ActionHistoryDaoTest.java b/src/test/java/cz/cvut/kbss/study/persistence/dao/ActionHistoryDaoTest.java
index db39b0e7..a046a3cb 100644
--- a/src/test/java/cz/cvut/kbss/study/persistence/dao/ActionHistoryDaoTest.java
+++ b/src/test/java/cz/cvut/kbss/study/persistence/dao/ActionHistoryDaoTest.java
@@ -5,10 +5,14 @@
import cz.cvut.kbss.study.model.Institution;
import cz.cvut.kbss.study.model.User;
import cz.cvut.kbss.study.persistence.BaseDaoTestRunner;
+import cz.cvut.kbss.study.util.Constants;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
import java.util.List;
+import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -24,9 +28,9 @@ public class ActionHistoryDaoTest extends BaseDaoTestRunner {
@Autowired
ActionHistoryDao actionHistoryDao;
- private final String LOAD_SUCCESS = "LOAD_SUCCESS";
- private final String LOAD_ERROR = "LOAD_ERROR";
- private final String LOAD_PENDING = "LOAD_PENDING";
+ private static final String LOAD_SUCCESS = "LOAD_SUCCESS";
+ private static final String LOAD_ERROR = "LOAD_ERROR";
+ private static final String LOAD_PENDING = "LOAD_PENDING";
@Test
public void findByKeyReturnsActionWithPayload() {
@@ -61,9 +65,9 @@ public void findAllWithParamsWithoutParamsReturnsAllActions() {
actionHistoryDao.persist(List.of(action1, action2, action3));
});
- List actionsList = actionHistoryDao.findAllWithParams(null, null, 1);
+ Page actionsList = actionHistoryDao.findAllWithParams(null, null, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
- assertEquals(3, actionsList.size());
+ assertEquals(3, actionsList.getNumberOfElements());
}
@Test
@@ -82,13 +86,13 @@ public void findAllWithParamsWithAuthorReturnsAuthorsActions() {
actionHistoryDao.persist(List.of(action1, action2, action3));
});
- List actionsList1 = actionHistoryDao.findAllWithParams(null, user1, 1);
- List actionsList2 = actionHistoryDao.findAllWithParams(null, user2, 1);
- List actionsList3 = actionHistoryDao.findAllWithParams(null, user3, 1);
+ Page actionsList1 = actionHistoryDao.findAllWithParams(null, user1, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
+ Page actionsList2 = actionHistoryDao.findAllWithParams(null, user2, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
+ Page actionsList3 = actionHistoryDao.findAllWithParams(null, user3, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
- assertEquals(2, actionsList1.size());
- assertEquals(1, actionsList2.size());
- assertEquals(0, actionsList3.size());
+ assertEquals(2, actionsList1.getNumberOfElements());
+ assertEquals(1, actionsList2.getNumberOfElements());
+ assertEquals(0, actionsList3.getNumberOfElements());
}
@Test
@@ -108,13 +112,13 @@ public void findAllWithParamsWithTypeReturnsActionsWithExactType() {
actionHistoryDao.persist(List.of(action1, action2, action3));
});
- List actionsList1 = actionHistoryDao.findAllWithParams(LOAD_SUCCESS, null, 1);
- List actionsList2 = actionHistoryDao.findAllWithParams(LOAD_ERROR, null, 1);
- List actionsList3 = actionHistoryDao.findAllWithParams(LOAD_PENDING, null, 1);
+ Page actionsList1 = actionHistoryDao.findAllWithParams(LOAD_SUCCESS, null, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
+ Page actionsList2 = actionHistoryDao.findAllWithParams(LOAD_ERROR, null, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
+ Page actionsList3 = actionHistoryDao.findAllWithParams(LOAD_PENDING, null, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
- assertEquals(2, actionsList1.size());
- assertEquals(1, actionsList2.size());
- assertEquals(0, actionsList3.size());
+ assertEquals(2, actionsList1.getNumberOfElements());
+ assertEquals(1, actionsList2.getNumberOfElements());
+ assertEquals(0, actionsList3.getNumberOfElements());
}
@Test
@@ -133,9 +137,9 @@ public void findAllWithParamsWithTypeReturnsActionsWithTypeContained() {
actionHistoryDao.persist(List.of(action1, action2, action3));
});
- List actionsList = actionHistoryDao.findAllWithParams("LOAD", null, 1);
+ Page actionsList = actionHistoryDao.findAllWithParams("LOAD", null, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
- assertEquals(2, actionsList.size());
+ assertEquals(2, actionsList.getNumberOfElements());
}
@Test
@@ -156,14 +160,30 @@ public void findAllWithParamsReturnsMatchingActions() {
actionHistoryDao.persist(List.of(action1, action2, action3));
});
- List actionsList1 = actionHistoryDao.findAllWithParams(LOAD_SUCCESS, user1, 1);
- List actionsList2 = actionHistoryDao.findAllWithParams(LOAD_SUCCESS, user2, 1);
- List actionsList3 = actionHistoryDao.findAllWithParams(LOAD_ERROR, user2, 1);
- List actionsList4 = actionHistoryDao.findAllWithParams("LOAD", user2, 1);
+ Page actionsList1 = actionHistoryDao.findAllWithParams(LOAD_SUCCESS, user1, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
+ Page actionsList2 = actionHistoryDao.findAllWithParams(LOAD_SUCCESS, user2, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
+ Page actionsList3 = actionHistoryDao.findAllWithParams(LOAD_ERROR, user2, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
+ Page actionsList4 = actionHistoryDao.findAllWithParams("LOAD", user2, PageRequest.of(0, Constants.DEFAULT_PAGE_SIZE));
- assertEquals(2, actionsList1.size());
- assertEquals(0, actionsList2.size());
- assertEquals(1, actionsList3.size());
- assertEquals(1, actionsList4.size());
+ assertEquals(2, actionsList1.getNumberOfElements());
+ assertEquals(0, actionsList2.getNumberOfElements());
+ assertEquals(1, actionsList3.getNumberOfElements());
+ assertEquals(1, actionsList4.getNumberOfElements());
+ }
+
+ @Test
+ void findAllReturnsActionsOnMatchingPage() {
+ Institution institution = Generator.generateInstitution();
+ User user = Generator.generateUser(institution);
+ final List allActions = IntStream.range(0, 10).mapToObj(i -> Generator.generateActionHistory(user)).toList();
+ transactional(() -> {
+ institutionDao.persist(institution);
+ userDao.persist(user);
+ actionHistoryDao.persist(allActions);
+ });
+
+ final PageRequest pageSpec = PageRequest.of(2, allActions.size() / 2);
+ final Page result = actionHistoryDao.findAllWithParams(null, null, pageSpec);
+ assertEquals(allActions.subList((int) pageSpec.getOffset(), allActions.size()), result.getContent());
}
}
diff --git a/src/test/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDaoTest.java b/src/test/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDaoTest.java
index 93ff7cb7..dac6c38f 100644
--- a/src/test/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDaoTest.java
+++ b/src/test/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDaoTest.java
@@ -15,17 +15,24 @@
import cz.cvut.kbss.study.persistence.BaseDaoTestRunner;
import cz.cvut.kbss.study.persistence.dao.util.QuestionSaver;
import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams;
+import cz.cvut.kbss.study.persistence.dao.util.RecordSort;
import cz.cvut.kbss.study.util.IdentificationUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import java.net.URI;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.List;
+import java.util.ListIterator;
import java.util.Set;
import java.util.stream.IntStream;
@@ -34,6 +41,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
public class PatientRecordDaoTest extends BaseDaoTestRunner {
@@ -255,7 +263,10 @@ private void persistRecordWithIdentification(PatientRecord record) {
@Test
void findAllFullReturnsRecordsMatchingSpecifiedDatePeriod() {
final User author = generateAuthorWithInstitution();
- final List allRecords = generateRecordsForAuthor(author);
+ final List allRecords = generateRecordsForAuthor(author, 5);
+ for (int i = 0; i < allRecords.size(); i++) {
+ allRecords.get(i).setDateCreated(new Date(System.currentTimeMillis() - i * Environment.MILLIS_PER_DAY));
+ }
transactional(() -> allRecords.forEach(this::persistRecordWithIdentification));
final LocalDate minDate = LocalDate.now().minusDays(3);
final LocalDate maxDate = LocalDate.now().minusDays(1);
@@ -265,23 +276,26 @@ void findAllFullReturnsRecordsMatchingSpecifiedDatePeriod() {
return !modifiedDate.isBefore(minDate) && !modifiedDate.isAfter(maxDate);
}).toList();
- final List result =
- sut.findAllFull(new RecordFilterParams(null, minDate, maxDate, Collections.emptySet()));
+ final Page result =
+ sut.findAllRecordsFull(new RecordFilterParams(null, minDate, maxDate, Collections.emptySet()),
+ Pageable.unpaged());
assertFalse(result.isEmpty());
- assertThat(result, containsSameEntities(expected));
+ assertThat(result.getContent(), containsSameEntities(expected));
}
- private List generateRecordsForAuthor(User author) {
- return IntStream.range(0, 5).mapToObj(i -> {
- final PatientRecord r = Generator.generatePatientRecord(author);
- if (Generator.randomBoolean()) {
- r.setDateCreated(new Date(System.currentTimeMillis() - i * Environment.MILLIS_PER_DAY));
- } else {
- r.setDateCreated(new Date(System.currentTimeMillis() - 365 * Environment.MILLIS_PER_DAY));
- r.setLastModified(new Date(System.currentTimeMillis() - i * Environment.MILLIS_PER_DAY));
- }
- return r;
- }).toList();
+ private List generateRecordsForAuthor(User author, int count) {
+ return IntStream.range(0, count).mapToObj(i -> {
+ final PatientRecord r = Generator.generatePatientRecord(author);
+ if (Generator.randomBoolean()) {
+ r.setDateCreated(new Date(System.currentTimeMillis() - 30 * i * Environment.MILLIS_PER_DAY));
+ } else {
+ r.setDateCreated(new Date(System.currentTimeMillis() - 30 * i * Environment.MILLIS_PER_DAY));
+ r.setLastModified(new Date(System.currentTimeMillis() - i * Environment.MILLIS_PER_DAY));
+ }
+ return r;
+ }).sorted(Comparator.comparing(
+ (PatientRecord r) -> r.getLastModified() != null ? r.getLastModified() : r.getDateCreated()).reversed())
+ .toList();
}
@Test
@@ -289,10 +303,10 @@ void findAllFullReturnsRecordsMatchingSpecifiedDatePeriodAndInstitution() {
final User authorOne = generateAuthorWithInstitution();
final Institution institution = authorOne.getInstitution();
final User authorTwo = generateAuthorWithInstitution();
- final List allRecords = new ArrayList<>(generateRecordsForAuthor(authorOne));
- allRecords.addAll(generateRecordsForAuthor(authorTwo));
+ final List allRecords = new ArrayList<>(generateRecordsForAuthor(authorOne, 5));
+ allRecords.addAll(generateRecordsForAuthor(authorTwo, 5));
transactional(() -> allRecords.forEach(this::persistRecordWithIdentification));
- final LocalDate minDate = LocalDate.now().minusDays(3);
+ final LocalDate minDate = LocalDate.now().minusDays(31);
final LocalDate maxDate = LocalDate.now().minusDays(1);
final List expected = allRecords.stream().filter(r -> {
final Date modified = r.getLastModified() != null ? r.getLastModified() : r.getDateCreated();
@@ -301,16 +315,17 @@ void findAllFullReturnsRecordsMatchingSpecifiedDatePeriodAndInstitution() {
.equals(institution.getUri());
}).toList();
- final List result =
- sut.findAllFull(new RecordFilterParams(institution.getKey(), minDate, maxDate, Collections.emptySet()));
+ final Page result =
+ sut.findAllRecordsFull(new RecordFilterParams(institution.getKey(), minDate, maxDate, Collections.emptySet()),
+ Pageable.unpaged());
assertFalse(result.isEmpty());
- assertThat(result, containsSameEntities(expected));
+ assertThat(result.getContent(), containsSameEntities(expected));
}
@Test
void findAllFullReturnsRecordsMatchingSpecifiedPhase() {
final User author = generateAuthorWithInstitution();
- final List allRecords = generateRecordsForAuthor(author);
+ final List allRecords = generateRecordsForAuthor(author, 5);
transactional(() -> allRecords.forEach(r -> {
r.setPhase(RecordPhase.values()[Generator.randomInt(RecordPhase.values().length)]);
persistRecordWithIdentification(r);
@@ -319,11 +334,89 @@ void findAllFullReturnsRecordsMatchingSpecifiedPhase() {
final RecordFilterParams filterParams = new RecordFilterParams();
filterParams.setPhaseIds(Set.of(phase.getIri()));
- final List result = sut.findAllFull(filterParams);
+ final Page result = sut.findAllRecordsFull(filterParams, Pageable.unpaged());
assertFalse(result.isEmpty());
result.forEach(res -> assertEquals(phase, res.getPhase()));
}
+ @Test
+ void findAllFullReturnsRecordsMatchingSpecifiedPage() {
+ final User author = generateAuthorWithInstitution();
+ final List allRecords = generateRecordsForAuthor(author, 10);
+ transactional(() -> allRecords.forEach(this::persistRecordWithIdentification));
+ final int pageSize = 5;
+
+ final Page result = sut.findAllRecordsFull(new RecordFilterParams(), PageRequest.of(1, pageSize));
+ assertFalse(result.isEmpty());
+ assertEquals(pageSize, result.getNumberOfElements());
+ assertThat(result.getContent(), containsSameEntities(allRecords.subList(pageSize, allRecords.size())));
+ }
+
+ @Test
+ void findAllFullReturnsRecordsSortedAccordingToSortSpecification() {
+ final User author = generateAuthorWithInstitution();
+ final List allRecords = generateRecordsForAuthor(author, 5);
+ transactional(() -> allRecords.forEach(this::persistRecordWithIdentification));
+ final Page result =
+ sut.findAllRecordsFull(new RecordFilterParams(), PageRequest.of(0, allRecords.size(), Sort.Direction.ASC,
+ RecordSort.SORT_DATE_PROPERTY));
+ assertFalse(result.isEmpty());
+ assertEquals(allRecords.size(), result.getNumberOfElements());
+ final ListIterator itExp = allRecords.listIterator(allRecords.size());
+ final ListIterator itAct = result.getContent().listIterator();
+ while (itExp.hasPrevious() && itAct.hasNext()) {
+ assertEquals(itExp.previous().getUri(), itAct.next().getUri());
+ }
+ }
+
+ @Test
+ void findAllFullThrowsIllegalArgumentExceptionForUnsupportedSortProperty() {
+ assertThrows(IllegalArgumentException.class,
+ () -> sut.findAllRecordsFull(new RecordFilterParams(), PageRequest.of(0, 10, Sort.Direction.ASC,
+ "unknownProperty")));
+ }
+
+ @Test
+ void findAllFullReturnsPageWithTotalNumberOfMatchingRecords() {
+ final User author = generateAuthorWithInstitution();
+ final List allRecords = generateRecordsForAuthor(author, 10);
+ transactional(() -> allRecords.forEach(this::persistRecordWithIdentification));
+ final int pageSize = 5;
+
+ final Page result = sut.findAllRecordsFull(new RecordFilterParams(), PageRequest.of(1, pageSize));
+ assertFalse(result.isEmpty());
+ assertEquals(pageSize, result.getNumberOfElements());
+ assertEquals(allRecords.size(), result.getTotalElements());
+ }
+
+ @Test
+ void findAllRecordsReturnsPageWithMatchingRecords() {
+ final User author = generateAuthorWithInstitution();
+ final int totalCount = 10;
+ final List allRecords = generateRecordsForAuthor(author, totalCount);
+ for (int i = 0; i < allRecords.size(); i++) {
+ allRecords.get(i).setDateCreated(new Date(System.currentTimeMillis() - i * Environment.MILLIS_PER_DAY));
+ }
+ transactional(() -> allRecords.forEach(this::persistRecordWithIdentification));
+ final LocalDate minDate = LocalDate.now().minusDays(3);
+ final LocalDate maxDate = LocalDate.now().minusDays(1);
+ final List allMatching = allRecords.stream().filter(r -> {
+ final Date modified = r.getLastModified() != null ? r.getLastModified() : r.getDateCreated();
+ final LocalDate modifiedDate = modified.toInstant().atZone(ZoneOffset.UTC).toLocalDate();
+ return !modifiedDate.isBefore(minDate) && !modifiedDate.isAfter(maxDate);
+ }).sorted(Comparator.comparing(r -> r.getLastModified() != null ? r.getLastModified() : r.getDateCreated())).toList();
+ final int pageSize = 3;
+
+ final Page result =
+ sut.findAllRecords(new RecordFilterParams(null, minDate, maxDate, Collections.emptySet()),
+ PageRequest.of(0, pageSize, Sort.Direction.ASC, RecordSort.SORT_DATE_PROPERTY));
+ assertEquals(Math.min(pageSize, allMatching.size()), result.getNumberOfElements());
+ assertEquals(allMatching.size(), result.getTotalElements());
+ for (int i = 0; i < result.getNumberOfElements(); i++) {
+ assertEquals(allMatching.get(i).getUri(), result.getContent().get(i).getUri());
+ }
+ }
+
@Test
void persistDoesNotGenerateIdentificationWhenRecordAlreadyHasIt() {
final User author = generateAuthorWithInstitution();
diff --git a/src/test/java/cz/cvut/kbss/study/rest/ActionHistoryControllerTest.java b/src/test/java/cz/cvut/kbss/study/rest/ActionHistoryControllerTest.java
index 393a0ded..52e3d31a 100644
--- a/src/test/java/cz/cvut/kbss/study/rest/ActionHistoryControllerTest.java
+++ b/src/test/java/cz/cvut/kbss/study/rest/ActionHistoryControllerTest.java
@@ -7,24 +7,34 @@
import cz.cvut.kbss.study.model.ActionHistory;
import cz.cvut.kbss.study.model.Institution;
import cz.cvut.kbss.study.model.User;
+import cz.cvut.kbss.study.rest.event.PaginatedResultRetrievedEvent;
import cz.cvut.kbss.study.service.ActionHistoryService;
import cz.cvut.kbss.study.service.UserService;
+import cz.cvut.kbss.study.util.Constants;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
+import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -39,23 +49,29 @@ public class ActionHistoryControllerTest extends BaseControllerTestRunner {
@Mock
private UserService userServiceMock;
+ @Mock
+ private ApplicationEventPublisher eventPublisherMock;
+
@InjectMocks
private ActionHistoryController controller;
+ private User user;
+
@BeforeEach
public void setUp() {
super.setUp(controller);
Institution institution = Generator.generateInstitution();
- User user = Generator.generateUser(institution);
+ this.user = Generator.generateUser(institution);
Environment.setCurrentUser(user);
}
@Test
public void createActionReturnsResponseStatusCreated() throws Exception {
- ActionHistory action = Generator.generateActionHistory(Environment.getCurrentUser());
+ ActionHistory action = Generator.generateActionHistory(user);
final MvcResult result = mockMvc.perform(post("/history").content(toJson(action))
- .contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+ .contentType(MediaType.APPLICATION_JSON_VALUE))
+ .andReturn();
assertEquals(HttpStatus.CREATED, HttpStatus.valueOf(result.getResponse().getStatus()));
}
@@ -72,49 +88,48 @@ public void getByKeyThrowsNotFoundWhenActionIsNotFound() throws Exception {
@Test
public void getByKeyReturnsFoundAction() throws Exception {
final String key = "12345";
- ActionHistory action = Generator.generateActionHistory(Environment.getCurrentUser());
+ ActionHistory action = Generator.generateActionHistory(user);
action.setKey(key);
when(actionHistoryServiceMock.findByKey(key)).thenReturn(action);
final MvcResult result = mockMvc.perform(get("/history/" + key)).andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
- final ActionHistory res = objectMapper.readValue(result.getResponse().getContentAsString(), ActionHistory.class);
- assertEquals(res.getUri(),action.getUri());
+ final ActionHistory res =
+ objectMapper.readValue(result.getResponse().getContentAsString(), ActionHistory.class);
+ assertEquals(res.getUri(), action.getUri());
verify(actionHistoryServiceMock).findByKey(key);
}
@Test
public void getActionsReturnsEmptyListWhenNoActionsAreFound() throws Exception {
- when(actionHistoryServiceMock.findAllWithParams(null, null, 1)).thenReturn(Collections.emptyList());
+ when(actionHistoryServiceMock.findAllWithParams(any(), any(), any())).thenReturn(Page.empty());
- final MvcResult result = mockMvc.perform(get("/history/").param("page", "1")).andReturn();
+ final MvcResult result = mockMvc.perform(get("/history/")).andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertTrue(body.isEmpty());
}
@Test
public void getActionsReturnsAllActions() throws Exception {
- ActionHistory action1 = Generator.generateActionHistory(Environment.getCurrentUser());
- ActionHistory action2 = Generator.generateActionHistory(Environment.getCurrentUser());
- List actions = new ArrayList<>();
-
- actions.add(action1);
- actions.add(action2);
+ ActionHistory action1 = Generator.generateActionHistory(user);
+ ActionHistory action2 = Generator.generateActionHistory(user);
+ List actions = List.of(action1, action2);
- when(actionHistoryServiceMock.findAllWithParams(null, null, 1)).thenReturn(actions);
+ when(actionHistoryServiceMock.findAllWithParams(any(), any(), any(
+ Pageable.class))).thenReturn(new PageImpl<>(actions));
final MvcResult result = mockMvc.perform(get("/history/").param("page", "1")).andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertEquals(2, body.size());
- verify(actionHistoryServiceMock).findAllWithParams(null , null, 1);
+ verify(actionHistoryServiceMock).findAllWithParams(null, null, PageRequest.of(1, Constants.DEFAULT_PAGE_SIZE));
}
@Test
@@ -123,90 +138,104 @@ public void getActionsByUnknownAuthorReturnsEmptyList() throws Exception {
when(userServiceMock.findByUsername(username)).thenThrow(NotFoundException.create("User", username));
final MvcResult result = mockMvc.perform(get("/history/")
- .param("author", username)
- .param("page", "1"))
- .andReturn();
+ .param("author", username)
+ .param("page", "1"))
+ .andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertTrue(body.isEmpty());
+ verify(actionHistoryServiceMock, never()).findAllWithParams(any(), any(), any());
}
@Test
public void getActionsByAuthorReturnsActions() throws Exception {
- ActionHistory action1 = Generator.generateActionHistory(Environment.getCurrentUser());
- ActionHistory action2 = Generator.generateActionHistory(Environment.getCurrentUser());
- List actions = new ArrayList<>();
-
- actions.add(action1);
- actions.add(action2);
+ ActionHistory action1 = Generator.generateActionHistory(user);
+ ActionHistory action2 = Generator.generateActionHistory(user);
+ List actions = List.of(action1, action2);
- when(actionHistoryServiceMock.findAllWithParams(null, Environment.getCurrentUser(), 1)).thenReturn(actions);
- when(userServiceMock.findByUsername(Environment.getCurrentUser().getUsername())).thenReturn(Environment.getCurrentUser());
+ when(actionHistoryServiceMock.findAllWithParams(any(), eq(user), any(
+ Pageable.class))).thenReturn(new PageImpl<>(actions));
+ when(userServiceMock.findByUsername(user.getUsername())).thenReturn(
+ user);
final MvcResult result = mockMvc.perform(get("/history/")
- .param("author", Environment.getCurrentUser().getUsername())
- .param("page", "1"))
- .andReturn();
+ .param("author", user.getUsername())
+ .param("page", "1"))
+ .andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertEquals(2, body.size());
- verify(actionHistoryServiceMock).findAllWithParams(null, Environment.getCurrentUser(), 1);
+ verify(actionHistoryServiceMock).findAllWithParams(null, user,
+ PageRequest.of(1, Constants.DEFAULT_PAGE_SIZE));
}
@Test
public void getActionsByTypeReturnsActions() throws Exception {
- ActionHistory action1 = Generator.generateActionHistory(Environment.getCurrentUser());
- ActionHistory action2 = Generator.generateActionHistory(Environment.getCurrentUser());
- List actions = new ArrayList<>();
-
- actions.add(action1);
- actions.add(action2);
+ ActionHistory action1 = Generator.generateActionHistory(user);
+ ActionHistory action2 = Generator.generateActionHistory(user);
+ List actions = List.of(action1, action2);
- when(actionHistoryServiceMock.findAllWithParams("TYPE", null, 1)).thenReturn(actions);
+ when(actionHistoryServiceMock.findAllWithParams(eq("TYPE"), any(), any(Pageable.class))).thenReturn(
+ new PageImpl<>(actions));
final MvcResult result = mockMvc.perform(get("/history/")
- .param("type", "TYPE")
- .param("page", "1"))
- .andReturn();
+ .param("type", "TYPE")
+ .param("page", "1"))
+ .andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertEquals(2, body.size());
- verify(actionHistoryServiceMock).findAllWithParams("TYPE", null, 1);
+ verify(actionHistoryServiceMock).findAllWithParams("TYPE", null,
+ PageRequest.of(1, Constants.DEFAULT_PAGE_SIZE));
}
@Test
public void getActionsByTypeAndAuthorReturnsActions() throws Exception {
- User user = Environment.getCurrentUser();
ActionHistory action1 = Generator.generateActionHistory(user);
ActionHistory action2 = Generator.generateActionHistory(user);
- List actions = new ArrayList<>();
-
- actions.add(action1);
- actions.add(action2);
+ List actions = List.of(action1, action2);
when(userServiceMock.findByUsername(user.getUsername())).thenReturn(user);
- when(actionHistoryServiceMock.findAllWithParams("TYPE", user, 1)).thenReturn(actions);
+ when(actionHistoryServiceMock.findAllWithParams(eq("TYPE"), eq(user), any(Pageable.class))).thenReturn(
+ new PageImpl<>(actions));
final MvcResult result = mockMvc.perform(get("/history/")
- .param("author", user.getUsername())
- .param("type", "TYPE")
- .param("page", "1"))
- .andReturn();
+ .param("author", user.getUsername())
+ .param("type", "TYPE")
+ .param("page", "1"))
+ .andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertEquals(2, body.size());
- verify(actionHistoryServiceMock).findAllWithParams("TYPE", user, 1);
+ verify(actionHistoryServiceMock).findAllWithParams("TYPE", user,
+ PageRequest.of(1, Constants.DEFAULT_PAGE_SIZE));
+ }
+
+ @Test
+ void getActionsPublishesPagingEvent() throws Exception {
+ List actions =
+ IntStream.range(0, 5).mapToObj(i -> Generator.generateActionHistory(user)).toList();
+ final Page page = new PageImpl<>(actions, PageRequest.of(2, 5), 0L);
+ when(actionHistoryServiceMock.findAllWithParams(any(), any(), any(Pageable.class))).thenReturn(page);
+
+ mockMvc.perform(get("/history/").param("page", "2").param("size", "5"));
+ verify(actionHistoryServiceMock).findAllWithParams(null, null, PageRequest.of(2, 5));
+ final ArgumentCaptor captor = ArgumentCaptor.forClass(
+ PaginatedResultRetrievedEvent.class);
+ verify(eventPublisherMock).publishEvent(captor.capture());
+ final PaginatedResultRetrievedEvent event = captor.getValue();
+ assertEquals(page, event.getPage());
}
}
diff --git a/src/test/java/cz/cvut/kbss/study/rest/InstitutionControllerTest.java b/src/test/java/cz/cvut/kbss/study/rest/InstitutionControllerTest.java
index 57db1670..03c5b2be 100644
--- a/src/test/java/cz/cvut/kbss/study/rest/InstitutionControllerTest.java
+++ b/src/test/java/cz/cvut/kbss/study/rest/InstitutionControllerTest.java
@@ -6,6 +6,7 @@
import cz.cvut.kbss.study.environment.util.Environment;
import cz.cvut.kbss.study.model.Institution;
import cz.cvut.kbss.study.model.User;
+import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams;
import cz.cvut.kbss.study.service.InstitutionService;
import cz.cvut.kbss.study.service.PatientRecordService;
import org.junit.jupiter.api.BeforeEach;
@@ -14,6 +15,8 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
@@ -24,6 +27,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
@@ -58,8 +62,8 @@ public void getAllInstitutionsReturnsEmptyListWhenNoInstitutionsAreFound() throw
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertTrue(body.isEmpty());
}
@@ -82,8 +86,8 @@ public void getAllInstitutionsReturnsAlphabeticallySortedInstitutions() throws E
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertEquals("A", body.get(0).getName());
assertEquals("B", body.get(1).getName());
@@ -102,7 +106,7 @@ public void findByKeyReturnsInstitution() throws Exception {
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final Institution res = objectMapper.readValue(result.getResponse().getContentAsString(), Institution.class);
- assertEquals(res.getUri(),institution.getUri());
+ assertEquals(res.getUri(), institution.getUri());
verify(institutionServiceMock).findByKey(key);
}
@@ -130,16 +134,17 @@ public void getTreatedPatientRecordsReturnsRecords() throws Exception {
records.add(record2);
when(institutionServiceMock.findByKey(key)).thenReturn(institution);
- when(patientRecordServiceMock.findByInstitution(institution)).thenReturn(records);
+ when(patientRecordServiceMock.findAll(any(RecordFilterParams.class), any(Pageable.class))).thenReturn(
+ new PageImpl<>(records));
final MvcResult result = mockMvc.perform(get("/institutions/" + key + "/patients/")).andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
- new TypeReference>() {
- });
+ new TypeReference<>() {
+ });
assertEquals(2, body.size());
verify(institutionServiceMock).findByKey(key);
- verify(patientRecordServiceMock).findByInstitution(institution);
+ verify(patientRecordServiceMock).findAll(new RecordFilterParams(key), Pageable.unpaged());
}
@Test
@@ -147,7 +152,8 @@ public void createInstitutionReturnsResponseStatusCreated() throws Exception {
Institution institution = Generator.generateInstitution();
final MvcResult result = mockMvc.perform(post("/institutions/").content(toJson(institution))
- .contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+ .contentType(MediaType.APPLICATION_JSON_VALUE))
+ .andReturn();
assertEquals(HttpStatus.CREATED, HttpStatus.valueOf(result.getResponse().getStatus()));
}
@@ -162,7 +168,9 @@ public void updateInstitutionReturnsResponseStatusNoContent() throws Exception {
when(institutionServiceMock.findByKey(key)).thenReturn(institution);
final MvcResult result = mockMvc.perform(put("/institutions/" + key).content(toJson(institution))
- .contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+ .contentType(
+ MediaType.APPLICATION_JSON_VALUE))
+ .andReturn();
assertEquals(HttpStatus.NO_CONTENT, HttpStatus.valueOf(result.getResponse().getStatus()));
verify(institutionServiceMock).findByKey(key);
@@ -175,8 +183,10 @@ public void updateInstitutionWithNonMatchingKeyReturnsResponseStatusBadRequest()
Institution institution = Generator.generateInstitution();
institution.setKey(key);
- final MvcResult result = mockMvc.perform(put("/institutions/123456" ).content(toJson(institution))
- .contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+ final MvcResult result = mockMvc.perform(put("/institutions/123456").content(toJson(institution))
+ .contentType(
+ MediaType.APPLICATION_JSON_VALUE))
+ .andReturn();
assertEquals(HttpStatus.BAD_REQUEST, HttpStatus.valueOf(result.getResponse().getStatus()));
}
@@ -191,7 +201,9 @@ public void updateInstitutionReturnsResponseStatusNotFound() throws Exception {
when(institutionServiceMock.findByKey(key)).thenReturn(null);
final MvcResult result = mockMvc.perform(put("/institutions/" + key).content(toJson(institution))
- .contentType(MediaType.APPLICATION_JSON_VALUE)).andReturn();
+ .contentType(
+ MediaType.APPLICATION_JSON_VALUE))
+ .andReturn();
assertEquals(HttpStatus.NOT_FOUND, HttpStatus.valueOf(result.getResponse().getStatus()));
verify(institutionServiceMock).findByKey(key);
@@ -206,7 +218,7 @@ public void deleteInstitutionReturnsResponseStatusNoContent() throws Exception {
when(institutionServiceMock.findByKey(key)).thenReturn(institution);
- final MvcResult result = mockMvc.perform(delete("/institutions/12345" )).andReturn();
+ final MvcResult result = mockMvc.perform(delete("/institutions/12345")).andReturn();
assertEquals(HttpStatus.NO_CONTENT, HttpStatus.valueOf(result.getResponse().getStatus()));
verify(institutionServiceMock).findByKey(key);
diff --git a/src/test/java/cz/cvut/kbss/study/rest/PatientRecordControllerTest.java b/src/test/java/cz/cvut/kbss/study/rest/PatientRecordControllerTest.java
index b3ec5d22..901120cb 100644
--- a/src/test/java/cz/cvut/kbss/study/rest/PatientRecordControllerTest.java
+++ b/src/test/java/cz/cvut/kbss/study/rest/PatientRecordControllerTest.java
@@ -11,8 +11,11 @@
import cz.cvut.kbss.study.model.RecordPhase;
import cz.cvut.kbss.study.model.User;
import cz.cvut.kbss.study.persistence.dao.util.RecordFilterParams;
-import cz.cvut.kbss.study.service.InstitutionService;
+import cz.cvut.kbss.study.persistence.dao.util.RecordSort;
+import cz.cvut.kbss.study.rest.event.PaginatedResultRetrievedEvent;
+import cz.cvut.kbss.study.rest.util.RestUtils;
import cz.cvut.kbss.study.service.PatientRecordService;
+import cz.cvut.kbss.study.util.Constants;
import cz.cvut.kbss.study.util.IdentificationUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -21,6 +24,12 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MvcResult;
@@ -55,7 +64,7 @@ public class PatientRecordControllerTest extends BaseControllerTestRunner {
private PatientRecordService patientRecordServiceMock;
@Mock
- private InstitutionService institutionServiceMock;
+ private ApplicationEventPublisher eventPublisherMock;
@InjectMocks
private PatientRecordController controller;
@@ -97,7 +106,8 @@ public void getRecordReturnsFoundRecord() throws Exception {
@Test
public void getRecordsReturnsEmptyListWhenNoReportsAreFound() throws Exception {
- when(patientRecordServiceMock.findAllRecords()).thenReturn(Collections.emptyList());
+ when(patientRecordServiceMock.findAll(any(RecordFilterParams.class), any(Pageable.class))).thenReturn(
+ Page.empty());
final MvcResult result = mockMvc.perform(get("/records/")).andReturn();
@@ -115,28 +125,25 @@ public void getRecordsReturnsAllRecords() throws Exception {
User user1 = Generator.generateUser(institution);
User user2 = Generator.generateUser(institution);
- PatientRecordDto record1 = Generator.generatePatientRecordDto(user1);
- PatientRecordDto record2 = Generator.generatePatientRecordDto(user1);
- PatientRecordDto record3 = Generator.generatePatientRecordDto(user2);
- List records = new ArrayList<>();
- records.add(record1);
- records.add(record2);
- records.add(record3);
+ List records =
+ List.of(Generator.generatePatientRecordDto(user1), Generator.generatePatientRecordDto(user1),
+ Generator.generatePatientRecordDto(user2));
- when(patientRecordServiceMock.findAllRecords()).thenReturn(records);
+ when(patientRecordServiceMock.findAll(any(RecordFilterParams.class), any(Pageable.class))).thenReturn(
+ new PageImpl<>(records));
- final MvcResult result = mockMvc.perform(get("/records/")).andReturn();
+ final MvcResult result = mockMvc.perform(get("/records")).andReturn();
assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
new TypeReference<>() {
});
assertEquals(3, body.size());
- verify(patientRecordServiceMock).findAllRecords();
+ verify(patientRecordServiceMock).findAll(new RecordFilterParams(), Pageable.unpaged());
}
@Test
- public void finByInstitutionReturnsRecords() throws Exception {
+ public void findByInstitutionReturnsRecords() throws Exception {
final String key = "12345";
Institution institution = Generator.generateInstitution();
@@ -151,8 +158,8 @@ public void finByInstitutionReturnsRecords() throws Exception {
records.add(record1);
records.add(record2);
- when(institutionServiceMock.findByKey(institution.getKey())).thenReturn(institution);
- when(patientRecordServiceMock.findByInstitution(institution)).thenReturn(records);
+ when(patientRecordServiceMock.findAll(any(RecordFilterParams.class), any(Pageable.class))).thenReturn(
+ new PageImpl<>(records));
System.out.println(institution.getKey());
final MvcResult result =
mockMvc.perform(get("/records").param("institution", institution.getKey())).andReturn();
@@ -162,17 +169,7 @@ public void finByInstitutionReturnsRecords() throws Exception {
new TypeReference<>() {
});
assertEquals(2, body.size());
- verify(institutionServiceMock).findByKey(institution.getKey());
- }
-
- @Test
- public void findByInstitutionReturnsNotFound() throws Exception {
- final String key = "12345";
-
- when(institutionServiceMock.findByKey(key)).thenReturn(null);
- final MvcResult result = mockMvc.perform(get("/records").param("institution", key)).andReturn();
-
- assertEquals(HttpStatus.NOT_FOUND, HttpStatus.valueOf(result.getResponse().getStatus()));
+ verify(patientRecordServiceMock).findAll(new RecordFilterParams(institution.getKey()), Pageable.unpaged());
}
@Test
@@ -255,7 +252,8 @@ void exportRecordsParsesProvidedDateBoundsAndPassesThemToService() throws Except
final LocalDate maxDate = LocalDate.now().minusDays(5);
final List records =
List.of(Generator.generatePatientRecord(user), Generator.generatePatientRecord(user));
- when(patientRecordServiceMock.findAllFull(any(RecordFilterParams.class))).thenReturn(records);
+ when(patientRecordServiceMock.findAllFull(any(RecordFilterParams.class), any(
+ Pageable.class))).thenReturn(new PageImpl<>(records));
final MvcResult mvcResult = mockMvc.perform(get("/records/export")
.param("minDate", minDate.toString())
@@ -265,20 +263,21 @@ void exportRecordsParsesProvidedDateBoundsAndPassesThemToService() throws Except
});
assertThat(result, containsSameEntities(records));
verify(patientRecordServiceMock).findAllFull(
- new RecordFilterParams(null, minDate, maxDate, Collections.emptySet()));
+ new RecordFilterParams(null, minDate, maxDate, Collections.emptySet()), Pageable.unpaged());
}
@Test
void exportRecordsUsesDefaultValuesForMinAndMaxDateWhenTheyAreNotProvidedByRequest() throws Exception {
final List records =
List.of(Generator.generatePatientRecord(user), Generator.generatePatientRecord(user));
- when(patientRecordServiceMock.findAllFull(any(RecordFilterParams.class))).thenReturn(records);
+ when(patientRecordServiceMock.findAllFull(any(RecordFilterParams.class), any(
+ Pageable.class))).thenReturn(new PageImpl<>(records));
final MvcResult mvcResult = mockMvc.perform(get("/records/export")).andReturn();
final List result = readValue(mvcResult, new TypeReference<>() {
});
assertThat(result, containsSameEntities(records));
- verify(patientRecordServiceMock).findAllFull(new RecordFilterParams());
+ verify(patientRecordServiceMock).findAllFull(new RecordFilterParams(), Pageable.unpaged());
}
@Test
@@ -287,7 +286,8 @@ void exportRecordsExportsRecordsForProvidedInstitutionForSpecifiedPeriod() throw
final LocalDate maxDate = LocalDate.now().minusDays(5);
final List records =
List.of(Generator.generatePatientRecord(user), Generator.generatePatientRecord(user));
- when(patientRecordServiceMock.findAllFull(any(RecordFilterParams.class))).thenReturn(records);
+ when(patientRecordServiceMock.findAllFull(any(RecordFilterParams.class), any(
+ Pageable.class))).thenReturn(new PageImpl<>(records));
final MvcResult mvcResult = mockMvc.perform(get("/records/export")
.param("minDate", minDate.toString())
@@ -298,7 +298,8 @@ void exportRecordsExportsRecordsForProvidedInstitutionForSpecifiedPeriod() throw
});
assertThat(result, containsSameEntities(records));
verify(patientRecordServiceMock).findAllFull(
- new RecordFilterParams(user.getInstitution().getKey(), minDate, maxDate, Collections.emptySet()));
+ new RecordFilterParams(user.getInstitution().getKey(), minDate, maxDate, Collections.emptySet()),
+ Pageable.unpaged());
}
@Test
@@ -343,4 +344,84 @@ void importRecordsReturnsConflictWhenServiceThrowsRecordAuthorNotFound() throws
mockMvc.perform(post("/records/import").content(toJson(records)).contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isConflict());
}
+
+ @Test
+ void getRecordsResolvesPagingConfigurationFromRequestParameters() throws Exception {
+ final LocalDate minDate = LocalDate.now().minusDays(35);
+ final LocalDate maxDate = LocalDate.now().minusDays(5);
+ final int page = Generator.randomInt(0, 5);
+ final int pageSize = Generator.randomInt(30, 50);
+ final List records =
+ List.of(Generator.generatePatientRecord(user), Generator.generatePatientRecord(user));
+ when(patientRecordServiceMock.findAllFull(any(RecordFilterParams.class), any(
+ Pageable.class))).thenReturn(new PageImpl<>(records));
+
+ final MvcResult mvcResult = mockMvc.perform(get("/records/export")
+ .param("minDate", minDate.toString())
+ .param("maxDate", maxDate.toString())
+ .param(Constants.PAGE_PARAM, Integer.toString(page))
+ .param(Constants.PAGE_SIZE_PARAM,
+ Integer.toString(pageSize))
+ .param(Constants.SORT_PARAM,
+ RestUtils.SORT_DESC + RecordSort.SORT_DATE_PROPERTY))
+ .andReturn();
+ final List result = readValue(mvcResult, new TypeReference<>() {
+ });
+ assertThat(result, containsSameEntities(records));
+ verify(patientRecordServiceMock).findAllFull(
+ new RecordFilterParams(null, minDate, maxDate, Collections.emptySet()),
+ PageRequest.of(page, pageSize, Sort.Direction.DESC, RecordSort.SORT_DATE_PROPERTY));
+ }
+
+ @Test
+ void getRecordsPublishesPagingEvent() throws Exception {
+ List records =
+ List.of(Generator.generatePatientRecordDto(user), Generator.generatePatientRecordDto(user),
+ Generator.generatePatientRecordDto(user));
+
+ final Page page = new PageImpl<>(records, PageRequest.of(0, 5), 3);
+ when(patientRecordServiceMock.findAll(any(RecordFilterParams.class), any(Pageable.class))).thenReturn(page);
+ final MvcResult result = mockMvc.perform(get("/records").queryParam(Constants.PAGE_PARAM, "0")
+ .queryParam(Constants.PAGE_SIZE_PARAM, "5"))
+ .andReturn();
+
+ assertEquals(HttpStatus.OK, HttpStatus.valueOf(result.getResponse().getStatus()));
+ final List body = objectMapper.readValue(result.getResponse().getContentAsString(),
+ new TypeReference<>() {
+ });
+ assertEquals(3, body.size());
+ verify(patientRecordServiceMock).findAll(new RecordFilterParams(), PageRequest.of(0, 5));
+ final ArgumentCaptor captor = ArgumentCaptor.forClass(
+ PaginatedResultRetrievedEvent.class);
+ verify(eventPublisherMock).publishEvent(captor.capture());
+ final PaginatedResultRetrievedEvent event = captor.getValue();
+ assertEquals(page, event.getPage());
+ }
+
+ @Test
+ void exportRecordsPublishesPagingEvent() throws Exception {
+ final LocalDate minDate = LocalDate.now().minusDays(35);
+ final LocalDate maxDate = LocalDate.now().minusDays(5);
+ final List records =
+ List.of(Generator.generatePatientRecord(user), Generator.generatePatientRecord(user));
+ final Page page = new PageImpl<>(records, PageRequest.of(0, 50), 100);
+ when(patientRecordServiceMock.findAllFull(any(RecordFilterParams.class), any(Pageable.class))).thenReturn(page);
+
+ final MvcResult mvcResult = mockMvc.perform(get("/records/export")
+ .param("minDate", minDate.toString())
+ .param("maxDate", maxDate.toString())
+ .param(Constants.PAGE_PARAM, "0")
+ .param(Constants.PAGE_SIZE_PARAM, "50"))
+ .andReturn();
+ final List result = readValue(mvcResult, new TypeReference<>() {
+ });
+ assertThat(result, containsSameEntities(records));
+ verify(patientRecordServiceMock).findAllFull(
+ new RecordFilterParams(null, minDate, maxDate, Collections.emptySet()), PageRequest.of(0, 50));
+ final ArgumentCaptor captor =
+ ArgumentCaptor.forClass(PaginatedResultRetrievedEvent.class);
+ verify(eventPublisherMock).publishEvent(captor.capture());
+ final PaginatedResultRetrievedEvent event = captor.getValue();
+ assertEquals(page, event.getPage());
+ }
}
diff --git a/src/test/java/cz/cvut/kbss/study/rest/handler/HateoasPagingListenerTest.java b/src/test/java/cz/cvut/kbss/study/rest/handler/HateoasPagingListenerTest.java
new file mode 100644
index 00000000..0d582ce9
--- /dev/null
+++ b/src/test/java/cz/cvut/kbss/study/rest/handler/HateoasPagingListenerTest.java
@@ -0,0 +1,185 @@
+package cz.cvut.kbss.study.rest.handler;
+
+import cz.cvut.kbss.study.dto.PatientRecordDto;
+import cz.cvut.kbss.study.environment.generator.Generator;
+import cz.cvut.kbss.study.model.User;
+import cz.cvut.kbss.study.rest.event.PaginatedResultRetrievedEvent;
+import cz.cvut.kbss.study.rest.util.HttpPaginationLink;
+import cz.cvut.kbss.study.util.Constants;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class HateoasPagingListenerTest {
+
+ private static final String BASE_URL = "http://localhost/rest/records";
+
+ private UriComponentsBuilder uriBuilder;
+ private MockHttpServletResponse responseMock;
+
+ private List records;
+
+ private HateoasPagingListener listener;
+
+ @BeforeEach
+ public void setUp() {
+ this.listener = new HateoasPagingListener();
+ this.uriBuilder = UriComponentsBuilder.newInstance().scheme("http").host("localhost").path("rest/records");
+ this.responseMock = new MockHttpServletResponse();
+ final User author = Generator.generateUser(null);
+ this.records = IntStream.range(0, 10).mapToObj(i -> Generator.generatePatientRecordDto(author))
+ .collect(Collectors.toList());
+ }
+
+ @Test
+ public void generatesNextRelativeLink() {
+ final int size = 5;
+ final Page page =
+ new PageImpl<>(records.subList(0, size), PageRequest.of(0, size), records.size());
+ listener.onApplicationEvent(event(page));
+ final String linkHeader = responseMock.getHeader(HttpHeaders.LINK);
+ assertNotNull(linkHeader);
+ final String nextLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.NEXT.getName());
+ assertThat(nextLink, containsString(BASE_URL));
+ assertThat(nextLink, containsString(page(1)));
+ assertThat(nextLink, containsString(pageSize(size)));
+ }
+
+ private static String page(int pageNo) {
+ return Constants.PAGE_PARAM + "=" + pageNo;
+ }
+
+ private static String pageSize(int size) {
+ return Constants.PAGE_SIZE_PARAM + "=" + size;
+ }
+
+ private PaginatedResultRetrievedEvent event(Page page) {
+ return new PaginatedResultRetrievedEvent(this, uriBuilder, responseMock, page);
+ }
+
+ @Test
+ public void generatesLastRelativeLink() {
+ final int size = 5;
+ final Page page =
+ new PageImpl<>(records.subList(0, size), PageRequest.of(0, size), records.size());
+ listener.onApplicationEvent(event(page));
+ final String linkHeader = responseMock.getHeader(HttpHeaders.LINK);
+ assertNotNull(linkHeader);
+ final String lastLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.LAST.getName());
+ assertThat(lastLink, containsString(BASE_URL));
+ assertThat(lastLink, containsString(page(1)));
+ assertThat(lastLink, containsString(pageSize(size)));
+ }
+
+ @Test
+ public void generatesPreviousRelativeLink() {
+ final int size = 5;
+ final Page page =
+ new PageImpl<>(records.subList(size, records.size()), PageRequest.of(1, size),
+ records.size());
+ listener.onApplicationEvent(event(page));
+ final String linkHeader = responseMock.getHeader(HttpHeaders.LINK);
+ assertNotNull(linkHeader);
+ final String lastLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.PREVIOUS.getName());
+ assertThat(lastLink, containsString(BASE_URL));
+ assertThat(lastLink, containsString(page(0)));
+ assertThat(lastLink, containsString(pageSize(size)));
+ }
+
+ @Test
+ public void generatesFirstRelativeLink() {
+ final int size = 5;
+ final Page page =
+ new PageImpl<>(records.subList(size, records.size()), PageRequest.of(1, size),
+ records.size());
+ listener.onApplicationEvent(event(page));
+ final String linkHeader = responseMock.getHeader(HttpHeaders.LINK);
+ assertNotNull(linkHeader);
+ final String lastLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.FIRST.getName());
+ assertThat(lastLink, containsString(BASE_URL));
+ assertThat(lastLink, containsString(page(0)));
+ assertThat(lastLink, containsString(pageSize(size)));
+ }
+
+ @Test
+ public void generatesAllRelativeLinks() {
+ final int size = 3;
+ final int pageNum = 2;
+ final Page page = new PageImpl<>(records.subList(pageNum * size, pageNum * size + size),
+ PageRequest.of(pageNum, size), records.size());
+ listener.onApplicationEvent(event(page));
+ final String linkHeader = responseMock.getHeader(HttpHeaders.LINK);
+ assertNotNull(linkHeader);
+ final String nextLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.NEXT.getName());
+ assertThat(nextLink, containsString(page(pageNum + 1)));
+ assertThat(nextLink, containsString(pageSize(size)));
+ final String previousLink =
+ HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.PREVIOUS.getName());
+ assertThat(previousLink, containsString(page(pageNum - 1)));
+ assertThat(previousLink, containsString(pageSize(size)));
+ final String firstLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.FIRST.getName());
+ assertThat(firstLink, containsString(page(0)));
+ assertThat(firstLink, containsString(pageSize(size)));
+ final String lastLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.LAST.getName());
+ assertThat(lastLink, containsString(page(3)));
+ assertThat(lastLink, containsString(pageSize(size)));
+ }
+
+ @Test
+ public void generatesNoLinksForEmptyPage() {
+ final int size = 5;
+ final Page page = new PageImpl<>(Collections.emptyList(), PageRequest.of(0, size), 0);
+ listener.onApplicationEvent(event(page));
+ final String linkHeader = responseMock.getHeader(HttpHeaders.LINK);
+ assertNull(linkHeader);
+ }
+
+ @Test
+ public void generatesPreviousAndFirstLinkForEmptyPageAfterEnd() {
+ final int size = 5;
+ final int pageNum = 4;
+ final Page page = new PageImpl<>(Collections.emptyList(), PageRequest.of(pageNum, size),
+ records.size());
+ listener.onApplicationEvent(event(page));
+ final String linkHeader = responseMock.getHeader(HttpHeaders.LINK);
+ assertNotNull(linkHeader);
+ final String previousLink =
+ HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.PREVIOUS.getName());
+ assertThat(previousLink, containsString(page(pageNum - 1)));
+ assertThat(previousLink, containsString(pageSize(size)));
+ final String firstLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.FIRST.getName());
+ assertThat(firstLink, containsString(page(0)));
+ assertThat(firstLink, containsString(pageSize(size)));
+ }
+
+ @Test
+ public void generatesFirstAndLastLinksForOnlyPage() {
+ final int size = records.size();
+ final Page page = new PageImpl<>(records, PageRequest.of(0, size), records.size());
+ listener.onApplicationEvent(event(page));
+ final String linkHeader = responseMock.getHeader(HttpHeaders.LINK);
+ assertNotNull(linkHeader);
+ final String firstLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.FIRST.getName());
+ assertThat(firstLink, containsString(page(0)));
+ assertThat(firstLink, containsString(pageSize(size)));
+ final String lastLink = HttpLinkHeaderUtil.extractURIByRel(linkHeader, HttpPaginationLink.LAST.getName());
+ assertThat(lastLink, containsString(page(0)));
+ assertThat(lastLink, containsString(pageSize(size)));
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/cz/cvut/kbss/study/rest/handler/HttpLinkHeaderUtil.java b/src/test/java/cz/cvut/kbss/study/rest/handler/HttpLinkHeaderUtil.java
new file mode 100644
index 00000000..75905662
--- /dev/null
+++ b/src/test/java/cz/cvut/kbss/study/rest/handler/HttpLinkHeaderUtil.java
@@ -0,0 +1,32 @@
+package cz.cvut.kbss.study.rest.handler;
+
+public class HttpLinkHeaderUtil {
+
+ private HttpLinkHeaderUtil() {
+ throw new AssertionError();
+ }
+
+ public static String extractURIByRel(final String linkHeader, final String rel) {
+ if (linkHeader == null) {
+ return null;
+ }
+ String uriWithSpecifiedRel = null;
+ final String[] links = linkHeader.split(", ");
+ String linkRelation;
+ for (final String link : links) {
+ final int positionOfSeparator = link.indexOf(';');
+ linkRelation = link.substring(positionOfSeparator + 1).trim();
+ if (extractTypeOfRelation(linkRelation).equals(rel)) {
+ uriWithSpecifiedRel = link.substring(0, positionOfSeparator);
+ break;
+ }
+ }
+
+ return uriWithSpecifiedRel;
+ }
+
+ private static Object extractTypeOfRelation(final String linkRelation) {
+ final int positionOfEquals = linkRelation.indexOf('=');
+ return linkRelation.substring(positionOfEquals + 2, linkRelation.length() - 1).trim();
+ }
+}
diff --git a/src/test/java/cz/cvut/kbss/study/rest/util/RecordFilterMapperTest.java b/src/test/java/cz/cvut/kbss/study/rest/util/RecordFilterMapperTest.java
index 959b08c0..ec36d28d 100644
--- a/src/test/java/cz/cvut/kbss/study/rest/util/RecordFilterMapperTest.java
+++ b/src/test/java/cz/cvut/kbss/study/rest/util/RecordFilterMapperTest.java
@@ -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 org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -40,16 +41,16 @@ static Stream testValues() {
)), new RecordFilterParams("1111111", LocalDate.EPOCH, LocalDate.now(), Collections.emptySet())),
Arguments.of(new LinkedMultiValueMap<>(Map.of(
"institution", List.of("1111111"),
- "phase", List.of("http://example.org/phaseOne", "http://example.org/phaseTwo")
+ "phase", List.of(RecordPhase.open.getIri(), RecordPhase.completed.name())
)), new RecordFilterParams("1111111", LocalDate.EPOCH, LocalDate.now(),
- Set.of("http://example.org/phaseOne", "http://example.org/phaseTwo"))),
+ Set.of(RecordPhase.open.getIri(), RecordPhase.completed.getIri()))),
Arguments.of(new LinkedMultiValueMap<>(Map.of(
"minDate", List.of(LocalDate.now().minusYears(1).toString()),
"maxDate", List.of(LocalDate.now().minusDays(1).toString()),
"institution", List.of("1111111"),
- "phase", List.of("http://example.org/phaseOne")
+ "phase", List.of(RecordPhase.published.name())
)), new RecordFilterParams("1111111", LocalDate.now().minusYears(1), LocalDate.now().minusDays(1),
- Set.of("http://example.org/phaseOne")))
+ Set.of(RecordPhase.published.getIri())))
);
}
}
\ No newline at end of file
diff --git a/src/test/java/cz/cvut/kbss/study/rest/util/RestUtilsTest.java b/src/test/java/cz/cvut/kbss/study/rest/util/RestUtilsTest.java
index a2045f83..ca7df782 100644
--- a/src/test/java/cz/cvut/kbss/study/rest/util/RestUtilsTest.java
+++ b/src/test/java/cz/cvut/kbss/study/rest/util/RestUtilsTest.java
@@ -1,14 +1,30 @@
package cz.cvut.kbss.study.rest.util;
+import cz.cvut.kbss.study.environment.generator.Generator;
+import cz.cvut.kbss.study.persistence.dao.util.RecordSort;
+import cz.cvut.kbss.study.util.Constants;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
class RestUtilsTest {
@@ -22,14 +38,86 @@ void parseTimestampReturnsLocalDateParsedFromSpecifiedString() {
void parseTimestampThrowsResponseStatusExceptionWithStatus400ForUnparseableString() {
final Date date = new Date();
final ResponseStatusException ex = assertThrows(ResponseStatusException.class,
- () -> RestUtils.parseDate(date.toString()));
+ () -> RestUtils.parseDate(date.toString()));
assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode());
}
@Test
void parseTimestampThrowsResponseStatusExceptionWithStatus400ForNullArgument() {
final ResponseStatusException ex = assertThrows(ResponseStatusException.class,
- () -> RestUtils.parseDate(null));
+ () -> RestUtils.parseDate(null));
assertEquals(HttpStatus.BAD_REQUEST, ex.getStatusCode());
}
+
+ @Test
+ void resolvePagingCreatesPageableObjectWithSpecifiedPageSizeAndNumber() {
+ final int page = Generator.randomInt(0, 10);
+ final int size = Generator.randomInt(20, 50);
+ final MultiValueMap params = new LinkedMultiValueMap<>(Map.of(
+ Constants.PAGE_PARAM, List.of(Integer.toString(page)),
+ Constants.PAGE_SIZE_PARAM, List.of(Integer.toString(size))));
+
+ final Pageable result = RestUtils.resolvePaging(params);
+ assertEquals(PageRequest.of(page, size), result);
+ }
+
+ @Test
+ void resolvePagingReturnsUnpagedObjectWhenNoPageNumberIsSpecified() {
+ final MultiValueMap params = new LinkedMultiValueMap<>();
+ final Pageable result = RestUtils.resolvePaging(params);
+ assertTrue(result.isUnpaged());
+ }
+
+ @Test
+ void resolvePagingReturnsPagedObjectWithDefaultPageSizeWhenNoPageSizeIsSpecified() {
+ final int page = Generator.randomInt(0, 10);
+ final MultiValueMap params = new LinkedMultiValueMap<>(Map.of(
+ Constants.PAGE_PARAM, List.of(Integer.toString(page))));
+
+ final Pageable result = RestUtils.resolvePaging(params);
+ assertEquals(PageRequest.of(page, Constants.DEFAULT_PAGE_SIZE), result);
+ }
+
+ @ParameterizedTest
+ @MethodSource("sortTestArguments")
+ void resolvePagingAddsSpecifiedSortPropertyAndDirectionToResult(String sortParam, Sort.Order expectedOrder) {
+ final int page = Generator.randomInt(0, 10);
+ final int size = Generator.randomInt(20, 50);
+ final MultiValueMap params = new LinkedMultiValueMap<>(Map.of(
+ Constants.PAGE_PARAM, List.of(Integer.toString(page)),
+ Constants.PAGE_SIZE_PARAM, List.of(Integer.toString(size)),
+ Constants.SORT_PARAM, List.of(sortParam)));
+
+ final Pageable result = RestUtils.resolvePaging(params);
+ assertNotNull(result.getSort());
+ assertEquals(expectedOrder, result.getSort().getOrderFor(expectedOrder.getProperty()));
+ }
+
+ protected static Stream sortTestArguments() {
+ return Stream.of(
+ Arguments.of(RestUtils.SORT_DESC + RecordSort.SORT_DATE_PROPERTY,
+ Sort.Order.desc(RecordSort.SORT_DATE_PROPERTY)),
+ Arguments.of(RestUtils.SORT_ASC + RecordSort.SORT_DATE_PROPERTY,
+ Sort.Order.asc(RecordSort.SORT_DATE_PROPERTY)),
+ Arguments.of(RecordSort.SORT_DATE_PROPERTY, Sort.Order.asc(RecordSort.SORT_DATE_PROPERTY))
+ );
+ }
+
+ @Test
+ void resolvePagingSupportsMultipleSortValues() {
+ final String anotherSort = "name";
+ final int page = Generator.randomInt(0, 10);
+ final int size = Generator.randomInt(20, 50);
+ final MultiValueMap params = new LinkedMultiValueMap<>(Map.of(
+ Constants.PAGE_PARAM, List.of(Integer.toString(page)),
+ Constants.PAGE_SIZE_PARAM, List.of(Integer.toString(size)),
+ Constants.SORT_PARAM, List.of(RecordSort.SORT_DATE_PROPERTY, RestUtils.SORT_ASC + anotherSort)));
+
+ final Pageable result = RestUtils.resolvePaging(params);
+ assertNotNull(result.getSort());
+ assertEquals(Sort.Order.asc(RecordSort.SORT_DATE_PROPERTY),
+ result.getSort().getOrderFor(RecordSort.SORT_DATE_PROPERTY));
+ assertEquals(Sort.Order.asc(anotherSort),
+ result.getSort().getOrderFor(anotherSort));
+ }
}
\ No newline at end of file
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
index 93b37394..a0506b13 100644
--- a/src/test/resources/logback-test.xml
+++ b/src/test/resources/logback-test.xml
@@ -24,12 +24,7 @@
-
-
-
-
-
-
+