001package ca.uhn.fhir.jpa.dao;
002
003/*
004 * #%L
005 * HAPI FHIR JPA Server
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.interceptor.model.RequestPartitionId;
025import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
026import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
027import ca.uhn.fhir.jpa.model.entity.ResourceTable;
028import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
029import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
030import ca.uhn.fhir.model.api.IQueryParameterType;
031import ca.uhn.fhir.rest.api.SortOrderEnum;
032import ca.uhn.fhir.rest.api.SortSpec;
033import ca.uhn.fhir.rest.api.server.RequestDetails;
034import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
035import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
036import ca.uhn.fhir.rest.param.ReferenceOrListParam;
037import ca.uhn.fhir.rest.param.ReferenceParam;
038import org.hl7.fhir.instance.model.api.IBaseResource;
039import org.springframework.beans.factory.annotation.Autowired;
040import org.springframework.transaction.support.TransactionTemplate;
041
042import java.util.ArrayList;
043import java.util.Date;
044import java.util.List;
045import java.util.TreeMap;
046
047public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoObservation<T> {
048
049        @Autowired
050        ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
051
052        @Autowired
053        private IRequestPartitionHelperSvc myRequestPartitionHelperService;
054
055        protected ResourceTable updateObservationEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity,
056                                                                                                                                        Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
057                                                                                                                                        TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
058                ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion,
059                        theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry);
060
061                if (getConfig().isLastNEnabled()) {
062                        if (!retVal.isUnchangedInCurrentOperation()) {
063                                if (retVal.getDeleted() == null) {
064                                        // Update indexes here for LastN operation.
065                                        myObservationLastNIndexPersistSvc.indexObservation(theResource);
066                                } else {
067                                        myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity);
068                                }
069                        }
070                }
071
072                return retVal;
073        }
074
075        protected void updateSearchParamsForLastn(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) {
076                if (!isPagingProviderDatabaseBacked(theRequestDetails)) {
077                        theSearchParameterMap.setLoadSynchronous(true);
078                }
079
080                theSearchParameterMap.setLastN(true);
081                SortSpec effectiveDtm = new SortSpec(getEffectiveParamName()).setOrder(SortOrderEnum.DESC);
082                SortSpec observationCode = new SortSpec(getCodeParamName()).setOrder(SortOrderEnum.ASC).setChain(effectiveDtm);
083                if (theSearchParameterMap.containsKey(getSubjectParamName()) || theSearchParameterMap.containsKey(getPatientParamName())) {
084
085                        new TransactionTemplate(myPlatformTransactionManager).executeWithoutResult(tx -> fixSubjectParamsOrderForLastn(theSearchParameterMap, theRequestDetails));
086
087                        theSearchParameterMap.setSort(new SortSpec(getSubjectParamName()).setOrder(SortOrderEnum.ASC).setChain(observationCode));
088                } else {
089                        theSearchParameterMap.setSort(observationCode);
090                }
091        }
092
093        private void fixSubjectParamsOrderForLastn(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) {
094                // Need to ensure that the patient/subject parameters are sorted in the SearchParameterMap to ensure correct ordering of
095                // the output. The reason for this is that observations are indexed by patient/subject forced ID, but then ordered in the
096                // final result set by subject/patient resource PID.
097                TreeMap<Long, IQueryParameterType> orderedSubjectReferenceMap = new TreeMap<>();
098                if (theSearchParameterMap.containsKey(getSubjectParamName())) {
099
100                        RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
101
102                        List<List<IQueryParameterType>> patientParams = new ArrayList<>();
103                        if (theSearchParameterMap.get(getPatientParamName()) != null) {
104                                patientParams.addAll(theSearchParameterMap.get(getPatientParamName()));
105                        }
106                        if (theSearchParameterMap.get(getSubjectParamName()) != null) {
107                                patientParams.addAll(theSearchParameterMap.get(getSubjectParamName()));
108                        }
109
110                        for (List<? extends IQueryParameterType> nextPatientList : patientParams) {
111                                for (IQueryParameterType nextOr : nextPatientList) {
112                                        if (nextOr instanceof ReferenceParam) {
113                                                ReferenceParam ref = (ReferenceParam) nextOr;
114                                                ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, ref.getResourceType(), ref.getIdPart());
115                                                orderedSubjectReferenceMap.put(pid.getIdAsLong(), nextOr);
116                                        } else {
117                                                throw new IllegalArgumentException(Msg.code(942) + "Invalid token type (expecting ReferenceParam): " + nextOr.getClass());
118                                        }
119                                }
120                        }
121
122                        theSearchParameterMap.remove(getSubjectParamName());
123                        theSearchParameterMap.remove(getPatientParamName());
124
125                        // Subject PIDs ordered - so create 'OR' list of subjects for lastN operation
126                        ReferenceOrListParam orList = new ReferenceOrListParam();
127                        orderedSubjectReferenceMap.keySet().forEach(key -> orList.addOr((ReferenceParam) orderedSubjectReferenceMap.get(key)));
128                        theSearchParameterMap.add(getSubjectParamName(), orList);
129                }
130
131        }
132
133        abstract protected String getEffectiveParamName();
134
135        abstract protected String getCodeParamName();
136
137        abstract protected String getSubjectParamName();
138
139        abstract protected String getPatientParamName();
140
141}