
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}