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(theResource, patientCompartmentSps, theSearchParamExtractor);
073        }
074
075        /**
076         * Extracts and returns an optional compartment of the received resource
077         * @param theResource source resource which compartment is extracted
078         * @param theCompartmentSps the RuntimeSearchParam list involving the searched compartment
079         * @param mySearchParamExtractor the ISearchParamExtractor to be used to extract the parameter values
080         * @return optional compartment of the received resource
081         */
082        public static Optional<String> getResourceCompartment(
083                        IBaseResource theResource,
084                        List<RuntimeSearchParam> theCompartmentSps,
085                        ISearchParamExtractor mySearchParamExtractor) {
086                return theCompartmentSps.stream()
087                                .flatMap(param -> Arrays.stream(BaseSearchParamExtractor.splitPathsR4(param.getPath())))
088                                .filter(StringUtils::isNotBlank)
089                                .map(path -> mySearchParamExtractor
090                                                .getPathValueExtractor(theResource, path)
091                                                .get())
092                                .filter(t -> !t.isEmpty())
093                                .map(t -> t.get(0))
094                                .filter(t -> t instanceof IBaseReference)
095                                .map(t -> (IBaseReference) t)
096                                .map(t -> t.getReferenceElement().getValue())
097                                .map(t -> new IdType(t).getIdPart())
098                                .filter(StringUtils::isNotBlank)
099                                .findFirst();
100        }
101
102        /**
103         * Returns a {@code RuntimeSearchParam} list with the parameters extracted from the received
104         * {@code RuntimeResourceDefinition}, which are of type REFERENCE and have a membership compartment
105         * for "Patient" resource
106         * @param resourceDef the RuntimeResourceDefinition providing the RuntimeSearchParam list
107         * @return the RuntimeSearchParam filtered list
108         */
109        @Nonnull
110        public static List<RuntimeSearchParam> getPatientCompartmentSearchParams(RuntimeResourceDefinition resourceDef) {
111                return resourceDef.getSearchParams().stream()
112                                .filter(param -> param.getParamType() == RestSearchParameterTypeEnum.REFERENCE)
113                                .filter(param -> param.getProvidesMembershipInCompartments() != null
114                                                && param.getProvidesMembershipInCompartments().contains("Patient"))
115                                .collect(Collectors.toList());
116        }
117}