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.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 RequestPartitionId requestPartitionId = 072 myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType( 073 theRequestDetails, getResourceName(), theSearchParameterMap); 074 return mySearchCoordinatorSvc.registerSearch( 075 this, 076 theSearchParameterMap, 077 getResourceName(), 078 new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), 079 theRequestDetails, 080 requestPartitionId); 081 } 082 083 private String getEffectiveParamName() { 084 return Observation.SP_DATE; 085 } 086 087 private String getCodeParamName() { 088 return Observation.SP_CODE; 089 } 090 091 private String getSubjectParamName() { 092 return Observation.SP_SUBJECT; 093 } 094 095 private String getPatientParamName() { 096 return Observation.SP_PATIENT; 097 } 098 099 protected void updateSearchParamsForLastn( 100 SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) { 101 if (!isPagingProviderDatabaseBacked(theRequestDetails)) { 102 theSearchParameterMap.setLoadSynchronous(true); 103 } 104 105 theSearchParameterMap.setLastN(true); 106 SortSpec effectiveDtm = new SortSpec(getEffectiveParamName()).setOrder(SortOrderEnum.DESC); 107 SortSpec observationCode = 108 new SortSpec(getCodeParamName()).setOrder(SortOrderEnum.ASC).setChain(effectiveDtm); 109 if (theSearchParameterMap.containsKey(getSubjectParamName()) 110 || theSearchParameterMap.containsKey(getPatientParamName())) { 111 112 new TransactionTemplate(myPlatformTransactionManager) 113 .executeWithoutResult( 114 tx -> fixSubjectParamsOrderForLastn(theSearchParameterMap, theRequestDetails)); 115 116 theSearchParameterMap.setSort(new SortSpec(getSubjectParamName()) 117 .setOrder(SortOrderEnum.ASC) 118 .setChain(observationCode)); 119 } else { 120 theSearchParameterMap.setSort(observationCode); 121 } 122 } 123 124 private void fixSubjectParamsOrderForLastn( 125 SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails) { 126 // Need to ensure that the patient/subject parameters are sorted in the SearchParameterMap to ensure correct 127 // ordering of 128 // the output. The reason for this is that observations are indexed by patient/subject forced ID, but then 129 // ordered in the 130 // final result set by subject/patient resource PID. 131 TreeMap<Long, IQueryParameterType> orderedSubjectReferenceMap = new TreeMap<>(); 132 if (theSearchParameterMap.containsKey(getSubjectParamName())) { 133 134 RequestPartitionId requestPartitionId = 135 myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType( 136 theRequestDetails, getResourceName(), theSearchParameterMap); 137 138 List<List<IQueryParameterType>> patientParams = new ArrayList<>(); 139 if (theSearchParameterMap.get(getPatientParamName()) != null) { 140 patientParams.addAll(theSearchParameterMap.get(getPatientParamName())); 141 } 142 if (theSearchParameterMap.get(getSubjectParamName()) != null) { 143 patientParams.addAll(theSearchParameterMap.get(getSubjectParamName())); 144 } 145 146 Map<IIdType, ReferenceParam> ids = new HashMap<>(); 147 for (List<? extends IQueryParameterType> nextPatientList : patientParams) { 148 for (IQueryParameterType nextOr : nextPatientList) { 149 if (nextOr instanceof ReferenceParam) { 150 ReferenceParam ref = (ReferenceParam) nextOr; 151 IIdType id = myFhirContext.getVersion().newIdType(); 152 id.setParts(null, ref.getResourceType(), ref.getIdPart(), null); 153 ids.put(id, ref); 154 } else { 155 throw new IllegalArgumentException( 156 Msg.code(942) + "Invalid token type (expecting ReferenceParam): " + nextOr.getClass()); 157 } 158 } 159 } 160 161 Map<IIdType, IResourceLookup<JpaPid>> resolvedIds = myIdHelperService.resolveResourceIdentities( 162 requestPartitionId, 163 ids.keySet(), 164 ResolveIdentityMode.includeDeleted().cacheOk()); 165 for (Map.Entry<IIdType, ReferenceParam> entry : ids.entrySet()) { 166 IResourceLookup<JpaPid> lookup = resolvedIds.get(entry.getKey()); 167 orderedSubjectReferenceMap.put(lookup.getPersistentId().getId(), entry.getValue()); 168 } 169 170 theSearchParameterMap.remove(getSubjectParamName()); 171 theSearchParameterMap.remove(getPatientParamName()); 172 173 // Subject PIDs ordered - so create 'OR' list of subjects for lastN operation 174 ReferenceOrListParam orList = new ReferenceOrListParam(); 175 orderedSubjectReferenceMap 176 .keySet() 177 .forEach(key -> orList.addOr((ReferenceParam) orderedSubjectReferenceMap.get(key))); 178 theSearchParameterMap.add(getSubjectParamName(), orList); 179 } 180 } 181}