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; 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.dao.JpaPid; 026import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; 027import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 028import ca.uhn.fhir.model.api.IQueryParameterType; 029import ca.uhn.fhir.rest.api.CacheControlDirective; 030import ca.uhn.fhir.rest.api.Constants; 031import ca.uhn.fhir.rest.api.SortOrderEnum; 032import ca.uhn.fhir.rest.api.SortSpec; 033import ca.uhn.fhir.rest.api.server.IBundleProvider; 034import ca.uhn.fhir.rest.api.server.RequestDetails; 035import ca.uhn.fhir.rest.param.ReferenceOrListParam; 036import ca.uhn.fhir.rest.param.ReferenceParam; 037import jakarta.persistence.EntityManager; 038import jakarta.persistence.PersistenceContext; 039import jakarta.persistence.PersistenceContextType; 040import jakarta.servlet.http.HttpServletResponse; 041import org.hl7.fhir.instance.model.api.IBaseResource; 042import org.hl7.fhir.r4.model.Observation; 043import org.springframework.beans.factory.annotation.Autowired; 044import org.springframework.transaction.support.TransactionTemplate; 045 046import java.util.ArrayList; 047import java.util.List; 048import java.util.TreeMap; 049 050public class JpaResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> 051 implements IFhirResourceDaoObservation<T> { 052 053 @PersistenceContext(type = PersistenceContextType.TRANSACTION) 054 protected EntityManager myEntityManager; 055 056 @Autowired 057 private IRequestPartitionHelperSvc myRequestPartitionHelperService; 058 059 @Override 060 public IBundleProvider observationsLastN( 061 SearchParameterMap theSearchParameterMap, 062 RequestDetails theRequestDetails, 063 HttpServletResponse theServletResponse) { 064 updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); 065 066 RequestPartitionId requestPartitionId = 067 myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType( 068 theRequestDetails, getResourceName(), theSearchParameterMap); 069 return mySearchCoordinatorSvc.registerSearch( 070 this, 071 theSearchParameterMap, 072 getResourceName(), 073 new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), 074 theRequestDetails, 075 requestPartitionId); 076 } 077 078 private String getEffectiveParamName() { 079 return Observation.SP_DATE; 080 } 081 082 private String getCodeParamName() { 083 return Observation.SP_CODE; 084 } 085 086 private String getSubjectParamName() { 087 return Observation.SP_SUBJECT; 088 } 089 090 private String getPatientParamName() { 091 return Observation.SP_PATIENT; 092 } 093 094 protected void updateSearchParamsForLastn( 095 SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) { 096 if (!isPagingProviderDatabaseBacked(theRequestDetails)) { 097 theSearchParameterMap.setLoadSynchronous(true); 098 } 099 100 theSearchParameterMap.setLastN(true); 101 SortSpec effectiveDtm = new SortSpec(getEffectiveParamName()).setOrder(SortOrderEnum.DESC); 102 SortSpec observationCode = 103 new SortSpec(getCodeParamName()).setOrder(SortOrderEnum.ASC).setChain(effectiveDtm); 104 if (theSearchParameterMap.containsKey(getSubjectParamName()) 105 || theSearchParameterMap.containsKey(getPatientParamName())) { 106 107 new TransactionTemplate(myPlatformTransactionManager) 108 .executeWithoutResult( 109 tx -> fixSubjectParamsOrderForLastn(theSearchParameterMap, theRequestDetails)); 110 111 theSearchParameterMap.setSort(new SortSpec(getSubjectParamName()) 112 .setOrder(SortOrderEnum.ASC) 113 .setChain(observationCode)); 114 } else { 115 theSearchParameterMap.setSort(observationCode); 116 } 117 } 118 119 private void fixSubjectParamsOrderForLastn( 120 SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) { 121 // Need to ensure that the patient/subject parameters are sorted in the SearchParameterMap to ensure correct 122 // ordering of 123 // the output. The reason for this is that observations are indexed by patient/subject forced ID, but then 124 // ordered in the 125 // final result set by subject/patient resource PID. 126 TreeMap<Long, IQueryParameterType> orderedSubjectReferenceMap = new TreeMap<>(); 127 if (theSearchParameterMap.containsKey(getSubjectParamName())) { 128 129 RequestPartitionId requestPartitionId = 130 myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType( 131 theRequestDetails, getResourceName(), theSearchParameterMap); 132 133 List<List<IQueryParameterType>> patientParams = new ArrayList<>(); 134 if (theSearchParameterMap.get(getPatientParamName()) != null) { 135 patientParams.addAll(theSearchParameterMap.get(getPatientParamName())); 136 } 137 if (theSearchParameterMap.get(getSubjectParamName()) != null) { 138 patientParams.addAll(theSearchParameterMap.get(getSubjectParamName())); 139 } 140 141 for (List<? extends IQueryParameterType> nextPatientList : patientParams) { 142 for (IQueryParameterType nextOr : nextPatientList) { 143 if (nextOr instanceof ReferenceParam) { 144 ReferenceParam ref = (ReferenceParam) nextOr; 145 JpaPid pid = myIdHelperService.resolveResourcePersistentIds( 146 requestPartitionId, ref.getResourceType(), ref.getIdPart()); 147 orderedSubjectReferenceMap.put(pid.getId(), nextOr); 148 } else { 149 throw new IllegalArgumentException( 150 Msg.code(942) + "Invalid token type (expecting ReferenceParam): " + nextOr.getClass()); 151 } 152 } 153 } 154 155 theSearchParameterMap.remove(getSubjectParamName()); 156 theSearchParameterMap.remove(getPatientParamName()); 157 158 // Subject PIDs ordered - so create 'OR' list of subjects for lastN operation 159 ReferenceOrListParam orList = new ReferenceOrListParam(); 160 orderedSubjectReferenceMap 161 .keySet() 162 .forEach(key -> orList.addOr((ReferenceParam) orderedSubjectReferenceMap.get(key))); 163 theSearchParameterMap.add(getSubjectParamName(), orList); 164 } 165 } 166}