001/*-
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.dao.search;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.jpa.model.entity.ResourceTable;
024import ca.uhn.fhir.jpa.model.entity.StorageSettings;
025import ca.uhn.fhir.jpa.model.search.ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams;
026import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
027import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper;
028import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
029import com.google.gson.JsonObject;
030import org.hibernate.search.backend.elasticsearch.ElasticsearchExtension;
031import org.hibernate.search.engine.search.aggregation.AggregationKey;
032import org.hibernate.search.engine.search.query.SearchResult;
033import org.hibernate.search.mapper.orm.session.SearchSession;
034
035import java.util.List;
036import java.util.Objects;
037
038public class LastNOperation {
039        public static final String OBSERVATION_RES_TYPE = "Observation";
040        private final SearchSession mySession;
041        private final FhirContext myFhirContext;
042        private final StorageSettings myStorageSettings;
043        private final ISearchParamRegistry mySearchParamRegistry;
044        private final ExtendedHSearchSearchBuilder myExtendedHSearchSearchBuilder = new ExtendedHSearchSearchBuilder();
045
046        public LastNOperation(
047                        SearchSession theSession,
048                        FhirContext theFhirContext,
049                        StorageSettings theStorageSettings,
050                        ISearchParamRegistry theSearchParamRegistry) {
051                mySession = theSession;
052                myFhirContext = theFhirContext;
053                myStorageSettings = theStorageSettings;
054                mySearchParamRegistry = theSearchParamRegistry;
055        }
056
057        public List<Long> executeLastN(SearchParameterMap theParams, Integer theMaximumResults) {
058                boolean lastNGroupedBySubject = isLastNGroupedBySubject(theParams);
059                LastNAggregation lastNAggregation =
060                                new LastNAggregation(getLastNMaxParamValue(theParams), lastNGroupedBySubject);
061                AggregationKey<JsonObject> observationsByCodeKey = AggregationKey.of("lastN_aggregation");
062
063                SearchResult<ResourceTable> result = mySession
064                                .search(ResourceTable.class)
065                                .extension(ElasticsearchExtension.get())
066                                .where(f -> f.bool(b -> {
067                                        // Must match observation type
068                                        b.must(f.match().field("myResourceType").matching(OBSERVATION_RES_TYPE));
069                                        ExtendedHSearchClauseBuilder builder =
070                                                        new ExtendedHSearchClauseBuilder(myFhirContext, myStorageSettings, b, f);
071                                        ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams params =
072                                                        new ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams();
073                                        params.setResourceType(OBSERVATION_RES_TYPE)
074                                                        .setSearchParameterMap(theParams.clone())
075                                                        .setSearchParamRegistry(mySearchParamRegistry);
076                                        myExtendedHSearchSearchBuilder.addAndConsumeAdvancedQueryClauses(builder, params);
077                                }))
078                                .aggregation(observationsByCodeKey, f -> f.fromJson(lastNAggregation.toAggregation()))
079                                .fetch(0);
080
081                JsonObject resultAggregation = result.aggregation(observationsByCodeKey);
082                List<Long> pidList = lastNAggregation.extractResourceIds(resultAggregation);
083                if (theMaximumResults != null && theMaximumResults <= pidList.size()) {
084                        return pidList.subList(0, theMaximumResults);
085                }
086                return pidList;
087        }
088
089        private boolean isLastNGroupedBySubject(SearchParameterMap theParams) {
090                String patientParamName = LastNParameterHelper.getPatientParamName(myFhirContext);
091                String subjectParamName = LastNParameterHelper.getSubjectParamName(myFhirContext);
092                return theParams.containsKey(patientParamName) || theParams.containsKey(subjectParamName);
093        }
094
095        private int getLastNMaxParamValue(SearchParameterMap theParams) {
096                return Objects.isNull(theParams.getLastNMax()) ? 1 : theParams.getLastNMax();
097        }
098}