From 3640e5d6b221ea64c296f1deb581d6b247718fbf Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Thu, 7 May 2026 15:01:45 -0400 Subject: [PATCH 1/7] Add new filter/sort models --- src/main/java/io/swagger/model/FilterBy.java | 25 +++++++++++++++ .../java/io/swagger/model/SearchRequest.java | 7 +++++ .../java/io/swagger/model/core/SortBy.java | 2 ++ .../model/core/StudySearchRequest.java | 2 ++ .../model/core/TrialSearchRequest.java | 23 +------------- .../java/io/swagger/model/sort/SortBy.java | 31 +++++++++++++++++++ .../model/{core => sort}/SortOrder.java | 2 +- .../service/SearchQueryBuilder.java | 2 +- .../service/core/StudyService.java | 3 +- .../service/core/TrialService.java | 1 + 10 files changed, 72 insertions(+), 26 deletions(-) create mode 100644 src/main/java/io/swagger/model/FilterBy.java create mode 100644 src/main/java/io/swagger/model/sort/SortBy.java rename src/main/java/io/swagger/model/{core => sort}/SortOrder.java (95%) diff --git a/src/main/java/io/swagger/model/FilterBy.java b/src/main/java/io/swagger/model/FilterBy.java new file mode 100644 index 00000000..a34afb7d --- /dev/null +++ b/src/main/java/io/swagger/model/FilterBy.java @@ -0,0 +1,25 @@ +package io.swagger.model; + +import io.swagger.model.sort.SortOrder; + +public class FilterBy { + private String filterOn; + private boolean addInfoColumn = false; + + public String getFilterOn() { + return filterOn; + } + + public void setFilterOn(String filterOn) { + this.filterOn = filterOn; + } + + public boolean isAddInfoColumn() { + return addInfoColumn; + } + + public void setAddInfoColumn(boolean addInfoColumn) { + this.addInfoColumn = addInfoColumn; + } + +} diff --git a/src/main/java/io/swagger/model/SearchRequest.java b/src/main/java/io/swagger/model/SearchRequest.java index 8ad217c4..ae723e1c 100644 --- a/src/main/java/io/swagger/model/SearchRequest.java +++ b/src/main/java/io/swagger/model/SearchRequest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.model.sort.SortBy; import java.util.ArrayList; import java.util.List; @@ -25,6 +26,12 @@ public abstract class SearchRequest { @JsonProperty("externalReferenceSources") protected List externalReferenceSources = null; + @JsonProperty("filterBy") + protected FilterBy filterBy = null; + + @JsonProperty("sortBy") + protected List sortBy = null; + final public SearchRequest page(Integer page) { this.page = page; return this; diff --git a/src/main/java/io/swagger/model/core/SortBy.java b/src/main/java/io/swagger/model/core/SortBy.java index 8a132a3e..bf215cc5 100644 --- a/src/main/java/io/swagger/model/core/SortBy.java +++ b/src/main/java/io/swagger/model/core/SortBy.java @@ -3,6 +3,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; +@Deprecated +// TODO: Phase out in favor of io.swagger.model.sort objects public enum SortBy { STUDYDBID("studyDbId"), diff --git a/src/main/java/io/swagger/model/core/StudySearchRequest.java b/src/main/java/io/swagger/model/core/StudySearchRequest.java index 0b26436b..26849475 100644 --- a/src/main/java/io/swagger/model/core/StudySearchRequest.java +++ b/src/main/java/io/swagger/model/core/StudySearchRequest.java @@ -5,6 +5,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.model.SearchRequest; +import io.swagger.model.sort.SortOrder; + import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/swagger/model/core/TrialSearchRequest.java b/src/main/java/io/swagger/model/core/TrialSearchRequest.java index 830dde51..5a4b741f 100644 --- a/src/main/java/io/swagger/model/core/TrialSearchRequest.java +++ b/src/main/java/io/swagger/model/core/TrialSearchRequest.java @@ -3,6 +3,7 @@ import java.util.Objects; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.model.SearchRequest; + import java.util.ArrayList; import java.util.List; import java.time.LocalDate; @@ -50,28 +51,6 @@ public class TrialSearchRequest extends SearchRequest { @JsonProperty("trialPUIs") private List trialPUIs = null; - @JsonProperty("sortBy") - private SortBy sortBy = null; - - @JsonProperty("sortOrder") - private SortOrder sortOrder = null; - - public SortBy getSortBy() { - return sortBy; - } - - public void setSortBy(SortBy sortBy) { - this.sortBy = sortBy; - } - - public SortOrder getSortOrder() { - return sortOrder; - } - - public void setSortOrder(SortOrder sortOrder) { - this.sortOrder = sortOrder; - } - public TrialSearchRequest commonCropNames(List commonCropNames) { this.commonCropNames = commonCropNames; return this; diff --git a/src/main/java/io/swagger/model/sort/SortBy.java b/src/main/java/io/swagger/model/sort/SortBy.java new file mode 100644 index 00000000..8501bd88 --- /dev/null +++ b/src/main/java/io/swagger/model/sort/SortBy.java @@ -0,0 +1,31 @@ +package io.swagger.model.sort; + +public class SortBy { + private String sortedOn; + private SortOrder sortOrder = SortOrder.ASC; + private boolean addInfoColumn = false; + + public String getSortedOn() { + return sortedOn; + } + + public void setSortedOn(String sortedOn) { + this.sortedOn = sortedOn; + } + + public SortOrder getSortOrder() { + return sortOrder; + } + + public void setSortOrder(SortOrder sortOrder) { + this.sortOrder = sortOrder; + } + + public boolean isAddInfoColumn() { + return addInfoColumn; + } + + public void setAddInfoColumn(boolean addInfoColumn) { + this.addInfoColumn = addInfoColumn; + } +} diff --git a/src/main/java/io/swagger/model/core/SortOrder.java b/src/main/java/io/swagger/model/sort/SortOrder.java similarity index 95% rename from src/main/java/io/swagger/model/core/SortOrder.java rename to src/main/java/io/swagger/model/sort/SortOrder.java index 0e5b36bd..4cc9d5aa 100644 --- a/src/main/java/io/swagger/model/core/SortOrder.java +++ b/src/main/java/io/swagger/model/sort/SortOrder.java @@ -1,4 +1,4 @@ -package io.swagger.model.core; +package io.swagger.model.sort; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java index 80bf5bc5..74ef4677 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java @@ -7,7 +7,7 @@ import java.time.OffsetDateTime; import io.swagger.model.GeoJSONSearchArea; -import io.swagger.model.core.SortOrder; +import io.swagger.model.sort.SortOrder; public class SearchQueryBuilder { diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/core/StudyService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/core/StudyService.java index 1b6526ea..4c0cb198 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/core/StudyService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/core/StudyService.java @@ -6,7 +6,6 @@ import org.apache.commons.lang3.StringUtils; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; -import org.brapi.test.BrAPITestServer.model.entity.BrAPIBaseEntity; import org.brapi.test.BrAPITestServer.model.entity.core.CropEntity; import org.brapi.test.BrAPITestServer.model.entity.core.DataLinkEntity; import org.brapi.test.BrAPITestServer.model.entity.core.EnvironmentParametersEntity; @@ -39,7 +38,7 @@ import io.swagger.model.core.Contact; import io.swagger.model.core.EnvironmentParameter; import io.swagger.model.core.SortBy; -import io.swagger.model.core.SortOrder; +import io.swagger.model.sort.SortOrder; import io.swagger.model.core.Study; import io.swagger.model.core.StudyExperimentalDesign; import io.swagger.model.core.StudyGrowthFacility; diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java index fb798a5b..36ac0b0a 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java @@ -4,6 +4,7 @@ import java.util.stream.Collectors; import io.swagger.model.core.*; +import io.swagger.model.sort.SortOrder; import jakarta.validation.Valid; import org.brapi.test.BrAPITestServer.exceptions.BatchDeleteWrongTypeException; From fd641789e287f6d39342d8d4763db452d6117f01 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Thu, 7 May 2026 18:57:31 -0400 Subject: [PATCH 2/7] Preliminary generic sortBy implementation on searchRequests --- src/main/java/io/swagger/model/FilterBy.java | 21 ++++-- .../java/io/swagger/model/SearchRequest.java | 20 +++++- .../sort/{SortBy.java => SortByEntry.java} | 11 +++- .../service/SearchQueryBuilder.java | 64 ++++++++++++++++++- .../service/core/TrialService.java | 42 ++---------- 5 files changed, 111 insertions(+), 47 deletions(-) rename src/main/java/io/swagger/model/sort/{SortBy.java => SortByEntry.java} (63%) diff --git a/src/main/java/io/swagger/model/FilterBy.java b/src/main/java/io/swagger/model/FilterBy.java index a34afb7d..6d72b821 100644 --- a/src/main/java/io/swagger/model/FilterBy.java +++ b/src/main/java/io/swagger/model/FilterBy.java @@ -1,17 +1,24 @@ package io.swagger.model; -import io.swagger.model.sort.SortOrder; - public class FilterBy { - private String filterOn; + private String filterColumn; + private String value; private boolean addInfoColumn = false; - public String getFilterOn() { - return filterOn; + public String getFilterColumn() { + return filterColumn; + } + + public void setFilterColumn(String filterColumn) { + this.filterColumn = filterColumn; + } + + public String getValue() { + return value; } - public void setFilterOn(String filterOn) { - this.filterOn = filterOn; + public void setValue(String value) { + this.value = value; } public boolean isAddInfoColumn() { diff --git a/src/main/java/io/swagger/model/SearchRequest.java b/src/main/java/io/swagger/model/SearchRequest.java index ae723e1c..6d999d8a 100644 --- a/src/main/java/io/swagger/model/SearchRequest.java +++ b/src/main/java/io/swagger/model/SearchRequest.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.model.sort.SortBy; +import io.swagger.model.sort.SortByEntry; import java.util.ArrayList; import java.util.List; @@ -30,7 +30,7 @@ public abstract class SearchRequest { protected FilterBy filterBy = null; @JsonProperty("sortBy") - protected List sortBy = null; + protected List sortBy = null; final public SearchRequest page(Integer page) { this.page = page; @@ -126,4 +126,20 @@ public void addExternalReferenceItem(String externalReferenceId, String external } } + + public FilterBy getFilterBy() { + return filterBy; + } + + public void setFilterBy(FilterBy filterBy) { + this.filterBy = filterBy; + } + + public List getSortByEntry() { + return sortBy; + } + + public void setSortByEntry(List sortBy) { + this.sortBy = sortBy; + } } diff --git a/src/main/java/io/swagger/model/sort/SortBy.java b/src/main/java/io/swagger/model/sort/SortByEntry.java similarity index 63% rename from src/main/java/io/swagger/model/sort/SortBy.java rename to src/main/java/io/swagger/model/sort/SortByEntry.java index 8501bd88..3d7fee7f 100644 --- a/src/main/java/io/swagger/model/sort/SortBy.java +++ b/src/main/java/io/swagger/model/sort/SortByEntry.java @@ -1,10 +1,19 @@ package io.swagger.model.sort; -public class SortBy { +// TODO: Replace io.swagger.model.core.SortBy with this class and rename this class to SortBy +public class SortByEntry { private String sortedOn; private SortOrder sortOrder = SortOrder.ASC; private boolean addInfoColumn = false; + public SortByEntry(String sortedOn, + SortOrder sortOrder, + boolean addInfoColumn) { + this.sortedOn = sortedOn; + this.sortOrder = sortOrder; + this.addInfoColumn = addInfoColumn; + } + public String getSortedOn() { return sortedOn; } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java index 74ef4677..67ab3378 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java @@ -6,7 +6,9 @@ import java.time.LocalDate; import java.time.OffsetDateTime; +import io.swagger.model.FilterBy; import io.swagger.model.GeoJSONSearchArea; +import io.swagger.model.sort.SortByEntry; import io.swagger.model.sort.SortOrder; public class SearchQueryBuilder { @@ -311,6 +313,8 @@ private String paramFilter(String param) { return param.replace('.', '_').replace('*', '_'); } + @Deprecated + // Use withSortBy instead public SearchQueryBuilder withSort(String sortByStr, SortOrder sortOrder) { String sortOrderStr = "ASC"; if (sortOrder != null) { @@ -321,4 +325,62 @@ public SearchQueryBuilder withSort(String sortByStr, SortOrder sortOrder) { return this; } -} + + /** + * Takes a list of SortBy options that should typically come in a searchRequest. + * Applies the entries in the list to sort the SearchQuery. + * + * A SortBy has + * - A column name + * - An order (DESC, ASC) + * - A boolean denoting whether the column to be sorted is data stored in additional info + */ + public SearchQueryBuilder sortBy(List sortBy) { + + if (sortBy == null || sortBy.isEmpty()) { + return this; + } + + for (SortByEntry sort : sortBy) { + if (sortBy.getFirst().equals(sort)) { + this.sortClause += " ORDER BY "; + buildSort(sort); + } + + this.sortClause += ", "; + buildSort(sort); + } + + return this; + } + + private void buildSort(SortByEntry sort) { + if (sort.isAddInfoColumn()) { + // TODO: This assumes the jsonb value of the key is always text. Might need to support numerical sort. + this.sortClause += "additional_info ->>" + sort.getSortedOn() + " " + sort.getSortOrder() + " "; + } else { + this.sortClause += sort.getSortedOn() + " " + sort.getSortOrder() + " "; + } + } + + /** + * Takes a list of FilterBy options that should typically come in a searchRequest. + * Applies the entries in the list to filter the SearchQuery. + * + * A FilterBy has + * - A column name + * - A boolean denoting whether the column to be sorted is data stored in additional info + */ + public SearchQueryBuilder filterBy(List filterBy) { + for (FilterBy filter : filterBy) { + if (filter.isAddInfoColumn()) { + // TODO: This assumes the jsonb value of the key is always text. Might need to support numerical sort. + this.whereClause += " AND additional_info ->> " + filter.getFilterColumn() + " = " + filter.getValue() + " "; + } + else { + this.whereClause += " AND additional_info ->> " + filter.getFilterColumn() + " " + filter.getValue() + " "; + } + } + + return this; + }} diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java index 36ac0b0a..a9ee35cc 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java @@ -4,6 +4,7 @@ import java.util.stream.Collectors; import io.swagger.model.core.*; +import io.swagger.model.sort.SortByEntry; import io.swagger.model.sort.SortOrder; import jakarta.validation.Valid; @@ -95,11 +96,10 @@ public List findTrials(@Valid String commonCropName, @Valid String contac request.setSearchDateRangeStart(searchDateRangeStart); if (searchDateRangeEnd != null) request.setSearchDateRangeEnd(searchDateRangeEnd); - if (sortBy != null && SortBy.fromValue(sortBy) != null) - request.setSortBy(SortBy.fromValue(sortBy)); - if (sortOrder != null && SortOrder.fromValue(sortOrder) != null) - request.setSortOrder(SortOrder.fromValue(sortOrder)); - + if (sortBy != null) { + SortByEntry querySortBy = new SortByEntry(sortBy, SortOrder.valueOf(sortOrder), false); + request.setSortByEntry(List.of()); + } request.addExternalReferenceItem(externalReferenceId, externalReferenceID, externalReferenceSource); return findTrials(request, metadata); } @@ -127,7 +127,7 @@ public List findTrials(@Valid TrialSearchRequest request, Metadata metada .appendList(request.getStudyNames(), "*study.studyName").appendList(request.getTrialDbIds(), "id") .appendList(request.getTrialNames(), "trialName") .appendDateRange(request.getSearchDateRangeStart(), request.getSearchDateRangeEnd(), "startDate") - .withSort(getSortByField(request.getSortBy()), request.getSortOrder()); + .sortBy(request.getSortByEntry()); Page trialsPage = trialRepository.findAllBySearchAndPaginate(searchQuery, pageReq); PagingUtility.calculateMetaData(metadata, trialsPage); @@ -357,34 +357,4 @@ private PublicationEntity convertToEntity(TrialNewRequestPublications pub) { return entity; } - - private String getSortByField(SortBy sortBy) { - String sortByStr = "id"; - if (sortBy != null) { - switch (sortBy) { - case STARTDATE: - sortByStr = "startDate"; - break; - case ENDDATE: - sortByStr = "endDate"; - break; - case TRIALNAME: - sortByStr = "trialName"; - break; - case PROGRAMDBID: - sortByStr = "program.id"; - break; - case PROGRAMNAME: - sortByStr = "program.name"; - break; - case TRIALDBID: - default: - sortByStr = "id"; - break; - } - } - - return sortByStr; - } - } From 32664a2162b3757df800c9fd50ad7d8fb02b3ac8 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Thu, 21 May 2026 16:06:21 -0400 Subject: [PATCH 3/7] Use @Formula to derive needed addInfo entity attrs, update SearchQueryBuilder --- src/main/java/io/swagger/model/FilterBy.java | 9 ----- .../java/io/swagger/model/SearchRequest.java | 8 ++-- .../{SortByEntry.java => SortByElement.java} | 20 +++------- .../model/entity/core/TrialEntity.java | 8 ++++ .../service/SearchQueryBuilder.java | 37 ++++++++----------- .../service/core/TrialService.java | 6 +-- 6 files changed, 36 insertions(+), 52 deletions(-) rename src/main/java/io/swagger/model/sort/{SortByEntry.java => SortByElement.java} (58%) diff --git a/src/main/java/io/swagger/model/FilterBy.java b/src/main/java/io/swagger/model/FilterBy.java index 6d72b821..001ea05b 100644 --- a/src/main/java/io/swagger/model/FilterBy.java +++ b/src/main/java/io/swagger/model/FilterBy.java @@ -3,7 +3,6 @@ public class FilterBy { private String filterColumn; private String value; - private boolean addInfoColumn = false; public String getFilterColumn() { return filterColumn; @@ -21,12 +20,4 @@ public void setValue(String value) { this.value = value; } - public boolean isAddInfoColumn() { - return addInfoColumn; - } - - public void setAddInfoColumn(boolean addInfoColumn) { - this.addInfoColumn = addInfoColumn; - } - } diff --git a/src/main/java/io/swagger/model/SearchRequest.java b/src/main/java/io/swagger/model/SearchRequest.java index 6d999d8a..9d9695c8 100644 --- a/src/main/java/io/swagger/model/SearchRequest.java +++ b/src/main/java/io/swagger/model/SearchRequest.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.model.sort.SortByEntry; +import io.swagger.model.sort.SortByElement; import java.util.ArrayList; import java.util.List; @@ -30,7 +30,7 @@ public abstract class SearchRequest { protected FilterBy filterBy = null; @JsonProperty("sortBy") - protected List sortBy = null; + protected List sortBy = null; final public SearchRequest page(Integer page) { this.page = page; @@ -135,11 +135,11 @@ public void setFilterBy(FilterBy filterBy) { this.filterBy = filterBy; } - public List getSortByEntry() { + public List getSortByEntry() { return sortBy; } - public void setSortByEntry(List sortBy) { + public void setSortByEntry(List sortBy) { this.sortBy = sortBy; } } diff --git a/src/main/java/io/swagger/model/sort/SortByEntry.java b/src/main/java/io/swagger/model/sort/SortByElement.java similarity index 58% rename from src/main/java/io/swagger/model/sort/SortByEntry.java rename to src/main/java/io/swagger/model/sort/SortByElement.java index 3d7fee7f..cbc79600 100644 --- a/src/main/java/io/swagger/model/sort/SortByEntry.java +++ b/src/main/java/io/swagger/model/sort/SortByElement.java @@ -1,19 +1,19 @@ package io.swagger.model.sort; // TODO: Replace io.swagger.model.core.SortBy with this class and rename this class to SortBy -public class SortByEntry { +public class SortByElement { private String sortedOn; private SortOrder sortOrder = SortOrder.ASC; - private boolean addInfoColumn = false; - public SortByEntry(String sortedOn, - SortOrder sortOrder, - boolean addInfoColumn) { + public SortByElement(String sortedOn, + SortOrder sortOrder, + boolean addInfoColumn) { this.sortedOn = sortedOn; this.sortOrder = sortOrder; - this.addInfoColumn = addInfoColumn; } + public SortByElement() {} + public String getSortedOn() { return sortedOn; } @@ -29,12 +29,4 @@ public SortOrder getSortOrder() { public void setSortOrder(SortOrder sortOrder) { this.sortOrder = sortOrder; } - - public boolean isAddInfoColumn() { - return addInfoColumn; - } - - public void setAddInfoColumn(boolean addInfoColumn) { - this.addInfoColumn = addInfoColumn; - } } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/model/entity/core/TrialEntity.java b/src/main/java/org/brapi/test/BrAPITestServer/model/entity/core/TrialEntity.java index 1c9be57a..f18543fc 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/model/entity/core/TrialEntity.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/model/entity/core/TrialEntity.java @@ -4,8 +4,10 @@ import org.brapi.test.BrAPITestServer.model.entity.BrAPIPrimaryEntity; import org.brapi.test.BrAPITestServer.model.entity.pheno.ObservationEntity; import org.brapi.test.BrAPITestServer.model.entity.pheno.ObservationUnitEntity; +import org.hibernate.annotations.Formula; import org.hibernate.annotations.Where; +import java.time.OffsetDateTime; import java.util.Date; import java.util.List; @Entity @@ -38,6 +40,12 @@ public class TrialEntity extends BrAPIPrimaryEntity { @Column(name = "soft_deleted") private boolean softDeleted; + @Formula("(to_timestamp(additional_info #>> '{createdDate}', 'YYYY-MM-DD'))") + private OffsetDateTime createdDate; + + @Formula("(additional_info #>> '{createdBy,userName}')") + private String createdBy; + @ManyToOne(fetch = FetchType.LAZY) private CropEntity crop; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java index 67ab3378..41a423ec 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java @@ -8,7 +8,7 @@ import io.swagger.model.FilterBy; import io.swagger.model.GeoJSONSearchArea; -import io.swagger.model.sort.SortByEntry; +import io.swagger.model.sort.SortByElement; import io.swagger.model.sort.SortOrder; public class SearchQueryBuilder { @@ -22,8 +22,8 @@ public class SearchQueryBuilder { private Class clazz; public SearchQueryBuilder(Class clazz) { - this.selectClause = "SELECT distinct entity FROM " + clazz.getSimpleName() + " entity "; - this.selectOnlyIds = "SELECT distinct entity.id FROM " + clazz.getSimpleName() + " entity "; + this.selectClause = "SELECT entity FROM " + clazz.getSimpleName() + " entity "; + this.selectOnlyIds = "SELECT entity.id FROM " + clazz.getSimpleName() + " entity "; this.whereClause = "WHERE 1=1 "; this.defaultSort = " ORDER BY entity.id ASC "; this.sortClause = ""; @@ -307,6 +307,10 @@ private String entityPrefix(String field) { } } + private String addInfoPrefix(String field) { + return "function('jsonb_extract_path_text', entity.additionalInfo, '" + field + "' ) "; + } + private String paramFilter(String param) { if (param == null) return ""; @@ -335,32 +339,27 @@ public SearchQueryBuilder withSort(String sortByStr, SortOrder sortOrder) { * - An order (DESC, ASC) * - A boolean denoting whether the column to be sorted is data stored in additional info */ - public SearchQueryBuilder sortBy(List sortBy) { + public SearchQueryBuilder sortBy(List sortBy) { if (sortBy == null || sortBy.isEmpty()) { return this; } - for (SortByEntry sort : sortBy) { + for (SortByElement sort : sortBy) { if (sortBy.getFirst().equals(sort)) { this.sortClause += " ORDER BY "; buildSort(sort); + } else { + this.sortClause += ", "; + buildSort(sort); } - - this.sortClause += ", "; - buildSort(sort); } return this; } - private void buildSort(SortByEntry sort) { - if (sort.isAddInfoColumn()) { - // TODO: This assumes the jsonb value of the key is always text. Might need to support numerical sort. - this.sortClause += "additional_info ->>" + sort.getSortedOn() + " " + sort.getSortOrder() + " "; - } else { - this.sortClause += sort.getSortedOn() + " " + sort.getSortOrder() + " "; - } + private void buildSort(SortByElement sort) { + this.sortClause += entityPrefix(sort.getSortedOn()) + " " + sort.getSortOrder() + " "; } /** @@ -373,13 +372,7 @@ private void buildSort(SortByEntry sort) { */ public SearchQueryBuilder filterBy(List filterBy) { for (FilterBy filter : filterBy) { - if (filter.isAddInfoColumn()) { - // TODO: This assumes the jsonb value of the key is always text. Might need to support numerical sort. - this.whereClause += " AND additional_info ->> " + filter.getFilterColumn() + " = " + filter.getValue() + " "; - } - else { - this.whereClause += " AND additional_info ->> " + filter.getFilterColumn() + " " + filter.getValue() + " "; - } + this.whereClause += " AND " + entityPrefix(filter.getFilterColumn()) + " LIKE '%" + filter.getValue() + "%' "; } return this; diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java index a9ee35cc..6217f755 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java @@ -4,7 +4,7 @@ import java.util.stream.Collectors; import io.swagger.model.core.*; -import io.swagger.model.sort.SortByEntry; +import io.swagger.model.sort.SortByElement; import io.swagger.model.sort.SortOrder; import jakarta.validation.Valid; @@ -97,8 +97,8 @@ public List findTrials(@Valid String commonCropName, @Valid String contac if (searchDateRangeEnd != null) request.setSearchDateRangeEnd(searchDateRangeEnd); if (sortBy != null) { - SortByEntry querySortBy = new SortByEntry(sortBy, SortOrder.valueOf(sortOrder), false); - request.setSortByEntry(List.of()); + SortByElement querySortBy = new SortByElement(sortBy, SortOrder.valueOf(sortOrder), false); + request.setSortByEntry(List.of(querySortBy)); } request.addExternalReferenceItem(externalReferenceId, externalReferenceID, externalReferenceSource); return findTrials(request, metadata); From 2d5128502765c616c7328dcf7331bc859a2dbac9 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Thu, 21 May 2026 19:22:15 -0400 Subject: [PATCH 4/7] Preliminary pass at allowed fields mapping --- .../java/io/swagger/model/SearchRequest.java | 62 +++++++++++++++++-- .../model/core/TrialSearchRequest.java | 20 +++++- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/swagger/model/SearchRequest.java b/src/main/java/io/swagger/model/SearchRequest.java index 9d9695c8..cb29ee2c 100644 --- a/src/main/java/io/swagger/model/SearchRequest.java +++ b/src/main/java/io/swagger/model/SearchRequest.java @@ -3,9 +3,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.model.sort.SortByElement; +import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; +import org.springframework.http.HttpStatus; import java.util.ArrayList; import java.util.List; +import java.util.Map; public abstract class SearchRequest { @JsonIgnore @@ -27,11 +30,19 @@ public abstract class SearchRequest { protected List externalReferenceSources = null; @JsonProperty("filterBy") - protected FilterBy filterBy = null; + protected List filterBy = null; @JsonProperty("sortBy") protected List sortBy = null; + @JsonIgnore + protected Map sortFilterEntityColumnNamesByRequestName = null; + + @JsonIgnore + public List getExternalReferenceIds() { + return externalReferenceIds; + } + final public SearchRequest page(Integer page) { this.page = page; return this; @@ -127,11 +138,30 @@ public void addExternalReferenceItem(String externalReferenceId, String external } - public FilterBy getFilterBy() { + public List getFilterBy() { return filterBy; } - public void setFilterBy(FilterBy filterBy) { + public void setFilterBy(List filterBy) throws BrAPIServerException { + + if (filterBy == null || filterBy.isEmpty()) { + return; + } + + Map allowedSortFilterNames = getSortFilterEntityColumnNamesByRequestName(); + + for (FilterBy filterByItem : filterBy) { + String filterColumnEntityName = getSortFilterEntityColumnNamesByRequestName().get(filterByItem.getFilterColumn()); + + if (filterColumnEntityName == null) { + throw new BrAPIServerException(HttpStatus.BAD_REQUEST, + String.format("Supplied filterColumn [%s] not available in allowed names [%s]", filterByItem.getFilterColumn(), allowedSortFilterNames.keySet()) + ); + } else { + // Remap suppliedFilterColumn to actual entity name supplied by mapper + filterByItem.setFilterColumn(filterColumnEntityName); + } + } this.filterBy = filterBy; } @@ -139,7 +169,31 @@ public List getSortByEntry() { return sortBy; } - public void setSortByEntry(List sortBy) { + public void setSortByEntry(List sortBy) throws BrAPIServerException { + + if (sortBy == null || sortBy.isEmpty()) { + return; + } + + Map allowedSortFilterNames = getSortFilterEntityColumnNamesByRequestName(); + + for (SortByElement sortByItem : sortBy) { + String filterColumnEntityName = getSortFilterEntityColumnNamesByRequestName().get(sortByItem.getSortedOn()); + + if (filterColumnEntityName == null) { + throw new BrAPIServerException(HttpStatus.BAD_REQUEST, + String.format("Supplied sortColumn [%s] not available in allowed names [%s]", sortByItem.getSortedOn(), allowedSortFilterNames.keySet()) + ); + } else { + // Remap suppliedFilterColumn to actual entity name supplied by mapper + sortByItem.setSortedOn(filterColumnEntityName); + } + } + this.sortBy = sortBy; } + + public Map getSortFilterEntityColumnNamesByRequestName() { + throw new UnsupportedOperationException(String.format("Sort/Filtering not implemented for %s", this.getClass().getSimpleName())); + } } diff --git a/src/main/java/io/swagger/model/core/TrialSearchRequest.java b/src/main/java/io/swagger/model/core/TrialSearchRequest.java index 5a4b741f..324774fa 100644 --- a/src/main/java/io/swagger/model/core/TrialSearchRequest.java +++ b/src/main/java/io/swagger/model/core/TrialSearchRequest.java @@ -1,14 +1,23 @@ package io.swagger.model.core; -import java.util.Objects; +import java.util.*; + import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.model.SearchRequest; -import java.util.ArrayList; -import java.util.List; import java.time.LocalDate; public class TrialSearchRequest extends SearchRequest { + + // Key - allowed sort or field filter name for this entity + // Value = entity field name that represents the submitted field. Used later on in query building. + private static final Map ALLOWED_SORT_AND_FILTER_FIELDS = + Map.of( + "trialName", "trialName", + "createdDate", "createdDate", + "createdBy", "createdBy" + ); + @JsonProperty("commonCropNames") private List commonCropNames = null; @@ -424,4 +433,9 @@ public Integer getTotalParameterCount() { count += this.trialPUIs.size(); return count; } + + @Override + public Map getSortFilterEntityColumnNamesByRequestName() { + return ALLOWED_SORT_AND_FILTER_FIELDS; + } } From f9440f06ea342f59e25b9fdb8ff8846eb8acb434 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Fri, 22 May 2026 15:42:16 -0400 Subject: [PATCH 5/7] Add parametrization logic for filter values, infrastructure for 400 errors on serialization --- src/main/java/io/swagger/model/FilterBy.java | 11 +++--- .../java/io/swagger/model/SearchRequest.java | 32 ++++++++++++---- .../BrapiExceptionHandler.java | 37 +++++++++++++++++-- .../model/entity/core/TrialEntity.java | 4 +- .../service/SearchQueryBuilder.java | 28 +++++++++++--- .../service/core/TrialService.java | 6 ++- 6 files changed, 92 insertions(+), 26 deletions(-) diff --git a/src/main/java/io/swagger/model/FilterBy.java b/src/main/java/io/swagger/model/FilterBy.java index 001ea05b..acbc03e7 100644 --- a/src/main/java/io/swagger/model/FilterBy.java +++ b/src/main/java/io/swagger/model/FilterBy.java @@ -1,15 +1,16 @@ package io.swagger.model; public class FilterBy { - private String filterColumn; + + private String filterOn; private String value; - public String getFilterColumn() { - return filterColumn; + public String getFilterOn() { + return filterOn; } - public void setFilterColumn(String filterColumn) { - this.filterColumn = filterColumn; + public void setFilterOn(String filterOn) { + this.filterOn = filterOn; } public String getValue() { diff --git a/src/main/java/io/swagger/model/SearchRequest.java b/src/main/java/io/swagger/model/SearchRequest.java index cb29ee2c..cefd14bd 100644 --- a/src/main/java/io/swagger/model/SearchRequest.java +++ b/src/main/java/io/swagger/model/SearchRequest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; import io.swagger.model.sort.SortByElement; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; import org.springframework.http.HttpStatus; @@ -142,6 +143,7 @@ public List getFilterBy() { return filterBy; } + @JsonSetter("filterBy") public void setFilterBy(List filterBy) throws BrAPIServerException { if (filterBy == null || filterBy.isEmpty()) { @@ -151,25 +153,35 @@ public void setFilterBy(List filterBy) throws BrAPIServerException { Map allowedSortFilterNames = getSortFilterEntityColumnNamesByRequestName(); for (FilterBy filterByItem : filterBy) { - String filterColumnEntityName = getSortFilterEntityColumnNamesByRequestName().get(filterByItem.getFilterColumn()); + + if (filterByItem.getFilterOn() == null || filterByItem.getFilterOn().isEmpty()) { + throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "filterOn attribute not provided in element of filterBy list."); + } + + if (filterByItem.getValue() == null || filterByItem.getValue().isEmpty()) { + throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "value attribute not provided in element of filterBy list."); + } + + String filterColumnEntityName = getSortFilterEntityColumnNamesByRequestName().get(filterByItem.getFilterOn()); if (filterColumnEntityName == null) { throw new BrAPIServerException(HttpStatus.BAD_REQUEST, - String.format("Supplied filterColumn [%s] not available in allowed names [%s]", filterByItem.getFilterColumn(), allowedSortFilterNames.keySet()) + String.format("Supplied filterColumn [%s] not available in allowed names [%s]", filterByItem.getFilterOn(), allowedSortFilterNames.keySet()) ); } else { // Remap suppliedFilterColumn to actual entity name supplied by mapper - filterByItem.setFilterColumn(filterColumnEntityName); + filterByItem.setFilterOn(filterColumnEntityName); } } this.filterBy = filterBy; } - public List getSortByEntry() { + public List getSortByElements() { return sortBy; } - public void setSortByEntry(List sortBy) throws BrAPIServerException { + @JsonSetter("sortBy") + public void setSortByElements(List sortBy) throws BrAPIServerException { if (sortBy == null || sortBy.isEmpty()) { return; @@ -178,15 +190,19 @@ public void setSortByEntry(List sortBy) throws BrAPIServerExcepti Map allowedSortFilterNames = getSortFilterEntityColumnNamesByRequestName(); for (SortByElement sortByItem : sortBy) { - String filterColumnEntityName = getSortFilterEntityColumnNamesByRequestName().get(sortByItem.getSortedOn()); + if (sortByItem.getSortedOn() == null || sortByItem.getSortedOn().isEmpty()) { + throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "sortedOn attribute not provided in element of sortBy list"); + } - if (filterColumnEntityName == null) { + String sortColumnEntityName = getSortFilterEntityColumnNamesByRequestName().get(sortByItem.getSortedOn()); + + if (sortColumnEntityName == null) { throw new BrAPIServerException(HttpStatus.BAD_REQUEST, String.format("Supplied sortColumn [%s] not available in allowed names [%s]", sortByItem.getSortedOn(), allowedSortFilterNames.keySet()) ); } else { // Remap suppliedFilterColumn to actual entity name supplied by mapper - sortByItem.setSortedOn(filterColumnEntityName); + sortByItem.setSortedOn(sortColumnEntityName); } } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/BrapiExceptionHandler.java b/src/main/java/org/brapi/test/BrAPITestServer/BrapiExceptionHandler.java index 7ee15a60..fa6508f0 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/BrapiExceptionHandler.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/BrapiExceptionHandler.java @@ -1,13 +1,12 @@ package org.brapi.test.BrAPITestServer; import java.util.ArrayList; +import java.util.Map; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.lang.Nullable; import org.springframework.web.bind.MissingServletRequestParameterException; @@ -70,4 +69,36 @@ private ResponseEntity buildErrorResponse(HttpStatus code, String messag return new ResponseEntity(apiError, code); } + + // This override handles JSON parsing failures detected by Jackson. + // It allows for capture of BrAPI generated BAD Request exceptions that are occur during serialization, + // like those involved in filtering and sorting in search requests. + @Override + protected ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, + HttpHeaders headers, + HttpStatusCode status, + WebRequest request + ) { + Throwable root = ex.getMostSpecificCause(); + + if (root instanceof BrAPIServerException brapiServerException) { + + ProblemDetail detail = + ProblemDetail.forStatus(HttpStatus.BAD_REQUEST); + + detail.setTitle("Bad Request"); + detail.setDetail(brapiServerException.getResponseMessage()); + + return ResponseEntity.badRequest().body(detail); + } + + // Delegate back to Spring default behavior + return super.handleHttpMessageNotReadable( + ex, + headers, + status, + request + ); + } } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/model/entity/core/TrialEntity.java b/src/main/java/org/brapi/test/BrAPITestServer/model/entity/core/TrialEntity.java index f18543fc..5f9bb989 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/model/entity/core/TrialEntity.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/model/entity/core/TrialEntity.java @@ -40,8 +40,8 @@ public class TrialEntity extends BrAPIPrimaryEntity { @Column(name = "soft_deleted") private boolean softDeleted; - @Formula("(to_timestamp(additional_info #>> '{createdDate}', 'YYYY-MM-DD'))") - private OffsetDateTime createdDate; + @Formula("(additional_info #>> '{createdDate}')") + private String createdDate; @Formula("(additional_info #>> '{createdBy,userName}')") private String createdBy; diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java index 41a423ec..86f4f37e 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java @@ -10,6 +10,8 @@ import io.swagger.model.GeoJSONSearchArea; import io.swagger.model.sort.SortByElement; import io.swagger.model.sort.SortOrder; +import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; +import org.springframework.http.HttpStatus; public class SearchQueryBuilder { @@ -135,6 +137,16 @@ public SearchQueryBuilder appendSingle(UUID single, String columnName) { return this; } + public SearchQueryBuilder appendLike(String like, String columnName) { + String paramName = paramFilter(columnName); + + if (like != null) { + this.whereClause += "AND " + entityPrefix(columnName) + " LIKE :" + paramName + " "; + this.params.put(paramName, "%" + like + "%"); + } + return this; + } + public > SearchQueryBuilder appendEnum(E enumVal, String columnName) { String paramName = paramFilter(columnName); if (enumVal != null) { @@ -337,9 +349,8 @@ public SearchQueryBuilder withSort(String sortByStr, SortOrder sortOrder) { * A SortBy has * - A column name * - An order (DESC, ASC) - * - A boolean denoting whether the column to be sorted is data stored in additional info */ - public SearchQueryBuilder sortBy(List sortBy) { + public SearchQueryBuilder sortBy(List sortBy) throws BrAPIServerException { if (sortBy == null || sortBy.isEmpty()) { return this; @@ -368,12 +379,17 @@ private void buildSort(SortByElement sort) { * * A FilterBy has * - A column name - * - A boolean denoting whether the column to be sorted is data stored in additional info */ - public SearchQueryBuilder filterBy(List filterBy) { + public SearchQueryBuilder filterBy(List filterBy) throws BrAPIServerException { + SearchQueryBuilder searchQuery = this; + + if (filterBy == null || filterBy.isEmpty()) { + return searchQuery; + } + for (FilterBy filter : filterBy) { - this.whereClause += " AND " + entityPrefix(filter.getFilterColumn()) + " LIKE '%" + filter.getValue() + "%' "; + searchQuery = appendLike(filter.getValue(), filter.getFilterOn()); } - return this; + return searchQuery; }} diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java index 6217f755..c01622d4 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java @@ -97,8 +97,9 @@ public List findTrials(@Valid String commonCropName, @Valid String contac if (searchDateRangeEnd != null) request.setSearchDateRangeEnd(searchDateRangeEnd); if (sortBy != null) { + // TODO: Fix this use case by adding allowable sort fields for trials from old enum in here SortByElement querySortBy = new SortByElement(sortBy, SortOrder.valueOf(sortOrder), false); - request.setSortByEntry(List.of(querySortBy)); + request.setSortByElements(List.of(querySortBy)); } request.addExternalReferenceItem(externalReferenceId, externalReferenceID, externalReferenceSource); return findTrials(request, metadata); @@ -127,7 +128,8 @@ public List findTrials(@Valid TrialSearchRequest request, Metadata metada .appendList(request.getStudyNames(), "*study.studyName").appendList(request.getTrialDbIds(), "id") .appendList(request.getTrialNames(), "trialName") .appendDateRange(request.getSearchDateRangeStart(), request.getSearchDateRangeEnd(), "startDate") - .sortBy(request.getSortByEntry()); + .sortBy(request.getSortByElements()) + .filterBy(request.getFilterBy()); Page trialsPage = trialRepository.findAllBySearchAndPaginate(searchQuery, pageReq); PagingUtility.calculateMetaData(metadata, trialsPage); From 90214e06326e2f204b645df7bf45f861e5e92071 Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Fri, 22 May 2026 17:06:44 -0400 Subject: [PATCH 6/7] Use new SortBy class in all use cases. --- .../java/io/swagger/model/SearchRequest.java | 12 ++-- .../java/io/swagger/model/core/SortBy.java | 58 ----------------- .../model/core/StudySearchRequest.java | 63 ++++++++----------- .../model/core/TrialSearchRequest.java | 6 +- .../sort/{SortByElement.java => SortBy.java} | 10 ++- .../service/SearchQueryBuilder.java | 9 ++- .../service/core/StudyService.java | 56 +++-------------- .../service/core/TrialService.java | 7 +-- 8 files changed, 53 insertions(+), 168 deletions(-) delete mode 100644 src/main/java/io/swagger/model/core/SortBy.java rename src/main/java/io/swagger/model/sort/{SortByElement.java => SortBy.java} (63%) diff --git a/src/main/java/io/swagger/model/SearchRequest.java b/src/main/java/io/swagger/model/SearchRequest.java index cefd14bd..828cd69c 100644 --- a/src/main/java/io/swagger/model/SearchRequest.java +++ b/src/main/java/io/swagger/model/SearchRequest.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; -import io.swagger.model.sort.SortByElement; +import io.swagger.model.sort.SortBy; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; import org.springframework.http.HttpStatus; @@ -34,7 +34,7 @@ public abstract class SearchRequest { protected List filterBy = null; @JsonProperty("sortBy") - protected List sortBy = null; + protected List sortBy = null; @JsonIgnore protected Map sortFilterEntityColumnNamesByRequestName = null; @@ -143,7 +143,6 @@ public List getFilterBy() { return filterBy; } - @JsonSetter("filterBy") public void setFilterBy(List filterBy) throws BrAPIServerException { if (filterBy == null || filterBy.isEmpty()) { @@ -176,12 +175,11 @@ public void setFilterBy(List filterBy) throws BrAPIServerException { this.filterBy = filterBy; } - public List getSortByElements() { + public List getSortByElements() { return sortBy; } - @JsonSetter("sortBy") - public void setSortByElements(List sortBy) throws BrAPIServerException { + public void setSortBy(List sortBy) throws BrAPIServerException { if (sortBy == null || sortBy.isEmpty()) { return; @@ -189,7 +187,7 @@ public void setSortByElements(List sortBy) throws BrAPIServerExce Map allowedSortFilterNames = getSortFilterEntityColumnNamesByRequestName(); - for (SortByElement sortByItem : sortBy) { + for (SortBy sortByItem : sortBy) { if (sortByItem.getSortedOn() == null || sortByItem.getSortedOn().isEmpty()) { throw new BrAPIServerException(HttpStatus.BAD_REQUEST, "sortedOn attribute not provided in element of sortBy list"); } diff --git a/src/main/java/io/swagger/model/core/SortBy.java b/src/main/java/io/swagger/model/core/SortBy.java deleted file mode 100644 index bf215cc5..00000000 --- a/src/main/java/io/swagger/model/core/SortBy.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.swagger.model.core; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -@Deprecated -// TODO: Phase out in favor of io.swagger.model.sort objects -public enum SortBy { - - STUDYDBID("studyDbId"), - - STARTDATE("startDate"), - - ENDDATE("endDate"), - - TRIALDBID("trialDbId"), - - TRIALNAME("trialName"), - - PROGRAMDBID("programDbId"), - - LOCATIONDBID("locationDbId"), - - SEASONDBID("seasonDbId"), - - STUDYTYPE("studyType"), - - STUDYNAME("studyName"), - - STUDYLOCATION("studyLocation"), - - PROGRAMNAME("programName"), - - GERMPLASMDBID("germplasmDbId"), - - OBSERVATIONVARIABLEDBID("observationVariableDbId"); -private String value; - - SortBy(String value) { - this.value = value; - } - - @Override - @JsonValue - public String toString() { - return String.valueOf(value); - } - - @JsonCreator - public static SortBy fromValue(String text) { - for (SortBy b : SortBy.values()) { - if (String.valueOf(b.value).equals(text)) { - return b; - } - } - return null; - } -} diff --git a/src/main/java/io/swagger/model/core/StudySearchRequest.java b/src/main/java/io/swagger/model/core/StudySearchRequest.java index 26849475..78ed90d0 100644 --- a/src/main/java/io/swagger/model/core/StudySearchRequest.java +++ b/src/main/java/io/swagger/model/core/StudySearchRequest.java @@ -1,16 +1,34 @@ package io.swagger.model.core; +import java.util.Map; import java.util.Objects; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.model.SearchRequest; -import io.swagger.model.sort.SortOrder; import java.util.ArrayList; import java.util.List; public class StudySearchRequest extends SearchRequest { + + // Key - allowed sort or field filter name for this entity + // Value = entity field name that represents the submitted field. Used later on in query building. + private static final Map ALLOWED_SORT_AND_FILTER_FIELDS = + Map.ofEntries( + Map.entry("germplasmDbId", "*obsunit.germplasm.id"), + Map.entry("locationDbId", "location.id"), + Map.entry("observationVariableDbId", "*observation.observationVariable.id"), + Map.entry("programDbId", "trial.program.id"), + Map.entry("programName", "trial.program.name"), + Map.entry("seasonDbId", "*season.id"), + Map.entry("studyDbId", "id"), + Map.entry("studyLocation", "location.id"), + Map.entry("trialDbId", "trial.id"), + Map.entry("studyType", "studyName"), + Map.entry("studyName", "studyName") + ); + @JsonProperty("commonCropNames") private List commonCropNames = null; @@ -56,12 +74,6 @@ public class StudySearchRequest extends SearchRequest { @JsonProperty("seasonDbIds") private List seasonDbIds = null; - @JsonProperty("sortBy") - private SortBy sortBy = null; - - @JsonProperty("sortOrder") - private SortOrder sortOrder = null; - @JsonProperty("studyCodes") private List studyCodes = null; @@ -378,32 +390,6 @@ public void setSeasonDbIds(List seasonDbIds) { this.seasonDbIds = seasonDbIds; } - public StudySearchRequest sortBy(SortBy sortBy) { - this.sortBy = sortBy; - return this; - } - - public SortBy getSortBy() { - return sortBy; - } - - public void setSortBy(SortBy sortBy) { - this.sortBy = sortBy; - } - - public StudySearchRequest sortOrder(SortOrder sortOrder) { - this.sortOrder = sortOrder; - return this; - } - - public SortOrder getSortOrder() { - return sortOrder; - } - - public void setSortOrder(SortOrder sortOrder) { - this.sortOrder = sortOrder; - } - public StudySearchRequest studyCodes(List studyCodes) { this.studyCodes = studyCodes; return this; @@ -494,7 +480,6 @@ public boolean equals(java.lang.Object o) { && Objects.equals(this.active, studySearchRequest.active) && Objects.equals(this.seasonDbIds, studySearchRequest.seasonDbIds) && Objects.equals(this.sortBy, studySearchRequest.sortBy) - && Objects.equals(this.sortOrder, studySearchRequest.sortOrder) && Objects.equals(this.studyCodes, studySearchRequest.studyCodes) && Objects.equals(this.studyPUIs, studySearchRequest.studyPUIs) && Objects.equals(this.studyTypes, studySearchRequest.studyTypes) && super.equals(o); @@ -505,7 +490,7 @@ public int hashCode() { return Objects.hash(commonCropNames, programDbIds, programNames, trialDbIds, trialNames, studyDbIds, studyNames, locationDbIds, locationNames, germplasmDbIds, germplasmNames, observationVariableDbIds, observationVariableNames, externalReferenceIds, externalReferenceSources, active, seasonDbIds, sortBy, - sortOrder, studyCodes, studyPUIs, studyTypes, super.hashCode()); + studyCodes, studyPUIs, studyTypes, super.hashCode()); } @Override @@ -531,7 +516,6 @@ public String toString() { sb.append(" active: ").append(toIndentedString(active)).append("\n"); sb.append(" seasonDbIds: ").append(toIndentedString(seasonDbIds)).append("\n"); sb.append(" sortBy: ").append(toIndentedString(sortBy)).append("\n"); - sb.append(" sortOrder: ").append(toIndentedString(sortOrder)).append("\n"); sb.append(" studyCodes: ").append(toIndentedString(studyCodes)).append("\n"); sb.append(" studyPUIs: ").append(toIndentedString(studyPUIs)).append("\n"); sb.append(" studyTypes: ").append(toIndentedString(studyTypes)).append("\n"); @@ -585,8 +569,6 @@ public Integer getTotalParameterCount() { count += this.seasonDbIds.size(); if (this.sortBy != null) count += 1; - if (this.sortOrder != null) - count += 1; if (this.studyCodes != null) count += this.studyCodes.size(); if (this.studyPUIs != null) @@ -595,4 +577,9 @@ public Integer getTotalParameterCount() { count += this.studyTypes.size(); return count; } + + @Override + public Map getSortFilterEntityColumnNamesByRequestName() { + return ALLOWED_SORT_AND_FILTER_FIELDS; + } } diff --git a/src/main/java/io/swagger/model/core/TrialSearchRequest.java b/src/main/java/io/swagger/model/core/TrialSearchRequest.java index 324774fa..aaed24eb 100644 --- a/src/main/java/io/swagger/model/core/TrialSearchRequest.java +++ b/src/main/java/io/swagger/model/core/TrialSearchRequest.java @@ -15,7 +15,11 @@ public class TrialSearchRequest extends SearchRequest { Map.of( "trialName", "trialName", "createdDate", "createdDate", - "createdBy", "createdBy" + "createdBy", "createdBy", + "trialDbId", "id", + "programDbId","program.id", + "startDate", "startDate", + "endDate", "endDate" ); @JsonProperty("commonCropNames") diff --git a/src/main/java/io/swagger/model/sort/SortByElement.java b/src/main/java/io/swagger/model/sort/SortBy.java similarity index 63% rename from src/main/java/io/swagger/model/sort/SortByElement.java rename to src/main/java/io/swagger/model/sort/SortBy.java index cbc79600..bb286fa6 100644 --- a/src/main/java/io/swagger/model/sort/SortByElement.java +++ b/src/main/java/io/swagger/model/sort/SortBy.java @@ -1,18 +1,16 @@ package io.swagger.model.sort; -// TODO: Replace io.swagger.model.core.SortBy with this class and rename this class to SortBy -public class SortByElement { +public class SortBy { private String sortedOn; private SortOrder sortOrder = SortOrder.ASC; - public SortByElement(String sortedOn, - SortOrder sortOrder, - boolean addInfoColumn) { + public SortBy(String sortedOn, + SortOrder sortOrder) { this.sortedOn = sortedOn; this.sortOrder = sortOrder; } - public SortByElement() {} + public SortBy() {} public String getSortedOn() { return sortedOn; diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java index 86f4f37e..4961ed14 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java @@ -8,10 +8,9 @@ import io.swagger.model.FilterBy; import io.swagger.model.GeoJSONSearchArea; -import io.swagger.model.sort.SortByElement; +import io.swagger.model.sort.SortBy; import io.swagger.model.sort.SortOrder; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; -import org.springframework.http.HttpStatus; public class SearchQueryBuilder { @@ -350,13 +349,13 @@ public SearchQueryBuilder withSort(String sortByStr, SortOrder sortOrder) { * - A column name * - An order (DESC, ASC) */ - public SearchQueryBuilder sortBy(List sortBy) throws BrAPIServerException { + public SearchQueryBuilder sortBy(List sortBy) throws BrAPIServerException { if (sortBy == null || sortBy.isEmpty()) { return this; } - for (SortByElement sort : sortBy) { + for (SortBy sort : sortBy) { if (sortBy.getFirst().equals(sort)) { this.sortClause += " ORDER BY "; buildSort(sort); @@ -369,7 +368,7 @@ public SearchQueryBuilder sortBy(List sortBy) throws BrAPIServ return this; } - private void buildSort(SortByElement sort) { + private void buildSort(SortBy sort) { this.sortClause += entityPrefix(sort.getSortedOn()) + " " + sort.getSortOrder() + " "; } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/core/StudyService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/core/StudyService.java index 4c0cb198..e5611ab7 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/core/StudyService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/core/StudyService.java @@ -3,6 +3,7 @@ import java.util.*; import java.util.stream.Collectors; +import io.swagger.model.sort.SortBy; import org.apache.commons.lang3.StringUtils; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerDbIdNotFoundException; import org.brapi.test.BrAPITestServer.exceptions.BrAPIServerException; @@ -37,7 +38,6 @@ import io.swagger.model.Metadata; import io.swagger.model.core.Contact; import io.swagger.model.core.EnvironmentParameter; -import io.swagger.model.core.SortBy; import io.swagger.model.sort.SortOrder; import io.swagger.model.core.Study; import io.swagger.model.core.StudyExperimentalDesign; @@ -107,10 +107,11 @@ public List findStudies(String commonCropName, String studyType, String p request.addObservationVariableDbIdsItem(observationVariableDbId); if (active != null) request.setActive(active); - if (sortBy != null && SortBy.fromValue(sortBy) != null) - request.setSortBy(SortBy.fromValue(sortBy)); - if (sortOrder != null && SortOrder.fromValue(sortOrder) != null) - request.setSortOrder(SortOrder.fromValue(sortOrder)); + if (sortBy != null) { + SortBy sortByElement = new SortBy(sortBy, SortOrder.valueOf(sortOrder)); + + request.setSortBy(List.of(sortByElement)); + } request.addExternalReferenceItem(externalReferenceId, externalReferenceID, externalReferenceSource); @@ -158,7 +159,7 @@ public List findStudies(StudySearchRequest request, Metadata metaData) .appendList(request.getStudyDbIds(), "id").appendList(request.getStudyNames(), "studyName") .appendList(request.getStudyPUIs(), "studyPUI").appendList(request.getStudyTypes(), "studyType") .appendList(request.getTrialDbIds(), "trial.id").appendList(request.getTrialNames(), "trial.trialName") - .withSort(getSortByField(request.getSortBy()), request.getSortOrder()); + .sortBy(request.getSortByElements()); Page studiesPage = studyRepository.findAllBySearchAndPaginate(searchQuery, pageReq); PagingUtility.calculateMetaData(metaData, studiesPage); @@ -565,47 +566,4 @@ private EnvironmentParametersEntity convertToEntity(EnvironmentParameter param) return entity; } - private String getSortByField(SortBy sortBy) { - String sortByStr = "id"; - if (sortBy != null) { - switch (sortBy) { - case GERMPLASMDBID: - sortByStr = "*obsunit.germplasm.id"; - break; - case LOCATIONDBID: - sortByStr = "location.id"; - break; - case OBSERVATIONVARIABLEDBID: - sortByStr = "*observation.observationVariable.id"; - break; - case PROGRAMDBID: - sortByStr = "trial.program.id"; - break; - case PROGRAMNAME: - sortByStr = "trial.program.name"; - break; - case SEASONDBID: - sortByStr = "*season.id"; - break; - case STUDYDBID: - sortByStr = "id"; - break; - case STUDYLOCATION: - sortByStr = "location.id"; - break; - case TRIALDBID: - sortByStr = "trial.id "; - break; - case STUDYTYPE: - sortByStr = "studyName"; - break; - case STUDYNAME: - default: - sortByStr = "studyName"; - break; - } - } - return sortByStr; - } - } diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java index c01622d4..21146a30 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/core/TrialService.java @@ -4,7 +4,7 @@ import java.util.stream.Collectors; import io.swagger.model.core.*; -import io.swagger.model.sort.SortByElement; +import io.swagger.model.sort.SortBy; import io.swagger.model.sort.SortOrder; import jakarta.validation.Valid; @@ -97,9 +97,8 @@ public List findTrials(@Valid String commonCropName, @Valid String contac if (searchDateRangeEnd != null) request.setSearchDateRangeEnd(searchDateRangeEnd); if (sortBy != null) { - // TODO: Fix this use case by adding allowable sort fields for trials from old enum in here - SortByElement querySortBy = new SortByElement(sortBy, SortOrder.valueOf(sortOrder), false); - request.setSortByElements(List.of(querySortBy)); + SortBy querySortBy = new SortBy(sortBy, SortOrder.valueOf(sortOrder)); + request.setSortBy(List.of(querySortBy)); } request.addExternalReferenceItem(externalReferenceId, externalReferenceID, externalReferenceSource); return findTrials(request, metadata); From 90e77dd854c47f07cfe002811b23e1ab8645e13c Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Fri, 29 May 2026 15:50:12 -0400 Subject: [PATCH 7/7] Make filter case insensitive Additionally add active as a filter column --- .../swagger/model/core/TrialSearchRequest.java | 3 ++- .../service/SearchQueryBuilder.java | 17 ++--------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/main/java/io/swagger/model/core/TrialSearchRequest.java b/src/main/java/io/swagger/model/core/TrialSearchRequest.java index aaed24eb..ce24b1f7 100644 --- a/src/main/java/io/swagger/model/core/TrialSearchRequest.java +++ b/src/main/java/io/swagger/model/core/TrialSearchRequest.java @@ -19,7 +19,8 @@ public class TrialSearchRequest extends SearchRequest { "trialDbId", "id", "programDbId","program.id", "startDate", "startDate", - "endDate", "endDate" + "endDate", "endDate", + "active", "active" ); @JsonProperty("commonCropNames") diff --git a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java index 4961ed14..61ca21f8 100644 --- a/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java +++ b/src/main/java/org/brapi/test/BrAPITestServer/service/SearchQueryBuilder.java @@ -140,7 +140,7 @@ public SearchQueryBuilder appendLike(String like, String columnName) { String paramName = paramFilter(columnName); if (like != null) { - this.whereClause += "AND " + entityPrefix(columnName) + " LIKE :" + paramName + " "; + this.whereClause += "AND lower(" + entityPrefix(columnName) + ") LIKE :" + paramName + " "; this.params.put(paramName, "%" + like + "%"); } return this; @@ -328,19 +328,6 @@ private String paramFilter(String param) { return param.replace('.', '_').replace('*', '_'); } - @Deprecated - // Use withSortBy instead - public SearchQueryBuilder withSort(String sortByStr, SortOrder sortOrder) { - String sortOrderStr = "ASC"; - if (sortOrder != null) { - sortOrderStr = sortOrder.toString(); - } - - this.sortClause += " ORDER BY " + entityPrefix(sortByStr) + " " + sortOrderStr; - - return this; - } - /** * Takes a list of SortBy options that should typically come in a searchRequest. * Applies the entries in the list to sort the SearchQuery. @@ -387,7 +374,7 @@ public SearchQueryBuilder filterBy(List filterBy) throws BrAPIServe } for (FilterBy filter : filterBy) { - searchQuery = appendLike(filter.getValue(), filter.getFilterOn()); + searchQuery = appendLike(filter.getValue().toLowerCase(), filter.getFilterOn()); } return searchQuery;