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.util;
021
022import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
024import ca.uhn.fhir.context.FhirContext;
025import ca.uhn.fhir.context.FhirVersionEnum;
026import ca.uhn.fhir.i18n.Msg;
027import org.apache.commons.lang3.StringUtils;
028import org.hl7.fhir.instance.model.api.IBase;
029import org.hl7.fhir.instance.model.api.IBaseExtension;
030import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
031import org.hl7.fhir.instance.model.api.IBaseMetaType;
032import org.hl7.fhir.instance.model.api.IBaseResource;
033import org.hl7.fhir.instance.model.api.IPrimitiveType;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037import java.util.List;
038import java.util.Set;
039import java.util.stream.Collectors;
040
041import static org.apache.commons.lang3.StringUtils.defaultString;
042import static org.apache.commons.lang3.StringUtils.isNotBlank;
043
044public class MetaUtil {
045        private static final Logger ourLog = LoggerFactory.getLogger(MetaUtil.class);
046
047        private MetaUtil() {
048                // non-instantiable
049        }
050
051        /**
052         * Resource.meta.source may include a request ID in the format sourceURI#requestID
053         * This method extracts the source URI component from the meta.source string
054         *
055         * @param theProvenanceSourceUri the meta.source string
056         * @return the source URI component, or a blank String if empty/null
057         */
058        public static String extractSourceUriOrEmpty(String theProvenanceSourceUri) {
059                String sanitizedProvenance = defaultString(theProvenanceSourceUri);
060                return StringUtils.substringBefore(sanitizedProvenance, "#");
061        }
062
063        /**
064         * @since 8.2.0
065         */
066        public static String getSource(FhirContext theContext, IBaseResource theResource) {
067                return getSource(theContext, theResource.getMeta());
068        }
069
070        public static String getSource(FhirContext theContext, IBaseMetaType theMeta) {
071                if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
072                        return getSourceR4Plus(theContext, theMeta);
073                } else if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
074                        return getSourceDstu3((IBaseHasExtensions) theMeta);
075                } else {
076                        throw new UnsupportedOperationException(
077                                        Msg.code(1782) + MetaUtil.class.getSimpleName() + ".getSource() not supported on FHIR Version "
078                                                        + theContext.getVersion().getVersion());
079                }
080        }
081
082        private static String getSourceDstu3(IBaseHasExtensions theMeta) {
083                IBaseHasExtensions metaWithExtensions = theMeta;
084                List<? extends IBaseExtension<?, ?>> extensions = metaWithExtensions.getExtension();
085                for (IBaseExtension extension : extensions) {
086                        if (HapiExtensions.EXT_META_SOURCE.equals(extension.getUrl())) {
087                                IPrimitiveType<String> value = (IPrimitiveType<String>) extension.getValue();
088                                return value.getValueAsString();
089                        }
090                }
091                return null;
092        }
093
094        private static String getSourceR4Plus(FhirContext theFhirContext, IBaseMetaType theMeta) {
095                BaseRuntimeElementCompositeDefinition<?> elementDef =
096                                (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition(theMeta.getClass());
097                BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");
098                if (sourceChild == null) {
099                        return null;
100                }
101                List<IBase> sourceValues = sourceChild.getAccessor().getValues(theMeta);
102                String retVal = null;
103                if (sourceValues.size() > 0) {
104                        retVal = ((IPrimitiveType<?>) sourceValues.get(0)).getValueAsString();
105                }
106                return retVal;
107        }
108
109        public static <R extends IBaseResource> void populateResourceSource(
110                        FhirContext theFhirContext, String theProvenanceSourceUri, String theProvenanceRequestId, R theRetVal) {
111                String sourceString = extractSourceUriOrEmpty(theProvenanceSourceUri);
112
113                if (isNotBlank(theProvenanceRequestId)) {
114                        sourceString = sourceString + "#" + theProvenanceRequestId;
115                }
116
117                if (isNotBlank(sourceString)) {
118                        setSource(theFhirContext, theRetVal, sourceString);
119                }
120        }
121
122        /**
123         * Sets the value for <code>Resource.meta.source</code> for R4+ resources, and places the value in
124         * an extension on <code>Resource.meta</code>
125         * with the URL <code>http://hapifhir.io/fhir/StructureDefinition/resource-meta-source</code> for DSTU3.
126         *
127         * @param theContext  The FhirContext object
128         * @param theResource The resource to modify
129         * @param theValue    The source URI
130         * @see <a href="http://hl7.org/fhir/resource-definitions.html#Resource.meta">Meta.source</a>
131         */
132        @SuppressWarnings("unchecked")
133        public static void setSource(FhirContext theContext, IBaseResource theResource, String theValue) {
134                if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
135                        MetaUtil.setSource(theContext, theResource.getMeta(), theValue);
136                } else if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
137                        IBaseExtension<?, ?> sourceExtension = ((IBaseHasExtensions) theResource.getMeta()).addExtension();
138                        sourceExtension.setUrl(HapiExtensions.EXT_META_SOURCE);
139                        IPrimitiveType<String> value = (IPrimitiveType<String>)
140                                        theContext.getElementDefinition("uri").newInstance();
141                        value.setValue(theValue);
142                        sourceExtension.setValue(value);
143                } else {
144                        ourLog.debug(MetaUtil.class.getSimpleName() + ".setSource() not supported on FHIR Version "
145                                        + theContext.getVersion().getVersion());
146                }
147        }
148
149        public static void setSource(FhirContext theContext, IBaseMetaType theMeta, String theValue) {
150                BaseRuntimeElementCompositeDefinition<?> elementDef =
151                                (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theMeta.getClass());
152                BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");
153                List<IBase> sourceValues = sourceChild.getAccessor().getValues(theMeta);
154                IPrimitiveType<?> sourceElement;
155                if (sourceValues.size() > 0) {
156                        sourceElement = ((IPrimitiveType<?>) sourceValues.get(0));
157                } else {
158                        sourceElement =
159                                        (IPrimitiveType<?>) theContext.getElementDefinition("uri").newInstance();
160                        sourceChild.getMutator().setValue(theMeta, sourceElement);
161                }
162                sourceElement.setValueAsString(theValue);
163        }
164
165        public static Set<String> getAutoVersionReferencesAtPath(IBaseMetaType theMeta, String theResourceType) {
166                return ExtensionUtil.getExtensionPrimitiveValues(
167                                                theMeta, HapiExtensions.EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH)
168                                .stream()
169                                .map(path -> String.format("%s.%s", theResourceType, path))
170                                .collect(Collectors.toSet());
171        }
172}