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.searchparam.SearchParameterMap;
026import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper;
027import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
028import com.google.gson.JsonObject;
029import org.hibernate.search.backend.elasticsearch.ElasticsearchExtension;
030import org.hibernate.search.engine.search.aggregation.AggregationKey;
031import org.hibernate.search.engine.search.query.SearchResult;
032import org.hibernate.search.mapper.orm.session.SearchSession;
033
034import java.util.List;
035import java.util.Objects;
036
037public class LastNOperation {
038        public static final String OBSERVATION_RES_TYPE = "Observation";
039        private final SearchSession mySession;
040        private final FhirContext myFhirContext;
041        private final StorageSettings myStorageSettings;
042        private final ISearchParamRegistry mySearchParamRegistry;
043        private final ExtendedHSearchSearchBuilder myExtendedHSearchSearchBuilder = new ExtendedHSearchSearchBuilder();
044
045        public LastNOperation(
046                        SearchSession theSession,
047                        FhirContext theFhirContext,
048                        StorageSettings theStorageSettings,
049                        ISearchParamRegistry theSearchParamRegistry) {
050                mySession = theSession;
051                myFhirContext = theFhirContext;
052                myStorageSettings = theStorageSettings;
053                mySearchParamRegistry = theSearchParamRegistry;
054        }
055
056        public List<Long> executeLastN(SearchParameterMap theParams, Integer theMaximumResults) {
057                boolean lastNGroupedBySubject = isLastNGroupedBySubject(theParams);
058                LastNAggregation lastNAggregation =
059                                new LastNAggregation(getLastNMaxParamValue(theParams), lastNGroupedBySubject);
060                AggregationKey<JsonObject> observationsByCodeKey = AggregationKey.of("lastN_aggregation");
061
062                SearchResult<ResourceTable> result = mySession
063                                .search(ResourceTable.class)
064                                .extension(ElasticsearchExtension.get())
065                                .where(f -> f.bool(b -> {
066                                        // Must match observation type
067                                        b.must(f.match().field("myResourceType").matching(OBSERVATION_RES_TYPE));
068                                        ExtendedHSearchClauseBuilder builder =
069                                                        new ExtendedHSearchClauseBuilder(myFhirContext, myStorageSettings, b, f);
070                                        myExtendedHSearchSearchBuilder.addAndConsumeAdvancedQueryClauses(
071                                                        builder, OBSERVATION_RES_TYPE, theParams.clone(), mySearchParamRegistry);
072                                }))
073                                .aggregation(observationsByCodeKey, f -> f.fromJson(lastNAggregation.toAggregation()))
074                                .fetch(0);
075
076                JsonObject resultAggregation = result.aggregation(observationsByCodeKey);
077                List<Long> pidList = lastNAggregation.extractResourceIds(resultAggregation);
078                if (theMaximumResults != null && theMaximumResults <= pidList.size()) {
079                        return pidList.subList(0, theMaximumResults);
080                }
081                return pidList;
082        }
083
084        private boolean isLastNGroupedBySubject(SearchParameterMap theParams) {
085                String patientParamName = LastNParameterHelper.getPatientParamName(myFhirContext);
086                String subjectParamName = LastNParameterHelper.getSubjectParamName(myFhirContext);
087                return theParams.containsKey(patientParamName) || theParams.containsKey(subjectParamName);
088        }
089
090        private int getLastNMaxParamValue(SearchParameterMap theParams) {
091                return Objects.isNull(theParams.getLastNMax()) ? 1 : theParams.getLastNMax();
092        }
093}