001/*-
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2025 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.narrative2;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.FhirVersionEnum;
024import ca.uhn.fhir.util.BundleUtil;
025import ca.uhn.fhir.util.TerserUtil;
026import org.apache.commons.lang3.StringUtils;
027import org.apache.commons.lang3.tuple.Pair;
028import org.hl7.fhir.instance.model.api.IBase;
029import org.hl7.fhir.instance.model.api.IBaseBundle;
030import org.hl7.fhir.instance.model.api.IBaseCoding;
031import org.hl7.fhir.instance.model.api.IBaseResource;
032
033import java.util.List;
034import java.util.Objects;
035import java.util.stream.Stream;
036
037/**
038 * An instance of this class is added to the Thymeleaf context as a variable with
039 * name <code>"narrativeUtil"</code> and can be accessed from narrative templates.
040 *
041 * @since 7.0.0
042 */
043public class NarrativeGeneratorTemplateUtils {
044
045        public static final NarrativeGeneratorTemplateUtils INSTANCE = new NarrativeGeneratorTemplateUtils();
046
047        /**
048         * Given a Bundle as input, are any entries present with a given resource type
049         */
050        public boolean bundleHasEntriesWithResourceType(IBaseBundle theBaseBundle, String theResourceType) {
051                FhirVersionEnum fhirVersionEnum = theBaseBundle.getStructureFhirVersionEnum();
052                FhirContext ctx = FhirContext.forCached(fhirVersionEnum);
053                List<Pair<String, IBaseResource>> entryResources =
054                                BundleUtil.getBundleEntryUrlsAndResources(ctx, theBaseBundle);
055                return entryResources.stream()
056                                .map(Pair::getValue)
057                                .filter(Objects::nonNull)
058                                .anyMatch(t -> ctx.getResourceType(t).equals(theResourceType));
059        }
060
061        /**
062         * Returns if the bundle contains an entry resource whose `code` property contains a matching code system and code.
063         *
064         * @param theBundle the bundle to inspect
065         * @param theResourceType the resource type to look for
066         * @param theCodeSystem the code system to find
067         * @param theCode the code to find
068         * @return returns true if bundle has a resource that with matching code/code system
069         */
070        public boolean bundleHasEntriesWithCode(
071                        IBaseBundle theBundle, String theResourceType, String theCodeSystem, String theCode) {
072                FhirVersionEnum fhirVersionEnum = theBundle.getStructureFhirVersionEnum();
073                FhirContext ctx = FhirContext.forCached(fhirVersionEnum);
074
075                return getEntryResources(ctx, theBundle, theResourceType).anyMatch(t -> {
076                        List<IBase> codeList = TerserUtil.getFieldByFhirPath(ctx, "code.coding", t);
077                        return codeList.stream().anyMatch(m -> {
078                                IBaseCoding coding = (IBaseCoding) m;
079                                return StringUtils.equals(coding.getSystem(), theCodeSystem)
080                                                && StringUtils.equals(coding.getCode(), theCode);
081                        });
082                });
083        }
084
085        /**
086         * Gets a boolean indicating if at least one bundle entry resource's `code` property does NOT contain the
087         * code system/code specified.
088         *
089         * @param theBundle the bundle to inspect
090         * @param theResourceType the resource type to find
091         * @param theCodeSystem the code system to find
092         * @param theCode the code to find
093         * @return Returns true if one entry of resource type requested does not contain the specified code/system
094         */
095        public boolean bundleHasEntriesWithoutCode(
096                        IBaseBundle theBundle, String theResourceType, String theCodeSystem, String theCode) {
097
098                FhirVersionEnum fhirVersionEnum = theBundle.getStructureFhirVersionEnum();
099                FhirContext ctx = FhirContext.forCached(fhirVersionEnum);
100
101                return getEntryResources(ctx, theBundle, theResourceType).anyMatch(t -> {
102                        List<IBase> codeList = TerserUtil.getFieldByFhirPath(ctx, "code.coding", t);
103                        return codeList.stream().allMatch(m -> {
104                                IBaseCoding coding = (IBaseCoding) m;
105                                return !(StringUtils.equals(coding.getSystem(), theCodeSystem)
106                                                && StringUtils.equals(coding.getCode(), theCode));
107                        });
108                });
109        }
110
111        private Stream<IBaseResource> getEntryResources(
112                        FhirContext theContext, IBaseBundle theBundle, String theResourceType) {
113                List<Pair<String, IBaseResource>> entryResources =
114                                BundleUtil.getBundleEntryUrlsAndResources(theContext, theBundle);
115                return entryResources.stream()
116                                .map(Pair::getValue)
117                                .filter(Objects::nonNull)
118                                .filter(t -> theContext.getResourceType(t).equals(theResourceType));
119        }
120}