001/*-
002 * #%L
003 * HAPI FHIR Storage api
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.util;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.RuntimeResourceDefinition;
024import ca.uhn.fhir.context.RuntimeSearchParam;
025import ca.uhn.fhir.i18n.Msg;
026import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor;
027import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
028import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
029import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
030import jakarta.annotation.Nonnull;
031import org.apache.commons.lang3.StringUtils;
032import org.hl7.fhir.instance.model.api.IBaseReference;
033import org.hl7.fhir.instance.model.api.IBaseResource;
034import org.hl7.fhir.r4.model.IdType;
035
036import java.util.Arrays;
037import java.util.List;
038import java.util.Optional;
039import java.util.stream.Collectors;
040
041import static org.apache.commons.lang3.StringUtils.isBlank;
042
043public class ResourceCompartmentUtil {
044
045        /**
046         * Extract, if exists, the patient compartment identity of the received resource.
047         * It must be invoked in patient compartment mode.
048         * @param theResource             the resource to which extract the patient compartment identity
049         * @param theFhirContext          the active FhirContext
050         * @param theSearchParamExtractor the configured search parameter extractor
051         * @return the optional patient compartment identifier
052         * @throws MethodNotAllowedException if received resource is of type "Patient" and ID is not assigned.
053         */
054        public static Optional<String> getPatientCompartmentIdentity(
055                        IBaseResource theResource, FhirContext theFhirContext, ISearchParamExtractor theSearchParamExtractor) {
056                RuntimeResourceDefinition resourceDef = theFhirContext.getResourceDefinition(theResource);
057                List<RuntimeSearchParam> patientCompartmentSps =
058                                ResourceCompartmentUtil.getPatientCompartmentSearchParams(resourceDef);
059                if (patientCompartmentSps.isEmpty()) {
060                        return Optional.empty();
061                }
062
063                if (resourceDef.getName().equals("Patient")) {
064                        String compartmentIdentity = theResource.getIdElement().getIdPart();
065                        if (isBlank(compartmentIdentity)) {
066                                throw new MethodNotAllowedException(
067                                                Msg.code(2475) + "Patient resource IDs must be client-assigned in patient compartment mode");
068                        }
069                        return Optional.of(compartmentIdentity);
070                }
071
072                return getResourceCompartment("Patient", theResource, patientCompartmentSps, theSearchParamExtractor);
073        }
074
075        /**
076         * Extracts and returns an optional compartment of the received resource
077         * @param theCompartmentName     the name of the compartment
078         * @param theResource            source resource which compartment is extracted
079         * @param theCompartmentSps      the RuntimeSearchParam list involving the searched compartment
080         * @param mySearchParamExtractor the ISearchParamExtractor to be used to extract the parameter values
081         * @return optional compartment of the received resource
082         */
083        public static Optional<String> getResourceCompartment(
084                        String theCompartmentName,
085                        IBaseResource theResource,
086                        List<RuntimeSearchParam> theCompartmentSps,
087                        ISearchParamExtractor mySearchParamExtractor) {
088                // TODO KHS consolidate with FhirTerser.getCompartmentOwnersForResource()
089                return theCompartmentSps.stream()
090                                .flatMap(param -> Arrays.stream(BaseSearchParamExtractor.splitPathsR4(param.getPath())))
091                                .filter(StringUtils::isNotBlank)
092                                .map(path -> mySearchParamExtractor
093                                                .getPathValueExtractor(theResource, path)
094                                                .get())
095                                .filter(t -> !t.isEmpty())
096                                .map(t -> t.get(0))
097                                .filter(t -> t instanceof IBaseReference)
098                                .map(t -> (IBaseReference) t)
099                                .map(t -> t.getReferenceElement().getValue())
100                                .map(IdType::new)
101                                .filter(t -> theCompartmentName.equals(
102                                                t.getResourceType())) // assume the compartment name matches the resource type
103                                .map(IdType::getIdPart)
104                                .filter(StringUtils::isNotBlank)
105                                .findFirst();
106        }
107
108        /**
109         * Returns a {@code RuntimeSearchParam} list with the parameters extracted from the received
110         * {@code RuntimeResourceDefinition}, which are of type REFERENCE and have a membership compartment
111         * for "Patient" resource
112         * @param resourceDef the RuntimeResourceDefinition providing the RuntimeSearchParam list
113         * @return the RuntimeSearchParam filtered list
114         */
115        @Nonnull
116        public static List<RuntimeSearchParam> getPatientCompartmentSearchParams(RuntimeResourceDefinition resourceDef) {
117                return resourceDef.getSearchParams().stream()
118                                .filter(param -> param.getParamType() == RestSearchParameterTypeEnum.REFERENCE)
119                                .filter(param -> param.getProvidesMembershipInCompartments() != null
120                                                && param.getProvidesMembershipInCompartments().contains("Patient"))
121                                .collect(Collectors.toList());
122        }
123}