001/* 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2023 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; 021 022import ca.uhn.fhir.i18n.Msg; 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.dao.JpaPid; 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.CacheControlDirective; 032import ca.uhn.fhir.rest.api.Constants; 033import ca.uhn.fhir.rest.api.SortOrderEnum; 034import ca.uhn.fhir.rest.api.SortSpec; 035import ca.uhn.fhir.rest.api.server.IBundleProvider; 036import ca.uhn.fhir.rest.api.server.RequestDetails; 037import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; 038import ca.uhn.fhir.rest.param.ReferenceOrListParam; 039import ca.uhn.fhir.rest.param.ReferenceParam; 040import org.hl7.fhir.instance.model.api.IBaseResource; 041import org.hl7.fhir.r4.model.Observation; 042import org.springframework.beans.factory.annotation.Autowired; 043import org.springframework.transaction.support.TransactionTemplate; 044 045import java.util.ArrayList; 046import java.util.Date; 047import java.util.List; 048import java.util.TreeMap; 049import javax.persistence.EntityManager; 050import javax.persistence.PersistenceContext; 051import javax.persistence.PersistenceContextType; 052import javax.servlet.http.HttpServletResponse; 053 054public class JpaResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> 055 implements IFhirResourceDaoObservation<T> { 056 057 @PersistenceContext(type = PersistenceContextType.TRANSACTION) 058 protected EntityManager myEntityManager; 059 060 @Autowired 061 ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc; 062 063 @Autowired 064 private IRequestPartitionHelperSvc myRequestPartitionHelperService; 065 066 @Override 067 public IBundleProvider observationsLastN( 068 SearchParameterMap theSearchParameterMap, 069 RequestDetails theRequestDetails, 070 HttpServletResponse theServletResponse) { 071 updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); 072 073 RequestPartitionId requestPartitionId = 074 myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType( 075 theRequestDetails, getResourceName(), theSearchParameterMap, null); 076 return mySearchCoordinatorSvc.registerSearch( 077 this, 078 theSearchParameterMap, 079 getResourceName(), 080 new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), 081 theRequestDetails, 082 requestPartitionId); 083 } 084 085 private String getEffectiveParamName() { 086 return Observation.SP_DATE; 087 } 088 089 private String getCodeParamName() { 090 return Observation.SP_CODE; 091 } 092 093 private String getSubjectParamName() { 094 return Observation.SP_SUBJECT; 095 } 096 097 private String getPatientParamName() { 098 return Observation.SP_PATIENT; 099 } 100 101 @Override 102 public ResourceTable updateEntity( 103 RequestDetails theRequest, 104 IBaseResource theResource, 105 IBasePersistedResource theEntity, 106 Date theDeletedTimestampOrNull, 107 boolean thePerformIndexing, 108 boolean theUpdateVersion, 109 TransactionDetails theTransactionDetails, 110 boolean theForceUpdate, 111 boolean theCreateNewHistoryEntry) { 112 return updateObservationEntity( 113 theRequest, 114 theResource, 115 theEntity, 116 theDeletedTimestampOrNull, 117 thePerformIndexing, 118 theUpdateVersion, 119 theTransactionDetails, 120 theForceUpdate, 121 theCreateNewHistoryEntry); 122 } 123 124 protected ResourceTable updateObservationEntity( 125 RequestDetails theRequest, 126 IBaseResource theResource, 127 IBasePersistedResource theEntity, 128 Date theDeletedTimestampOrNull, 129 boolean thePerformIndexing, 130 boolean theUpdateVersion, 131 TransactionDetails theTransactionDetails, 132 boolean theForceUpdate, 133 boolean theCreateNewHistoryEntry) { 134 ResourceTable retVal = super.updateEntity( 135 theRequest, 136 theResource, 137 theEntity, 138 theDeletedTimestampOrNull, 139 thePerformIndexing, 140 theUpdateVersion, 141 theTransactionDetails, 142 theForceUpdate, 143 theCreateNewHistoryEntry); 144 145 if (getStorageSettings().isLastNEnabled()) { 146 if (!retVal.isUnchangedInCurrentOperation()) { 147 if (retVal.getDeleted() == null) { 148 // Update indexes here for LastN operation. 149 myObservationLastNIndexPersistSvc.indexObservation(theResource); 150 } else { 151 myObservationLastNIndexPersistSvc.deleteObservationIndex(theEntity); 152 } 153 } 154 } 155 156 return retVal; 157 } 158 159 protected void updateSearchParamsForLastn( 160 SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) { 161 if (!isPagingProviderDatabaseBacked(theRequestDetails)) { 162 theSearchParameterMap.setLoadSynchronous(true); 163 } 164 165 theSearchParameterMap.setLastN(true); 166 SortSpec effectiveDtm = new SortSpec(getEffectiveParamName()).setOrder(SortOrderEnum.DESC); 167 SortSpec observationCode = 168 new SortSpec(getCodeParamName()).setOrder(SortOrderEnum.ASC).setChain(effectiveDtm); 169 if (theSearchParameterMap.containsKey(getSubjectParamName()) 170 || theSearchParameterMap.containsKey(getPatientParamName())) { 171 172 new TransactionTemplate(myPlatformTransactionManager) 173 .executeWithoutResult( 174 tx -> fixSubjectParamsOrderForLastn(theSearchParameterMap, theRequestDetails)); 175 176 theSearchParameterMap.setSort(new SortSpec(getSubjectParamName()) 177 .setOrder(SortOrderEnum.ASC) 178 .setChain(observationCode)); 179 } else { 180 theSearchParameterMap.setSort(observationCode); 181 } 182 } 183 184 private void fixSubjectParamsOrderForLastn( 185 SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) { 186 // Need to ensure that the patient/subject parameters are sorted in the SearchParameterMap to ensure correct 187 // ordering of 188 // the output. The reason for this is that observations are indexed by patient/subject forced ID, but then 189 // ordered in the 190 // final result set by subject/patient resource PID. 191 TreeMap<Long, IQueryParameterType> orderedSubjectReferenceMap = new TreeMap<>(); 192 if (theSearchParameterMap.containsKey(getSubjectParamName())) { 193 194 RequestPartitionId requestPartitionId = 195 myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType( 196 theRequestDetails, getResourceName(), theSearchParameterMap, null); 197 198 List<List<IQueryParameterType>> patientParams = new ArrayList<>(); 199 if (theSearchParameterMap.get(getPatientParamName()) != null) { 200 patientParams.addAll(theSearchParameterMap.get(getPatientParamName())); 201 } 202 if (theSearchParameterMap.get(getSubjectParamName()) != null) { 203 patientParams.addAll(theSearchParameterMap.get(getSubjectParamName())); 204 } 205 206 for (List<? extends IQueryParameterType> nextPatientList : patientParams) { 207 for (IQueryParameterType nextOr : nextPatientList) { 208 if (nextOr instanceof ReferenceParam) { 209 ReferenceParam ref = (ReferenceParam) nextOr; 210 JpaPid pid = myIdHelperService.resolveResourcePersistentIds( 211 requestPartitionId, ref.getResourceType(), ref.getIdPart()); 212 orderedSubjectReferenceMap.put(pid.getId(), nextOr); 213 } else { 214 throw new IllegalArgumentException( 215 Msg.code(942) + "Invalid token type (expecting ReferenceParam): " + nextOr.getClass()); 216 } 217 } 218 } 219 220 theSearchParameterMap.remove(getSubjectParamName()); 221 theSearchParameterMap.remove(getPatientParamName()); 222 223 // Subject PIDs ordered - so create 'OR' list of subjects for lastN operation 224 ReferenceOrListParam orList = new ReferenceOrListParam(); 225 orderedSubjectReferenceMap 226 .keySet() 227 .forEach(key -> orList.addOr((ReferenceParam) orderedSubjectReferenceMap.get(key))); 228 theSearchParameterMap.add(getSubjectParamName(), orList); 229 } 230 } 231}