001/*- 002 * #%L 003 * HAPI FHIR JPA Server - International Patient Summary (IPS) 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.ips.jpa; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.context.RuntimeResourceDefinition; 024import ca.uhn.fhir.context.RuntimeSearchParam; 025import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 026import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 027import ca.uhn.fhir.jpa.ips.api.ISectionResourceSupplier; 028import ca.uhn.fhir.jpa.ips.api.IpsContext; 029import ca.uhn.fhir.jpa.ips.api.IpsSectionContext; 030import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 031import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; 032import ca.uhn.fhir.model.dstu2.resource.Observation; 033import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; 034import ca.uhn.fhir.rest.api.server.IBundleProvider; 035import ca.uhn.fhir.rest.api.server.RequestDetails; 036import ca.uhn.fhir.rest.param.ReferenceParam; 037import jakarta.annotation.Nonnull; 038import jakarta.annotation.Nullable; 039import org.hl7.fhir.instance.model.api.IBaseResource; 040import org.hl7.fhir.r4.model.Coverage; 041import org.thymeleaf.util.Validate; 042 043import java.util.ArrayList; 044import java.util.List; 045import java.util.Set; 046import java.util.stream.Collectors; 047 048public class JpaSectionResourceSupplier implements ISectionResourceSupplier { 049 public static final int CHUNK_SIZE = 10; 050 051 private final JpaSectionSearchStrategyCollection mySectionSearchStrategyCollection; 052 private final DaoRegistry myDaoRegistry; 053 private final FhirContext myFhirContext; 054 055 public JpaSectionResourceSupplier( 056 @Nonnull JpaSectionSearchStrategyCollection theSectionSearchStrategyCollection, 057 @Nonnull DaoRegistry theDaoRegistry, 058 @Nonnull FhirContext theFhirContext) { 059 Validate.notNull(theSectionSearchStrategyCollection, "theSectionSearchStrategyCollection must not be null"); 060 Validate.notNull(theDaoRegistry, "theDaoRegistry must not be null"); 061 Validate.notNull(theFhirContext, "theFhirContext must not be null"); 062 mySectionSearchStrategyCollection = theSectionSearchStrategyCollection; 063 myDaoRegistry = theDaoRegistry; 064 myFhirContext = theFhirContext; 065 } 066 067 @Nullable 068 @Override 069 public <T extends IBaseResource> List<ResourceEntry> fetchResourcesForSection( 070 IpsContext theIpsContext, IpsSectionContext<T> theIpsSectionContext, RequestDetails theRequestDetails) { 071 072 IJpaSectionSearchStrategy<T> searchStrategy = 073 mySectionSearchStrategyCollection.getSearchStrategy(theIpsSectionContext.getResourceType()); 074 075 SearchParameterMap searchParameterMap = new SearchParameterMap(); 076 077 String subjectSp = determinePatientCompartmentSearchParameterName(theIpsSectionContext.getResourceType()); 078 searchParameterMap.add(subjectSp, new ReferenceParam(theIpsContext.getSubjectId())); 079 080 searchStrategy.massageResourceSearch(theIpsSectionContext, searchParameterMap); 081 082 IFhirResourceDao<T> dao = myDaoRegistry.getResourceDao(theIpsSectionContext.getResourceType()); 083 IBundleProvider searchResult = dao.search(searchParameterMap, theRequestDetails); 084 085 List<ResourceEntry> retVal = null; 086 for (int startIndex = 0; ; startIndex += CHUNK_SIZE) { 087 int endIndex = startIndex + CHUNK_SIZE; 088 List<IBaseResource> resources = searchResult.getResources(startIndex, endIndex); 089 if (resources.isEmpty()) { 090 break; 091 } 092 093 for (IBaseResource next : resources) { 094 if (!next.getClass().isAssignableFrom(theIpsSectionContext.getResourceType()) 095 || searchStrategy.shouldInclude(theIpsSectionContext, (T) next)) { 096 if (retVal == null) { 097 retVal = new ArrayList<>(); 098 } 099 InclusionTypeEnum inclusionType = 100 ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next) == BundleEntrySearchModeEnum.INCLUDE 101 ? InclusionTypeEnum.SECONDARY_RESOURCE 102 : InclusionTypeEnum.PRIMARY_RESOURCE; 103 retVal.add(new ResourceEntry(next, inclusionType)); 104 } 105 } 106 } 107 108 return retVal; 109 } 110 111 private String determinePatientCompartmentSearchParameterName(Class<? extends IBaseResource> theResourceType) { 112 RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType); 113 Set<String> searchParams = resourceDef.getSearchParamsForCompartmentName("Patient").stream() 114 .map(RuntimeSearchParam::getName) 115 .collect(Collectors.toSet()); 116 // A few we prefer 117 if (searchParams.contains(Observation.SP_PATIENT)) { 118 return Observation.SP_PATIENT; 119 } 120 if (searchParams.contains(Observation.SP_SUBJECT)) { 121 return Observation.SP_SUBJECT; 122 } 123 if (searchParams.contains(Coverage.SP_BENEFICIARY)) { 124 return Observation.SP_SUBJECT; 125 } 126 return searchParams.iterator().next(); 127 } 128}