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