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}