From bfd4b530b37dfca36b0ef6567f1e900ecc5cbecb Mon Sep 17 00:00:00 2001 From: Todd Young Date: Wed, 19 Feb 2025 09:08:52 -0500 Subject: [PATCH 1/2] wip: add standards searching capability OCD-4711 --- .../chpl/search/dao/ListingSearchDao.java | 15 +++++++++++++++ .../chpl/search/domain/ListingSearchResult.java | 2 ++ .../chpl/search/domain/SearchRequest.java | 3 +++ .../chpl/search/entity/ListingSearchEntity.java | 3 +++ 4 files changed, 23 insertions(+) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/dao/ListingSearchDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/dao/ListingSearchDao.java index 539a16d0e6..bd60b95c75 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/dao/ListingSearchDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/dao/ListingSearchDao.java @@ -13,6 +13,7 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.springframework.stereotype.Repository; import gov.healthit.chpl.dao.impl.BaseDAOImpl; @@ -136,6 +137,7 @@ private ListingSearchResult buildListingSearchResult(ListingSearchEntity entity) .apiDocumentation(convertToSetOfCriteriaWithStringFields(entity.getCriteriaWithApiDocumentation(), CertifiedProductSearchResult.SMILEY_SPLIT_CHAR)) .serviceBaseUrlList(convertToCriterionWithStringField(entity.getCriteriaWithServiceBaseUrlList())) .svaps(convertToSetOfCriteriaWithLongFields(entity.getCriteriaWithSvap(), CertifiedProductSearchResult.SMILEY_SPLIT_CHAR)) + .standardsMet(convertToSetOfLongs(entity.getStandardsMet(), STANDARD_VALUE_SPLIT_CHAR)) .build(); } @@ -172,6 +174,19 @@ private Set convertToSetOfStrings(String delimitedString, String valueTo .collect(Collectors.toSet()); } + private Set convertToSetOfLongs(String delimitedString, String delimeter) + throws EntityRetrievalException, NumberFormatException { + if (ObjectUtils.isEmpty(delimitedString)) { + return new LinkedHashSet(); + } + + String[] splitStrings = delimitedString.split(delimeter); + return Stream.of(splitStrings) + .filter(str -> NumberUtils.isParsable(str)) + .map(str -> Long.valueOf(str)) + .collect(Collectors.toSet()); + } + private Set convertToSetOfDateRangesWithDelimiter(String delimitedDateRangeString, String delimeter) throws EntityRetrievalException, DateTimeParseException, NumberFormatException { if (ObjectUtils.isEmpty(delimitedDateRangeString)) { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/ListingSearchResult.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/ListingSearchResult.java index e2977997a3..f07d435446 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/ListingSearchResult.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/ListingSearchResult.java @@ -81,6 +81,7 @@ public class ListingSearchResult implements Serializable { private String rwtPlansUrl; private String rwtResultsUrl; private String svapNoticeUrl; + private Set standardsMet; public ListingSearchResult() { this.setDirectReviewCount(0); @@ -101,6 +102,7 @@ public ListingSearchResult() { statusEvents = new HashSet(); apiDocumentation = new HashSet(); svaps = new HashSet(); + standardsMet = new HashSet(); } @Override diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/SearchRequest.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/SearchRequest.java index 9063bb9b01..d61f97907f 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/SearchRequest.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/SearchRequest.java @@ -85,6 +85,9 @@ public class SearchRequest implements Serializable { private String svapOperatorString; private SearchSetOperator svapOperator; + @Builder.Default + private Set standards = new HashSet(); + @JsonIgnore private String orderByString; private OrderByOption orderBy; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/entity/ListingSearchEntity.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/entity/ListingSearchEntity.java index 5566ddce64..b11b21d9fb 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/entity/ListingSearchEntity.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/entity/ListingSearchEntity.java @@ -163,4 +163,7 @@ public class ListingSearchEntity { @Column(name = "children") private String children; + + @Column(name = "standards_met") + private String standardsMet; } From 40c951023ba4e093c73f64b62d87a04ff29c3a1d Mon Sep 17 00:00:00 2001 From: Todd Young Date: Fri, 21 Feb 2025 09:42:00 -0500 Subject: [PATCH 2/2] feat: allow searching for listings by standards OCD-4711 --- .gitignore | 1 + .../chpl/web/controller/SearchController.java | 457 +++++++++--------- .../chpl/search/ListingSearchService.java | 15 + .../chpl/search/SearchRequestNormalizer.java | 28 ++ .../chpl/search/domain/SearchRequest.java | 10 +- 5 files changed, 288 insertions(+), 223 deletions(-) diff --git a/.gitignore b/.gitignore index 6834c137ea..844ff2d825 100644 --- a/.gitignore +++ b/.gitignore @@ -278,3 +278,4 @@ ready-for-integration.sh update-pr.sh /chpl/chpl-api/e2e/env/*.postman_environment.json newman/ +Servers/ diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/SearchController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/SearchController.java index 13814dca95..3aa6b83335 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/SearchController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/SearchController.java @@ -25,7 +25,9 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.log4j.Log4j2; +@Log4j2 @Tag(name = "search", description = "Search all CHPL listing data.") @RestController @RequestMapping("/search") @@ -41,124 +43,128 @@ public SearchController(ListingSearchService searchService) { "checkstyle:methodlength", "checkstyle:parameternumber" }) @Operation(summary = "Search the CHPL", - description = "This endpoint will always use the oldest, valid version of the " - + "/search/vX endpoint. The current version being used is v3. For the " - + "current documentation, see /search/v3.", + description = "This endpoint will always use the oldest, valid version of the " + + "/search/vX endpoint. The current version being used is v3. For the " + + "current documentation, see /search/v3.", security = {@SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY)}) @RequestMapping(method = RequestMethod.GET, produces = "application/json; charset=utf-8") public @ResponseBody ListingSearchResponse search( - @Parameter(description = "CHPL ID, Developer (or previous developer) Name, Product Name, ONC-ACB Certification ID", + @Parameter(description = "CHPL ID, Developer (or previous developer) Name, Product Name, ONC-ACB Certification ID", allowEmptyValue = true, in = ParameterIn.QUERY, name = "searchTerm") - @RequestParam(value = "searchTerm", required = false, defaultValue = "") String searchTerm, - @Parameter(description = "A comma-separated list of listing IDs to be queried together (ex: \"1,2\" finds the listing with ID 1 and the listing with ID 2.", + @RequestParam(value = "searchTerm", required = false, defaultValue = "") String searchTerm, + @Parameter(description = "A comma-separated list of listing IDs to be queried together (ex: \"1,2\" finds the listing with ID 1 and the listing with ID 2.", allowEmptyValue = true, in = ParameterIn.QUERY, name = "listingIds") - @RequestParam(value = "listingIds", required = false, defaultValue = "") String listingIdsDelimited, - @Parameter(description = "A comma-separated list of certification statuses (ex: \"Active,Retired,Withdrawn by Developer\"). Results may match any of the provided statuses.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationStatuses") - @RequestParam(value = "certificationStatuses", required = false, defaultValue = "") String certificationStatusesDelimited, - @Parameter(description = "A comma-separated list of derived certification editions (ex: \"2015,2015 Cures Update\" finds listings that are either 2015 or 2015 Cures Update). Allowable values are 2011, 2014, 2015, and \"2015 Cures Update\". Results may match any of the provided derived editions.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "derivedCertificationEditions") - @RequestParam(value = "derivedCertificationEditions", required = false, defaultValue = "") String derivedCertificationEditionsDelimited, - @Parameter(description = "A comma-separated list of certification criteria IDs to be queried together (ex: \"1,2\" finds listings attesting to 170.315 (a)(1) or 170.315 (a)(2)).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationCriteriaIds") - @RequestParam(value = "certificationCriteriaIds", required = false, defaultValue = "") String certificationCriteriaIdsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR. " - + "Indicates whether a listing must have all certificationCriteriaIds or " - + "may have any one or more of the certificationCriteriaIds.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationCriteriaOperator") - @RequestParam(value = "certificationCriteriaOperator", required = false, defaultValue = "OR") String certificationCriteriaOperatorStr, - @Parameter(description = "A comma-separated list of cqms to be queried together (ex: \"CMS2,CMS9\" " - + "finds listings with either CMS2 or CMS9).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "cqms") - @RequestParam(value = "cqms", required = false, defaultValue = "") String cqmsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR. " - + "Indicates whether a listing must have all cqms or may have any one or more of the cqms.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "cqmsOperator") - @RequestParam(value = "cqmsOperator", required = false, defaultValue = "OR") String cqmsOperatorStr, - @Parameter(description = "A comma-separated list of certification body names to be 'or'ed together " - + "(ex: \"Drummond,ICSA\" finds listings belonging to either Drummond or ICSA).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationBodies") - @RequestParam(value = "certificationBodies", required = false, defaultValue = "") String certificationBodiesDelimited, - @Parameter(description = "True or False if a listing has ever had surveillance or direct reviews.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasHadComplianceActivity") - @RequestParam(value = "hasHadComplianceActivity", required = false, defaultValue = "") Boolean hasHadComplianceActivity, - @Parameter(description = "A comma-separated list of non-conformity search options applied across surveillance and direct review activity. " - + "Valid options are OPEN_NONCONFORMITY, CLOSED_NONCONFORMITY, NEVER_NONCONFORMITY," - + "NOT_OPEN_NONCONFORMITY, NOT_CLOSED_NONCONFORMITY, and NOT_NEVER_NONCONFORMITY.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "nonConformityOptions") - @RequestParam(value = "nonConformityOptions", required = false, defaultValue = "") String nonConformityOptionsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR." - + "Indicates whether a listing must have met all nonConformityOptions " - + "specified or may have met any one or more of the nonConformityOptions", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "nonConformityOptionsOperator") - @RequestParam(value = "nonConformityOptionsOperator", required = false, defaultValue = "OR") String nonConformityOptionsOperator, - @Parameter(description = "A comma-separated list of Real World Testing search options. " - + "Valid options are HAS_PLANS_URL, HAS_RESULTS_URL, NO_PLANS_URL, NO_RESULTS_URL", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "rwtOptions") - @RequestParam(value = "rwtOptions", required = false, defaultValue = "") String rwtOptionsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR." - + "Indicates whether a listing must have met all rwtOptions specified or may have met any one or more of the rwtOptions", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "rwtOperator") - @RequestParam(value = "rwtOperator", required = false, defaultValue = "OR") String rwtOperator, - @Parameter(description = "A comma-separated list of SVAP IDs to be queried together (ex: \"1,2\" finds listings associated with those SVAPs).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapIds") - @RequestParam(value = "svapIds", required = false, defaultValue = "") String svapIdsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR. " - + "Indicates whether a listing must have all svapIds or may have any one or more of the svapIds.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapOperator") - @RequestParam(value = "svapOperator", required = false, defaultValue = "OR") String svapOperatorStr, - @Parameter(description = "Specifies whether to match listings with an empty or non-empty SVAP Notice Url.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasSvapNoticeUrl") - @RequestParam(value = "hasSvapNoticeUrl", required = false, defaultValue = "") Boolean hasSvapNoticeUrl, - @Parameter(description = "Specifies whether to match listings with SVAP data associated to any criteria or " - + "a non-empty SVAP Notice URL.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasAnySvap") - @RequestParam(value = "hasAnySvap", required = false, defaultValue = "") Boolean hasAnySvap, - @Parameter(description = "The full name of a developer.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "developer") - @RequestParam(value = "developer", required = false, defaultValue = "") String developer, - @Parameter(description = "The full name of a product.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "product") - @RequestParam(value = "product", required = false, defaultValue = "") String product, - @Parameter(description = "The full name of a version.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "version") - @RequestParam(value = "version", required = false, defaultValue = "") String version, - @Parameter(description = "A practice type (either Ambulatory or Inpatient). Valid only for 2014 listings.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "practiceType") - @RequestParam(value = "practiceType", required = false, defaultValue = "") String practiceType, - @Parameter(description = "To return only listings certified on or after this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationDateStart") - @RequestParam(value = "certificationDateStart", required = false, defaultValue = "") String certificationDateStart, - @Parameter(description = "To return only listings certified on or before this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationDateEnd") - @RequestParam(value = "certificationDateEnd", required = false, defaultValue = "") String certificationDateEnd, - @Parameter(description = "To return only listings decertified on or after this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, - allowEmptyValue = true, in = ParameterIn.QUERY, name = "decertificationDateStart") - @RequestParam(value = "decertificationDateStart", required = false, defaultValue = "") String decertificationDateStart, - @Parameter(description = "To return only listings decertified on or before this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, - allowEmptyValue = true, in = ParameterIn.QUERY, name = "decertificationDateEnd") - @RequestParam(value = "decertificationDateEnd", required = false, defaultValue = "") String decertificationDateEnd, - @Parameter(description = "Zero-based page number used in concert with pageSize. Defaults to 0.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "pageNumber") - @RequestParam(value = "pageNumber", required = false, defaultValue = "0") Integer pageNumber, - @Parameter(description = "Number of results to return used in concert with pageNumber. " - + "Defaults to 20. Maximum allowed page size is 100.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "pageSize") - @RequestParam(value = "pageSize", required = false, defaultValue = "20") Integer pageSize, - @Parameter(description = "What to order by. Options are one of the following: CERTIFICATION_DATE, CHPL_ID, " - + "DEVELOPER, PRODUCT, VERSION, EDITION, STATUS, OPEN_SURVEILLANCE_NC_COUNT, CLOSED_SURVEILLANCE_NC_COUNT, " - + "OPEN_DIRECT_REVIEW_NC_COUNT, or CLOSED_DIRECT_REVIEW_NC_COUNT. Defaults to PRODUCT.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "orderBy") - @RequestParam(value = "orderBy", required = false, defaultValue = "product") String orderBy, - @Parameter(description = "Use to specify the direction of the sort. Defaults to false (ascending sort).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "sortDescending") - @RequestParam(value = "sortDescending", required = false, defaultValue = "false") Boolean sortDescending) - throws InvalidArgumentsException, ValidationException { + @RequestParam(value = "listingIds", required = false, defaultValue = "") String listingIdsDelimited, + @Parameter(description = "A comma-separated list of certification statuses (ex: \"Active,Retired,Withdrawn by Developer\"). Results may match any of the provided statuses.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationStatuses") + @RequestParam(value = "certificationStatuses", required = false, defaultValue = "") String certificationStatusesDelimited, + @Parameter(description = "A comma-separated list of derived certification editions (ex: \"2015,2015 Cures Update\" finds listings that are either 2015 or 2015 Cures Update). Allowable values are 2011, 2014, 2015, and \"2015 Cures Update\". Results may match any of the provided derived editions.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "derivedCertificationEditions") + @RequestParam(value = "derivedCertificationEditions", required = false, defaultValue = "") String derivedCertificationEditionsDelimited, + @Parameter(description = "A comma-separated list of certification criteria IDs to be queried together (ex: \"1,2\" finds listings attesting to 170.315 (a)(1) or 170.315 (a)(2)).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationCriteriaIds") + @RequestParam(value = "certificationCriteriaIds", required = false, defaultValue = "") String certificationCriteriaIdsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR. " + + "Indicates whether a listing must have all certificationCriteriaIds or " + + "may have any one or more of the certificationCriteriaIds.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationCriteriaOperator") + @RequestParam(value = "certificationCriteriaOperator", required = false, defaultValue = "OR") String certificationCriteriaOperatorStr, + @Parameter(description = "A comma-separated list of cqms to be queried together (ex: \"CMS2,CMS9\" " + + "finds listings with either CMS2 or CMS9).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "cqms") + @RequestParam(value = "cqms", required = false, defaultValue = "") String cqmsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR. " + + "Indicates whether a listing must have all cqms or may have any one or more of the cqms.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "cqmsOperator") + @RequestParam(value = "cqmsOperator", required = false, defaultValue = "OR") String cqmsOperatorStr, + @Parameter(description = "A comma-separated list of certification body names to be 'or'ed together " + + "(ex: \"Drummond,ICSA\" finds listings belonging to either Drummond or ICSA).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationBodies") + @RequestParam(value = "certificationBodies", required = false, defaultValue = "") String certificationBodiesDelimited, + @Parameter(description = "True or False if a listing has ever had surveillance or direct reviews.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasHadComplianceActivity") + @RequestParam(value = "hasHadComplianceActivity", required = false, defaultValue = "") Boolean hasHadComplianceActivity, + @Parameter(description = "A comma-separated list of non-conformity search options applied across surveillance and direct review activity. " + + "Valid options are OPEN_NONCONFORMITY, CLOSED_NONCONFORMITY, NEVER_NONCONFORMITY," + + "NOT_OPEN_NONCONFORMITY, NOT_CLOSED_NONCONFORMITY, and NOT_NEVER_NONCONFORMITY.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "nonConformityOptions") + @RequestParam(value = "nonConformityOptions", required = false, defaultValue = "") String nonConformityOptionsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR." + + "Indicates whether a listing must have met all nonConformityOptions " + + "specified or may have met any one or more of the nonConformityOptions", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "nonConformityOptionsOperator") + @RequestParam(value = "nonConformityOptionsOperator", required = false, defaultValue = "OR") String nonConformityOptionsOperator, + @Parameter(description = "A comma-separated list of Real World Testing search options. " + + "Valid options are HAS_PLANS_URL, HAS_RESULTS_URL, NO_PLANS_URL, NO_RESULTS_URL", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "rwtOptions") + @RequestParam(value = "rwtOptions", required = false, defaultValue = "") String rwtOptionsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR." + + "Indicates whether a listing must have met all rwtOptions specified or may have met any one or more of the rwtOptions", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "rwtOperator") + @RequestParam(value = "rwtOperator", required = false, defaultValue = "OR") String rwtOperator, + @Parameter(description = "A comma-separated list of SVAP IDs to be queried together (ex: \"1,2\" finds listings associated with those SVAPs).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapIds") + @RequestParam(value = "svapIds", required = false, defaultValue = "") String svapIdsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR. " + + "Indicates whether a listing must have all svapIds or may have any one or more of the svapIds.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapOperator") + @RequestParam(value = "svapOperator", required = false, defaultValue = "OR") String svapOperatorStr, + @Parameter(description = "Specifies whether to match listings with an empty or non-empty SVAP Notice Url.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasSvapNoticeUrl") + @RequestParam(value = "hasSvapNoticeUrl", required = false, defaultValue = "") Boolean hasSvapNoticeUrl, + @Parameter(description = "Specifies whether to match listings with SVAP data associated to any criteria or " + + "a non-empty SVAP Notice URL.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasAnySvap") + @RequestParam(value = "hasAnySvap", required = false, defaultValue = "") Boolean hasAnySvap, + @Parameter(description = "A comma-separated list of Standard IDs to be queried together (ex: \"1,2\" finds listings associated with those Standards).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "standardIds") @RequestParam(value = "standardIds", required = false, defaultValue = "") String standardIdsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR. " + + "Indicates whether a listing must have all standardIds or may have any one or more of the standardIds.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapOperator") @RequestParam(value = "standardOperator", required = false, defaultValue = "OR") String standardOperatorStr, + @Parameter(description = "The full name of a developer.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "developer") @RequestParam(value = "developer", required = false, defaultValue = "") String developer, + @Parameter(description = "The full name of a product.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "product") + @RequestParam(value = "product", required = false, defaultValue = "") String product, + @Parameter(description = "The full name of a version.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "version") + @RequestParam(value = "version", required = false, defaultValue = "") String version, + @Parameter(description = "A practice type (either Ambulatory or Inpatient). Valid only for 2014 listings.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "practiceType") + @RequestParam(value = "practiceType", required = false, defaultValue = "") String practiceType, + @Parameter(description = "To return only listings certified on or after this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationDateStart") + @RequestParam(value = "certificationDateStart", required = false, defaultValue = "") String certificationDateStart, + @Parameter(description = "To return only listings certified on or before this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationDateEnd") + @RequestParam(value = "certificationDateEnd", required = false, defaultValue = "") String certificationDateEnd, + @Parameter(description = "To return only listings decertified on or after this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, + allowEmptyValue = true, in = ParameterIn.QUERY, name = "decertificationDateStart") + @RequestParam(value = "decertificationDateStart", required = false, defaultValue = "") String decertificationDateStart, + @Parameter(description = "To return only listings decertified on or before this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, + allowEmptyValue = true, in = ParameterIn.QUERY, name = "decertificationDateEnd") + @RequestParam(value = "decertificationDateEnd", required = false, defaultValue = "") String decertificationDateEnd, + @Parameter(description = "Zero-based page number used in concert with pageSize. Defaults to 0.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "pageNumber") + @RequestParam(value = "pageNumber", required = false, defaultValue = "0") Integer pageNumber, + @Parameter(description = "Number of results to return used in concert with pageNumber. " + + "Defaults to 20. Maximum allowed page size is 100.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "pageSize") + @RequestParam(value = "pageSize", required = false, defaultValue = "20") Integer pageSize, + @Parameter(description = "What to order by. Options are one of the following: CERTIFICATION_DATE, CHPL_ID, " + + "DEVELOPER, PRODUCT, VERSION, EDITION, STATUS, OPEN_SURVEILLANCE_NC_COUNT, CLOSED_SURVEILLANCE_NC_COUNT, " + + "OPEN_DIRECT_REVIEW_NC_COUNT, or CLOSED_DIRECT_REVIEW_NC_COUNT. Defaults to PRODUCT.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "orderBy") + @RequestParam(value = "orderBy", required = false, defaultValue = "product") String orderBy, + @Parameter(description = "Use to specify the direction of the sort. Defaults to false (ascending sort).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "sortDescending") + @RequestParam(value = "sortDescending", required = false, defaultValue = "false") Boolean sortDescending) + throws InvalidArgumentsException, ValidationException { return searchV3(searchTerm, listingIdsDelimited, certificationStatusesDelimited, derivedCertificationEditionsDelimited, certificationCriteriaIdsDelimited, certificationCriteriaOperatorStr, cqmsDelimited, cqmsOperatorStr, certificationBodiesDelimited, hasHadComplianceActivity, nonConformityOptionsDelimited, nonConformityOptionsOperator, rwtOptionsDelimited, rwtOperator, - svapIdsDelimited, svapOperatorStr, hasSvapNoticeUrl, hasAnySvap, developer, + svapIdsDelimited, svapOperatorStr, hasSvapNoticeUrl, hasAnySvap, standardIdsDelimited, standardOperatorStr, developer, product, version, practiceType, certificationDateStart, certificationDateEnd, decertificationDateStart, decertificationDateEnd, pageNumber, pageSize, orderBy, sortDescending); } @@ -167,123 +173,130 @@ public SearchController(ListingSearchService searchService) { "checkstyle:methodlength", "checkstyle:parameternumber" }) @Operation(summary = "Search the CHPL", - description = "If paging parameters are not specified, the first 20 records are returned by default. " - + "All parameters are optional. " - + "Any parameter that can accept multiple things (i.e. certificationStatuses) expects " - + "a comma-delimited list of those things (i.e. certificationStatuses = Active,Suspended). " - + "Date parameters are required to be in the format " - + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT + ". ", + description = "If paging parameters are not specified, the first 20 records are returned by default. " + + "All parameters are optional. " + + "Any parameter that can accept multiple things (i.e. certificationStatuses) expects " + + "a comma-delimited list of those things (i.e. certificationStatuses = Active,Suspended). " + + "Date parameters are required to be in the format " + + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT + ". ", security = {@SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY)}) @RequestMapping(value = "/v3", method = RequestMethod.GET, produces = "application/json; charset=utf-8") public @ResponseBody ListingSearchResponse searchV3( - @Parameter(description = "CHPL ID, Developer (or previous developer) Name, Product Name, ONC-ACB Certification ID", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "searchTerm") - @RequestParam(value = "searchTerm", required = false, defaultValue = "") String searchTerm, - @Parameter(description = "A comma-separated list of listing IDs to be queried together (ex: \"1,2\" finds the listing with ID 1 and the listing with ID 2.", + @Parameter(description = "CHPL ID, Developer (or previous developer) Name, Product Name, ONC-ACB Certification ID", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "searchTerm") + @RequestParam(value = "searchTerm", required = false, defaultValue = "") String searchTerm, + @Parameter(description = "A comma-separated list of listing IDs to be queried together (ex: \"1,2\" finds the listing with ID 1 and the listing with ID 2.", allowEmptyValue = true, in = ParameterIn.QUERY, name = "listingIds") - @RequestParam(value = "listingIds", required = false, defaultValue = "") String listingIdsDelimited, - @Parameter(description = "A comma-separated list of certification statuses (ex: \"Active,Retired,Withdrawn by Developer\"). Results may match any of the provided statuses.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationStatuses") - @RequestParam(value = "certificationStatuses", required = false, defaultValue = "") String certificationStatusesDelimited, - @Parameter(description = "A comma-separated list of derived certification editions (ex: \"2015,2015 Cures Update\" finds listings that are either 2015 or 2015 Cures Update). Allowable values are 2011, 2014, 2015, and \"2015 Cures Update\". Results may match any of the provided derived editions.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "derivedCertificationEditions") - @RequestParam(value = "derivedCertificationEditions", required = false, defaultValue = "") String derivedCertificationEditionsDelimited, - @Parameter(description = "A comma-separated list of certification criteria IDs to be queried together (ex: \"1,2\" finds listings attesting to 170.315 (a)(1) or 170.315 (a)(2)).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationCriteriaIds") - @RequestParam(value = "certificationCriteriaIds", required = false, defaultValue = "") String certificationCriteriaIdsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR. " - + "Indicates whether a listing must have all certificationCriteriaIds or " - + "may have any one or more of the certificationCriteriaIds.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationCriteriaOperator") - @RequestParam(value = "certificationCriteriaOperator", required = false, defaultValue = "OR") String certificationCriteriaOperatorStr, - @Parameter(description = "A comma-separated list of cqms to be queried together (ex: \"CMS2,CMS9\" " - + "finds listings with either CMS2 or CMS9).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "cqms") - @RequestParam(value = "cqms", required = false, defaultValue = "") String cqmsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR. " - + "Indicates whether a listing must have all cqms or may have any one or more of the cqms.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "cqmsOperator") - @RequestParam(value = "cqmsOperator", required = false, defaultValue = "OR") String cqmsOperatorStr, - @Parameter(description = "A comma-separated list of certification body names to be 'or'ed together " - + "(ex: \"Drummond,ICSA\" finds listings belonging to either Drummond or ICSA).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationBodies") - @RequestParam(value = "certificationBodies", required = false, defaultValue = "") String certificationBodiesDelimited, - @Parameter(description = "True or False if a listing has ever had surveillance or direct reviews.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasHadComplianceActivity") - @RequestParam(value = "hasHadComplianceActivity", required = false, defaultValue = "") Boolean hasHadComplianceActivity, - @Parameter(description = "A comma-separated list of non-conformity search options applied across surveillance and direct review activity. " - + "Valid options are OPEN_NONCONFORMITY, CLOSED_NONCONFORMITY, NEVER_NONCONFORMITY," - + "NOT_OPEN_NONCONFORMITY, NOT_CLOSED_NONCONFORMITY, and NOT_NEVER_NONCONFORMITY.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "nonConformityOptions") - @RequestParam(value = "nonConformityOptions", required = false, defaultValue = "") String nonConformityOptionsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR." - + "Indicates whether a listing must have met all nonConformityOptions " - + "specified or may have met any one or more of the nonConformityOptions", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "nonConformityOptionsOperator") - @RequestParam(value = "nonConformityOptionsOperator", required = false, defaultValue = "OR") String nonConformityOptionsOperator, - @Parameter(description = "A comma-separated list of Real World Testing search options. " - + "Valid options are HAS_PLANS_URL, HAS_RESULTS_URL, NO_PLANS_URL, NO_RESULTS_URL", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "rwtOptions") - @RequestParam(value = "rwtOptions", required = false, defaultValue = "") String rwtOptionsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR." - + "Indicates whether a listing must have met all rwtOptions specified or may have met any one or more of the rwtOptions", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "rwtOperator") - @RequestParam(value = "rwtOperator", required = false, defaultValue = "OR") String rwtOperator, - @Parameter(description = "A comma-separated list of SVAP IDs to be queried together (ex: \"1,2\" finds listings associated with those SVAPs).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapIds") - @RequestParam(value = "svapIds", required = false, defaultValue = "") String svapIdsDelimited, - @Parameter(description = "Either AND or OR. Defaults to OR. " - + "Indicates whether a listing must have all svapIds or may have any one or more of the svapIds.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapOperator") - @RequestParam(value = "svapOperator", required = false, defaultValue = "OR") String svapOperatorStr, - @Parameter(description = "Specifies whether to match listings with an empty or non-empty SVAP Notice Url.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasSvapNoticeUrl") - @RequestParam(value = "hasSvapNoticeUrl", required = false, defaultValue = "") Boolean hasSvapNoticeUrl, - @Parameter(description = "Specifies whether to match listings with SVAP data associated to any criteria or " - + "a non-empty SVAP Notice URL.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasAnySvap") - @RequestParam(value = "hasAnySvap", required = false, defaultValue = "") Boolean hasAnySvap, - @Parameter(description = "The full name of a developer.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "developer") - @RequestParam(value = "developer", required = false, defaultValue = "") String developer, - @Parameter(description = "The full name of a product.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "product") - @RequestParam(value = "product", required = false, defaultValue = "") String product, - @Parameter(description = "The full name of a version.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "version") - @RequestParam(value = "version", required = false, defaultValue = "") String version, - @Parameter(description = "A practice type (either Ambulatory or Inpatient). Valid only for 2014 listings.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "practiceType") - @RequestParam(value = "practiceType", required = false, defaultValue = "") String practiceType, - @Parameter(description = "To return only listings certified on or after this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationDateStart") - @RequestParam(value = "certificationDateStart", required = false, defaultValue = "") String certificationDateStart, - @Parameter(description = "To return only listings certified on or before this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, - allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationDateEnd") - @RequestParam(value = "certificationDateEnd", required = false, defaultValue = "") String certificationDateEnd, - @Parameter(description = "To return only listings decertified on or after this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, - allowEmptyValue = true, in = ParameterIn.QUERY, name = "decertificationDateStart") - @RequestParam(value = "decertificationDateStart", required = false, defaultValue = "") String decertificationDateStart, - @Parameter(description = "To return only listings decertified on or before this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, - allowEmptyValue = true, in = ParameterIn.QUERY, name = "decertificationDateEnd") - @RequestParam(value = "decertificationDateEnd", required = false, defaultValue = "") String decertificationDateEnd, - @Parameter(description = "Zero-based page number used in concert with pageSize. Defaults to 0.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "pageNumber") - @RequestParam(value = "pageNumber", required = false, defaultValue = "0") Integer pageNumber, - @Parameter(description = "Number of results to return used in concert with pageNumber. " - + "Defaults to 20. Maximum allowed page size is 100.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "pageSize") - @RequestParam(value = "pageSize", required = false, defaultValue = "20") Integer pageSize, - @Parameter(description = "What to order by. Options are one of the following: CERTIFICATION_DATE, CHPL_ID, " - + "DEVELOPER, PRODUCT, VERSION, EDITION, STATUS, OPEN_SURVEILLANCE_NC_COUNT, CLOSED_SURVEILLANCE_NC_COUNT, " - + "OPEN_DIRECT_REVIEW_NC_COUNT, or CLOSED_DIRECT_REVIEW_NC_COUNT. Defaults to PRODUCT.", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "orderBy") - @RequestParam(value = "orderBy", required = false, defaultValue = "product") String orderBy, - @Parameter(description = "Use to specify the direction of the sort. Defaults to false (ascending sort).", - allowEmptyValue = true, in = ParameterIn.QUERY, name = "sortDescending") - @RequestParam(value = "sortDescending", required = false, defaultValue = "false") Boolean sortDescending) - throws InvalidArgumentsException, ValidationException { + @RequestParam(value = "listingIds", required = false, defaultValue = "") String listingIdsDelimited, + @Parameter(description = "A comma-separated list of certification statuses (ex: \"Active,Retired,Withdrawn by Developer\"). Results may match any of the provided statuses.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationStatuses") + @RequestParam(value = "certificationStatuses", required = false, defaultValue = "") String certificationStatusesDelimited, + @Parameter(description = "A comma-separated list of derived certification editions (ex: \"2015,2015 Cures Update\" finds listings that are either 2015 or 2015 Cures Update). Allowable values are 2011, 2014, 2015, and \"2015 Cures Update\". Results may match any of the provided derived editions.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "derivedCertificationEditions") + @RequestParam(value = "derivedCertificationEditions", required = false, defaultValue = "") String derivedCertificationEditionsDelimited, + @Parameter(description = "A comma-separated list of certification criteria IDs to be queried together (ex: \"1,2\" finds listings attesting to 170.315 (a)(1) or 170.315 (a)(2)).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationCriteriaIds") + @RequestParam(value = "certificationCriteriaIds", required = false, defaultValue = "") String certificationCriteriaIdsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR. " + + "Indicates whether a listing must have all certificationCriteriaIds or " + + "may have any one or more of the certificationCriteriaIds.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationCriteriaOperator") + @RequestParam(value = "certificationCriteriaOperator", required = false, defaultValue = "OR") String certificationCriteriaOperatorStr, + @Parameter(description = "A comma-separated list of cqms to be queried together (ex: \"CMS2,CMS9\" " + + "finds listings with either CMS2 or CMS9).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "cqms") + @RequestParam(value = "cqms", required = false, defaultValue = "") String cqmsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR. " + + "Indicates whether a listing must have all cqms or may have any one or more of the cqms.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "cqmsOperator") + @RequestParam(value = "cqmsOperator", required = false, defaultValue = "OR") String cqmsOperatorStr, + @Parameter(description = "A comma-separated list of certification body names to be 'or'ed together " + + "(ex: \"Drummond,ICSA\" finds listings belonging to either Drummond or ICSA).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationBodies") + @RequestParam(value = "certificationBodies", required = false, defaultValue = "") String certificationBodiesDelimited, + @Parameter(description = "True or False if a listing has ever had surveillance or direct reviews.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasHadComplianceActivity") + @RequestParam(value = "hasHadComplianceActivity", required = false, defaultValue = "") Boolean hasHadComplianceActivity, + @Parameter(description = "A comma-separated list of non-conformity search options applied across surveillance and direct review activity. " + + "Valid options are OPEN_NONCONFORMITY, CLOSED_NONCONFORMITY, NEVER_NONCONFORMITY," + + "NOT_OPEN_NONCONFORMITY, NOT_CLOSED_NONCONFORMITY, and NOT_NEVER_NONCONFORMITY.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "nonConformityOptions") + @RequestParam(value = "nonConformityOptions", required = false, defaultValue = "") String nonConformityOptionsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR." + + "Indicates whether a listing must have met all nonConformityOptions " + + "specified or may have met any one or more of the nonConformityOptions", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "nonConformityOptionsOperator") + @RequestParam(value = "nonConformityOptionsOperator", required = false, defaultValue = "OR") String nonConformityOptionsOperator, + @Parameter(description = "A comma-separated list of Real World Testing search options. " + + "Valid options are HAS_PLANS_URL, HAS_RESULTS_URL, NO_PLANS_URL, NO_RESULTS_URL", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "rwtOptions") + @RequestParam(value = "rwtOptions", required = false, defaultValue = "") String rwtOptionsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR." + + "Indicates whether a listing must have met all rwtOptions specified or may have met any one or more of the rwtOptions", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "rwtOperator") + @RequestParam(value = "rwtOperator", required = false, defaultValue = "OR") String rwtOperator, + @Parameter(description = "A comma-separated list of SVAP IDs to be queried together (ex: \"1,2\" finds listings associated with those SVAPs).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapIds") + @RequestParam(value = "svapIds", required = false, defaultValue = "") String svapIdsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR. " + + "Indicates whether a listing must have all svapIds or may have any one or more of the svapIds.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapOperator") + @RequestParam(value = "svapOperator", required = false, defaultValue = "OR") String svapOperatorStr, + @Parameter(description = "Specifies whether to match listings with an empty or non-empty SVAP Notice Url.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasSvapNoticeUrl") + @RequestParam(value = "hasSvapNoticeUrl", required = false, defaultValue = "") Boolean hasSvapNoticeUrl, + @Parameter(description = "Specifies whether to match listings with SVAP data associated to any criteria or " + + "a non-empty SVAP Notice URL.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "hasAnySvap") + @RequestParam(value = "hasAnySvap", required = false, defaultValue = "") Boolean hasAnySvap, + @Parameter(description = "A comma-separated list of Standard IDs to be queried together (ex: \"1,2\" finds listings associated with those Standards).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "standardIds") @RequestParam(value = "standardIds", required = false, defaultValue = "") String standardIdsDelimited, + @Parameter(description = "Either AND or OR. Defaults to OR. " + + "Indicates whether a listing must have all standardIds or may have any one or more of the standardIds.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "svapOperator") @RequestParam(value = "standardOperator", required = false, defaultValue = "OR") String standardOperatorStr, + @Parameter(description = "The full name of a developer.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "developer") + @RequestParam(value = "developer", required = false, defaultValue = "") String developer, + @Parameter(description = "The full name of a product.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "product") + @RequestParam(value = "product", required = false, defaultValue = "") String product, + @Parameter(description = "The full name of a version.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "version") + @RequestParam(value = "version", required = false, defaultValue = "") String version, + @Parameter(description = "A practice type (either Ambulatory or Inpatient). Valid only for 2014 listings.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "practiceType") + @RequestParam(value = "practiceType", required = false, defaultValue = "") String practiceType, + @Parameter(description = "To return only listings certified on or after this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationDateStart") + @RequestParam(value = "certificationDateStart", required = false, defaultValue = "") String certificationDateStart, + @Parameter(description = "To return only listings certified on or before this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, + allowEmptyValue = true, in = ParameterIn.QUERY, name = "certificationDateEnd") + @RequestParam(value = "certificationDateEnd", required = false, defaultValue = "") String certificationDateEnd, + @Parameter(description = "To return only listings decertified on or after this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, + allowEmptyValue = true, in = ParameterIn.QUERY, name = "decertificationDateStart") + @RequestParam(value = "decertificationDateStart", required = false, defaultValue = "") String decertificationDateStart, + @Parameter(description = "To return only listings decertified on or before this date. Required format is " + SearchRequest.CERTIFICATION_DATE_SEARCH_FORMAT, + allowEmptyValue = true, in = ParameterIn.QUERY, name = "decertificationDateEnd") + @RequestParam(value = "decertificationDateEnd", required = false, defaultValue = "") String decertificationDateEnd, + @Parameter(description = "Zero-based page number used in concert with pageSize. Defaults to 0.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "pageNumber") + @RequestParam(value = "pageNumber", required = false, defaultValue = "0") Integer pageNumber, + @Parameter(description = "Number of results to return used in concert with pageNumber. " + + "Defaults to 20. Maximum allowed page size is 100.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "pageSize") + @RequestParam(value = "pageSize", required = false, defaultValue = "20") Integer pageSize, + @Parameter(description = "What to order by. Options are one of the following: CERTIFICATION_DATE, CHPL_ID, " + + "DEVELOPER, PRODUCT, VERSION, EDITION, STATUS, OPEN_SURVEILLANCE_NC_COUNT, CLOSED_SURVEILLANCE_NC_COUNT, " + + "OPEN_DIRECT_REVIEW_NC_COUNT, or CLOSED_DIRECT_REVIEW_NC_COUNT. Defaults to PRODUCT.", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "orderBy") + @RequestParam(value = "orderBy", required = false, defaultValue = "product") String orderBy, + @Parameter(description = "Use to specify the direction of the sort. Defaults to false (ascending sort).", + allowEmptyValue = true, in = ParameterIn.QUERY, name = "sortDescending") + @RequestParam(value = "sortDescending", required = false, defaultValue = "false") Boolean sortDescending) + throws InvalidArgumentsException, ValidationException { + LOGGER.info("Standard Ids: {}", standardIdsDelimited); + LOGGER.info("Standards Operator: {}", standardOperatorStr); SearchRequest searchRequest = SearchRequest.builder() .searchTerm(searchTerm.trim()) .listingIdStrings(convertToSetWithDelimeter(listingIdsDelimited, ",")) @@ -305,6 +318,8 @@ public SearchController(ListingSearchService searchService) { .svapOperatorString(svapOperatorStr) .hasSvapNoticeUrl(hasSvapNoticeUrl) .hasAnySvap(hasAnySvap) + .standardIdStrings(convertToSetWithDelimeter(standardIdsDelimited, ",")) + .standardOperatorString(standardOperatorStr) .developer(developer) .product(product) .version(version) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/ListingSearchService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/ListingSearchService.java index 91e041377d..af30859f9a 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/ListingSearchService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/ListingSearchService.java @@ -124,6 +124,7 @@ public ListingSearchResponse findListings(SearchRequest searchRequest) throws Va .filter(listing -> matchesHasAnySvapFilter(listing, searchRequest.getHasAnySvap())) .filter(listing -> matchesSvapNoticeUrlFilter(listing, searchRequest.getHasSvapNoticeUrl())) .filter(listing -> matchesSvaps(listing, searchRequest.getSvapIds(), searchRequest.getSvapOperator())) + .filter(listing -> matchesStandards(listing, searchRequest.getStandardIds(), searchRequest.getStandardOperator())) .collect(Collectors.toList()); LOGGER.debug("Total matched listings: " + matchedListings.size()); @@ -533,6 +534,20 @@ private Set getSvapIds(Set standardIds, SearchSetOperator searchOperator) { + if (CollectionUtils.isEmpty(standardIds)) { + return true; + } + if (searchOperator.equals(SearchSetOperator.AND)) { + return standardIds.stream() + .allMatch(standardId -> listing.getStandardsMet().contains(standardId)); + } else if (searchOperator.equals(SearchSetOperator.OR)) { + return standardIds.stream() + .anyMatch(standardId -> listing.getStandardsMet().contains(standardId)); + } + return false; + } + private boolean applyOperation(SearchSetOperator operation, Boolean... filters) { List nonNullFilters = Stream.of(filters) .filter(booleanElement -> booleanElement != null) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/SearchRequestNormalizer.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/SearchRequestNormalizer.java index 7bf0ae5b4e..2bf95e49bc 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/SearchRequestNormalizer.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/SearchRequestNormalizer.java @@ -11,7 +11,9 @@ import gov.healthit.chpl.search.domain.RwtSearchOptions; import gov.healthit.chpl.search.domain.SearchRequest; import gov.healthit.chpl.search.domain.SearchSetOperator; +import lombok.extern.log4j.Log4j2; +@Log4j2 public class SearchRequestNormalizer { public void normalize(SearchRequest request) { @@ -31,6 +33,8 @@ public void normalize(SearchRequest request) { normalizeRwtOptionsOperator(request); normalizeSvapIds(request); normalizeSvapOperator(request); + normalizeStanadrdIds(request); + normalizeStandardOperator(request); normalizeOrderBy(request); } @@ -259,6 +263,30 @@ private void normalizeSvapOperator(SearchRequest request) { } } + private void normalizeStanadrdIds(SearchRequest request) { + if (request.getStandardIdStrings() != null && request.getStandardIdStrings().size() > 0 + && (request.getStandardIds() == null || request.getStandardIds().size() == 0)) { + request.setStandardIds(request.getStandardIdStrings().stream() + .filter(standardIdString -> !StringUtils.isBlank(standardIdString)) + .map(standardIdString -> standardIdString.trim()) + .filter(standardIdString -> isParseableLong(standardIdString)) + .map(standardIdString -> Long.parseLong(standardIdString)) + .collect(Collectors.toSet())); + } + } + + private void normalizeStandardOperator(SearchRequest request) { + if (!StringUtils.isBlank(request.getStandardOperatorString()) + && request.getStandardOperator() == null) { + try { + request.setStandardOperator( + SearchSetOperator.valueOf(request.getStandardOperatorString().toUpperCase().trim())); + } catch (Exception ignore) { + LOGGER.error(ignore); + } + } + } + private void normalizeOrderBy(SearchRequest request) { if (!StringUtils.isBlank(request.getOrderByString()) && request.getOrderBy() == null) { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/SearchRequest.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/SearchRequest.java index d61f97907f..250090290a 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/SearchRequest.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/search/domain/SearchRequest.java @@ -86,8 +86,14 @@ public class SearchRequest implements Serializable { private SearchSetOperator svapOperator; @Builder.Default - private Set standards = new HashSet(); - + @JsonIgnore + private Set standardIdStrings = new HashSet(); + @Builder.Default + private Set standardIds = new HashSet(); + @JsonIgnore + private String standardOperatorString; + private SearchSetOperator standardOperator; + @JsonIgnore private String orderByString; private OrderByOption orderBy;