From 12c5e0c5a704af4e7c47b755d92ea624cfde02a6 Mon Sep 17 00:00:00 2001 From: Mark Czotter Date: Wed, 31 May 2023 17:53:25 +0200 Subject: [PATCH] feat(api): introduce new generic concept search endpoints `GET /concepts` and `POST /concepts/search` can be used to easily search across all available terminologies. --- .../core/rest/concepts/ConceptRestSearch.java | 64 ++++++++++++++ .../concepts/ConceptSearchRestService.java | 85 +++++++++++++++++++ .../core/request/ConceptSearchRequest.java | 11 ++- 3 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 core/com.b2international.snowowl.core.rest/src/com/b2international/snowowl/core/rest/concepts/ConceptRestSearch.java create mode 100644 core/com.b2international.snowowl.core.rest/src/com/b2international/snowowl/core/rest/concepts/ConceptSearchRestService.java diff --git a/core/com.b2international.snowowl.core.rest/src/com/b2international/snowowl/core/rest/concepts/ConceptRestSearch.java b/core/com.b2international.snowowl.core.rest/src/com/b2international/snowowl/core/rest/concepts/ConceptRestSearch.java new file mode 100644 index 00000000000..044764514e2 --- /dev/null +++ b/core/com.b2international.snowowl.core.rest/src/com/b2international/snowowl/core/rest/concepts/ConceptRestSearch.java @@ -0,0 +1,64 @@ +/* + * Copyright 2023 B2i Healthcare Pte Ltd, http://b2i.sg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.b2international.snowowl.core.rest.concepts; + +import java.util.List; + +import com.b2international.snowowl.core.rest.domain.ObjectRestSearch; + +/** + * @since 8.11.0 + */ +public class ConceptRestSearch extends ObjectRestSearch { + + private Boolean active; + private List codeSystem; + private String term; + private String query; + + public Boolean getActive() { + return active; + } + + public List getCodeSystem() { + return codeSystem; + } + + public String getQuery() { + return query; + } + + public String getTerm() { + return term; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public void setCodeSystem(List codeSystem) { + this.codeSystem = codeSystem; + } + + public void setQuery(String query) { + this.query = query; + } + + public void setTerm(String term) { + this.term = term; + } + +} diff --git a/core/com.b2international.snowowl.core.rest/src/com/b2international/snowowl/core/rest/concepts/ConceptSearchRestService.java b/core/com.b2international.snowowl.core.rest/src/com/b2international/snowowl/core/rest/concepts/ConceptSearchRestService.java new file mode 100644 index 00000000000..46f305cb551 --- /dev/null +++ b/core/com.b2international.snowowl.core.rest/src/com/b2international/snowowl/core/rest/concepts/ConceptSearchRestService.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 B2i Healthcare Pte Ltd, http://b2i.sg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.b2international.snowowl.core.rest.concepts; + +import org.elasticsearch.core.Set; +import org.springdoc.api.annotations.ParameterObject; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.b2international.snowowl.core.codesystem.CodeSystemRequests; +import com.b2international.snowowl.core.domain.Concepts; +import com.b2international.snowowl.core.events.util.Promise; +import com.b2international.snowowl.core.rest.AbstractRestService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * @since 8.11.0 + */ +@Tag(description = "Concepts", name = "concepts") +@RestController +@RequestMapping(value = "/concepts", produces = { AbstractRestService.JSON_MEDIA_TYPE }) +public class ConceptSearchRestService extends AbstractRestService { + + public ConceptSearchRestService() { + super(Set.of("id", "active")); + } + + @Operation( + summary = "Search concepts", + description = "Returns a list of concepts matching the given search criteria." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Invalid search config"), + }) + @GetMapping + public Promise searchConcepts(@ParameterObject ConceptRestSearch params) { + return CodeSystemRequests.prepareSearchConcepts() + .filterByActive(params.getActive()) + .filterByTerm(params.getTerm()) + .filterByCodeSystems(params.getCodeSystem()) + .filterByQuery(params.getQuery()) + .setFields(params.getField()) + .setExpand(params.getExpand()) + .setLimit(params.getLimit()) + .setSearchAfter(params.getSearchAfter()) + .sortBy(extractSortFields(params.getSort())) + .buildAsync() + .execute(getBus()); + } + + @Operation( + summary = "Search concepts", + description = "Returns a list of concepts matching the given search criteria." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "400", description = "Invalid search config"), + }) + @PostMapping(value = "/search", consumes = { AbstractRestService.JSON_MEDIA_TYPE }) + public Promise searchConceptsByPost(@RequestBody ConceptRestSearch params) { + return searchConcepts(params); + } + +} diff --git a/core/com.b2international.snowowl.core/src/com/b2international/snowowl/core/request/ConceptSearchRequest.java b/core/com.b2international.snowowl.core/src/com/b2international/snowowl/core/request/ConceptSearchRequest.java index 8d12fc5ea88..518bcb1ce4d 100644 --- a/core/com.b2international.snowowl.core/src/com/b2international/snowowl/core/request/ConceptSearchRequest.java +++ b/core/com.b2international.snowowl.core/src/com/b2international/snowowl/core/request/ConceptSearchRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 B2i Healthcare Pte Ltd, http://b2i.sg + * Copyright 2020-2023 B2i Healthcare Pte Ltd, http://b2i.sg * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import com.b2international.commons.exceptions.BadRequestException; import com.b2international.commons.options.Options; +import com.b2international.snowowl.core.Repository; import com.b2international.snowowl.core.RepositoryManager; import com.b2international.snowowl.core.ResourceURI; import com.b2international.snowowl.core.ServiceProvider; @@ -94,7 +95,13 @@ protected Concepts doExecute(ServiceProvider context) throws IOException { .stream() .map(codeSystem -> { final ResourceURI uriToEvaluateOn = codeSystemResourceFiltersByResource.getOrDefault(codeSystem.getResourceURI(), codeSystem.getResourceURI()); - return context.service(RepositoryManager.class).get(codeSystem.getToolingId()).service(ConceptSearchRequestEvaluator.class).evaluate(uriToEvaluateOn, context, conceptSearchOptions); + final Repository repository = context.service(RepositoryManager.class).get(codeSystem.getToolingId()); + if (repository == null) { + context.log().warn("Tooling module '{}' is missing from this deployment.", codeSystem.getToolingId()); + return new Concepts(0, 0); + } else { + return repository.service(ConceptSearchRequestEvaluator.class).evaluate(uriToEvaluateOn, context, conceptSearchOptions); + } }) // .sorted(comparator) // TODO perform Java SORT on Concept fields // .limit(limit)