001package ca.uhn.fhir.jpa.dao;
002
003/*
004 * #%L
005 * HAPI FHIR JPA Server
006 * %%
007 * Copyright (C) 2014 - 2021 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.interceptor.model.RequestPartitionId;
024import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoObservation;
025import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
026import ca.uhn.fhir.jpa.model.entity.ResourceTable;
027import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
028import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
029import ca.uhn.fhir.model.api.IQueryParameterType;
030import ca.uhn.fhir.rest.api.SortOrderEnum;
031import ca.uhn.fhir.rest.api.SortSpec;
032import ca.uhn.fhir.rest.api.server.RequestDetails;
033import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
034import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
035import ca.uhn.fhir.rest.param.ReferenceParam;
036import org.hl7.fhir.instance.model.api.IBaseResource;
037import org.springframework.beans.factory.annotation.Autowired;
038import org.springframework.transaction.support.TransactionTemplate;
039
040import java.util.ArrayList;
041import java.util.Date;
042import java.util.List;
043import java.util.TreeMap;
044
045public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoObservation<T> {
046
047        @Autowired
048        ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
049
050        @Autowired
051        private IRequestPartitionHelperSvc myRequestPartitionHelperService;
052
053        protected ResourceTable updateObservationEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity,
054                                                                                                                                        Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
055                                                                                                                                        TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
056                ResourceTable retVal = super.updateEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion,
057                        theTransactionDetails, theForceUpdate, theCreateNewHistoryEntry);
058
059                if (getConfig().isLastNEnabled()) {
060                        if (!retVal.isUnchangedInCurrentOperation()) {
061                                if (retVal.getDeleted() == null) {
062                                        // Update indexes here for LastN operation.
063                                        myObservationLastNIndexPersistSvc.indexObservation(theResource);
064                                } else {
065                                        myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity);
066                                }
067                        }
068                }
069
070                return retVal;
071        }
072
073        protected void updateSearchParamsForLastn(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) {
074                if (!isPagingProviderDatabaseBacked(theRequestDetails)) {
075                        theSearchParameterMap.setLoadSynchronous(true);
076                }
077
078                theSearchParameterMap.setLastN(true);
079                SortSpec effectiveDtm = new SortSpec(getEffectiveParamName()).setOrder(SortOrderEnum.DESC);
080                SortSpec observationCode = new SortSpec(getCodeParamName()).setOrder(SortOrderEnum.ASC).setChain(effectiveDtm);
081                if (theSearchParameterMap.containsKey(getSubjectParamName()) || theSearchParameterMap.containsKey(getPatientParamName())) {
082
083                        new TransactionTemplate(myPlatformTransactionManager).executeWithoutResult(tx -> fixSubjectParamsOrderForLastn(theSearchParameterMap, theRequestDetails));
084
085                        theSearchParameterMap.setSort(new SortSpec(getSubjectParamName()).setOrder(SortOrderEnum.ASC).setChain(observationCode));
086                } else {
087                        theSearchParameterMap.setSort(observationCode);
088                }
089        }
090
091        private void fixSubjectParamsOrderForLastn(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) {
092                // Need to ensure that the patient/subject parameters are sorted in the SearchParameterMap to ensure correct ordering of
093                // the output. The reason for this is that observations are indexed by patient/subject forced ID, but then ordered in the
094                // final result set by subject/patient resource PID.
095                TreeMap<Long, IQueryParameterType> orderedSubjectReferenceMap = new TreeMap<>();
096                if (theSearchParameterMap.containsKey(getSubjectParamName())) {
097
098                        RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
099
100                        List<List<IQueryParameterType>> patientParams = new ArrayList<>();
101                        if (theSearchParameterMap.get(getPatientParamName()) != null) {
102                                patientParams.addAll(theSearchParameterMap.get(getPatientParamName()));
103                        }
104                        if (theSearchParameterMap.get(getSubjectParamName()) != null) {
105                                patientParams.addAll(theSearchParameterMap.get(getSubjectParamName()));
106                        }
107
108                        for (List<? extends IQueryParameterType> nextPatientList : patientParams) {
109                                for (IQueryParameterType nextOr : nextPatientList) {
110                                        if (nextOr instanceof ReferenceParam) {
111                                                ReferenceParam ref = (ReferenceParam) nextOr;
112                                                ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(requestPartitionId, ref.getResourceType(), ref.getIdPart());
113                                                orderedSubjectReferenceMap.put(pid.getIdAsLong(), nextOr);
114                                        } else {
115                                                throw new IllegalArgumentException("Invalid token type (expecting ReferenceParam): " + nextOr.getClass());
116                                        }
117                                }
118                        }
119
120                        theSearchParameterMap.remove(getSubjectParamName());
121                        theSearchParameterMap.remove(getPatientParamName());
122                        for (Long subjectPid : orderedSubjectReferenceMap.keySet()) {
123                                theSearchParameterMap.add(getSubjectParamName(), orderedSubjectReferenceMap.get(subjectPid));
124                        }
125                }
126
127        }
128
129        abstract protected String getEffectiveParamName();
130
131        abstract protected String getCodeParamName();
132
133        abstract protected String getSubjectParamName();
134
135        abstract protected String getPatientParamName();
136
137}