Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
blcham committed Feb 9, 2024
2 parents 79b21c4 + f554753 commit d164a28
Show file tree
Hide file tree
Showing 38 changed files with 1,263 additions and 322 deletions.
9 changes: 6 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<version>3.2.2</version>
</parent>

<artifactId>record-manager</artifactId>
Expand Down Expand Up @@ -48,7 +48,6 @@
<maven.compiler.target>${jdk.version}</maven.compiler.target>

<cz.cvut.kbss.jopa.version>2.0.0-SNAPSHOT</cz.cvut.kbss.jopa.version>
<org.aspectj.version>1.9.20</org.aspectj.version>
<org.mockito.version>4.11.0</org.mockito.version>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>
Expand All @@ -71,6 +70,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
Expand Down Expand Up @@ -98,7 +101,7 @@
<dependency>
<groupId>com.github.ledsoft</groupId>
<artifactId>jopa-spring-transaction</artifactId>
<version>0.2.0</version>
<version>0.3.0-SNAPSHOT</version>
</dependency>

<!-- Logging -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ static CorsConfigurationSource createCorsConfiguration(ConfigReader configReader
corsConfiguration.addExposedHeader(HttpHeaders.AUTHORIZATION);
corsConfiguration.addExposedHeader(HttpHeaders.LOCATION);
corsConfiguration.addExposedHeader(HttpHeaders.CONTENT_DISPOSITION);
corsConfiguration.addExposedHeader(HttpHeaders.LINK);

final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/cz/cvut/kbss/study/config/WebAppConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
17 changes: 9 additions & 8 deletions src/main/java/cz/cvut/kbss/study/dto/PatientRecordDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -21,17 +22,17 @@ 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;

@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;
Expand All @@ -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;
}

Expand All @@ -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;
}

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

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

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

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

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

public List<ActionHistory> findAllWithParams(String typeFilter, User author, int pageNumber) {
public Page<ActionHistory> findAllWithParams(String typeFilter, User author, Pageable pageSpec) {
String params;
if (typeFilter == null && author == null) {
params = " } ";
Expand All @@ -52,10 +54,11 @@ public List<ActionHistory> findAllWithParams(String typeFilter, User author, int
params + "ORDER BY DESC(?timestamp)",
ActionHistory.class)
.setParameter("type", typeUri)
.setParameter("isCreated", URI.create(Vocabulary.s_p_created))
.setFirstResult((pageNumber - 1) * Constants.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());
Expand All @@ -64,6 +67,6 @@ public List<ActionHistory> findAllWithParams(String typeFilter, User author, int
q.setParameter("typeFilter", typeFilter, Constants.PU_LANGUAGE)
.setParameter("isType", URI.create(Vocabulary.s_p_action_type));
}
return q.getResultList();
return new PageImpl<>(q.getResultList(), pageSpec, 0L);
}
}
110 changes: 88 additions & 22 deletions src/main/java/cz/cvut/kbss/study/persistence/dao/PatientRecordDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -177,52 +182,97 @@ public void requireUniqueNonEmptyLocalName(PatientRecord entity) {
em.clear();
}

/**
* Retrieves DTOs of records matching the specified filtering criteria.
* <p>
* 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.
* <p>
* 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<PatientRecordDto> findAllRecords(RecordFilterParams filters, Pageable pageSpec) {
Objects.requireNonNull(filters);
Objects.requireNonNull(pageSpec);
return findRecords(filters, pageSpec, PatientRecordDto.class);
}

/**
* Retrieves records matching the specified filtering criteria.
* <p>
* 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.
* <p>
* 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<PatientRecord> findAllFull(RecordFilterParams filterParams) {
Objects.requireNonNull(filterParams);
public Page<PatientRecord> findAllRecordsFull(RecordFilterParams filters, Pageable pageSpec) {
Objects.requireNonNull(filters);
Objects.requireNonNull(pageSpec);
return findRecords(filters, pageSpec, PatientRecord.class);
}

private <T> Page<T> findRecords(RecordFilterParams filters, Pageable pageSpec, Class<T> resultClass) {
final Map<String, Object> queryParams = new HashMap<>();
final String whereClause = constructWhereClause(filters, queryParams);
final String queryString = "SELECT ?r WHERE " + whereClause + resolveOrderBy(pageSpec.getSortOr(RecordSort.defaultSort()));
final TypedQuery<T> query = em.createNativeQuery(queryString, resultClass);
setQueryParameters(query, queryParams);
if (pageSpec.isPaged()) {
query.setFirstResult((int) pageSpec.getOffset());
query.setMaxResults(pageSpec.getPageSize());
}
final List<T> records = query.getResultList();
final TypedQuery<Integer> 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<String, Object> 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<String, Object> 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<String, Object> queryParams = new HashMap<>();
queryString += mapParamsToQuery(filterParams, queryParams);
queryString += "} ORDER BY ?edited";

final TypedQuery<PatientRecord> 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<String, Object> queryParams) {
final List<String> filters = new ArrayList<>();
filterParams.getInstitutionKey()
.ifPresent(key -> 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()) {
Expand All @@ -232,4 +282,20 @@ private static String mapParamsToQuery(RecordFilterParams filterParams, Map<Stri
}
return String.join(" ", filters);
}

private static String resolveOrderBy(Sort sort) {
if (sort.isUnsorted()) {
return "";
}
final StringBuilder sb = new StringBuilder(" ORDER BY");
for (Sort.Order o : sort) {
if (!RecordSort.SORTING_PROPERTIES.contains(o.getProperty())) {
throw new IllegalArgumentException("Unsupported record sorting property '" + o.getProperty() + "'.");
}
sb.append(' ');
sb.append(o.isAscending() ? "ASC(" : "DESC(");
sb.append('?').append(o.getProperty()).append(')');
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public class RecordFilterParams {
public RecordFilterParams() {
}

public RecordFilterParams(String institutionKey) {
this.institutionKey = institutionKey;
}

// This one mainly is for test data setup
public RecordFilterParams(String institutionKey, LocalDate minModifiedDate, LocalDate maxModifiedDate,
Set<String> phaseIds) {
Expand Down
Loading

0 comments on commit d164a28

Please sign in to comment.