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        public static String cleanProvenanceSourceUriOrEmpty(String theProvenanceSourceUri) {
052                String sanitizedProvenance = defaultString(theProvenanceSourceUri);
053                return StringUtils.substringBefore(sanitizedProvenance, "#");
054        }
055
056        /**
057         * @since 8.2.0
058         */
059        public static String getSource(FhirContext theContext, IBaseResource theResource) {
060                return getSource(theContext, theResource.getMeta());
061        }
062
063        public static String getSource(FhirContext theContext, IBaseMetaType theMeta) {
064                if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
065                        return getSourceR4Plus(theContext, theMeta);
066                } else if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
067                        return getSourceDstu3((IBaseHasExtensions) theMeta);
068                } else {
069                        throw new UnsupportedOperationException(
070                                        Msg.code(1782) + MetaUtil.class.getSimpleName() + ".getSource() not supported on FHIR Version "
071                                                        + theContext.getVersion().getVersion());
072                }
073        }
074
075        private static String getSourceDstu3(IBaseHasExtensions theMeta) {
076                IBaseHasExtensions metaWithExtensions = theMeta;
077                List<? extends IBaseExtension<?, ?>> extensions = metaWithExtensions.getExtension();
078                for (IBaseExtension extension : extensions) {
079                        if (HapiExtensions.EXT_META_SOURCE.equals(extension.getUrl())) {
080                                IPrimitiveType<String> value = (IPrimitiveType<String>) extension.getValue();
081                                return value.getValueAsString();
082                        }
083                }
084                return null;
085        }
086
087        private static String getSourceR4Plus(FhirContext theFhirContext, IBaseMetaType theMeta) {
088                BaseRuntimeElementCompositeDefinition<?> elementDef =
089                                (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition(theMeta.getClass());
090                BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");
091                if (sourceChild == null) {
092                        return null;
093                }
094                List<IBase> sourceValues = sourceChild.getAccessor().getValues(theMeta);
095                String retVal = null;
096                if (sourceValues.size() > 0) {
097                        retVal = ((IPrimitiveType<?>) sourceValues.get(0)).getValueAsString();
098                }
099                return retVal;
100        }
101
102        public static <R extends IBaseResource> void populateResourceSource(
103                        FhirContext theFhirContext, String theProvenanceSourceUri, String theProvenanceRequestId, R theRetVal) {
104                String sourceString = cleanProvenanceSourceUriOrEmpty(theProvenanceSourceUri);
105                if (isNotBlank(theProvenanceRequestId)) {
106                        sourceString = sourceString + "#" + theProvenanceRequestId;
107                }
108
109                if (isNotBlank(sourceString)) {
110                        setSource(theFhirContext, theRetVal, sourceString);
111                }
112        }
113
114        /**
115         * Sets the value for <code>Resource.meta.source</code> for R4+ resources, and places the value in
116         * an extension on <code>Resource.meta</code>
117         * with the URL <code>http://hapifhir.io/fhir/StructureDefinition/resource-meta-source</code> for DSTU3.
118         *
119         * @param theContext  The FhirContext object
120         * @param theResource The resource to modify
121         * @param theValue    The source URI
122         * @see <a href="http://hl7.org/fhir/resource-definitions.html#Resource.meta">Meta.source</a>
123         */
124        @SuppressWarnings("unchecked")
125        public static void setSource(FhirContext theContext, IBaseResource theResource, String theValue) {
126                if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
127                        MetaUtil.setSource(theContext, theResource.getMeta(), theValue);
128                } else if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) {
129                        IBaseExtension<?, ?> sourceExtension = ((IBaseHasExtensions) theResource.getMeta()).addExtension();
130                        sourceExtension.setUrl(HapiExtensions.EXT_META_SOURCE);
131                        IPrimitiveType<String> value = (IPrimitiveType<String>)
132                                        theContext.getElementDefinition("uri").newInstance();
133                        value.setValue(theValue);
134                        sourceExtension.setValue(value);
135                } else {
136                        ourLog.debug(MetaUtil.class.getSimpleName() + ".setSource() not supported on FHIR Version "
137                                        + theContext.getVersion().getVersion());
138                }
139        }
140
141        public static void setSource(FhirContext theContext, IBaseMetaType theMeta, String theValue) {
142                BaseRuntimeElementCompositeDefinition<?> elementDef =
143                                (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theMeta.getClass());
144                BaseRuntimeChildDefinition sourceChild = elementDef.getChildByName("source");
145                List<IBase> sourceValues = sourceChild.getAccessor().getValues(theMeta);
146                IPrimitiveType<?> sourceElement;
147                if (sourceValues.size() > 0) {
148                        sourceElement = ((IPrimitiveType<?>) sourceValues.get(0));
149                } else {
150                        sourceElement =
151                                        (IPrimitiveType<?>) theContext.getElementDefinition("uri").newInstance();
152                        sourceChild.getMutator().setValue(theMeta, sourceElement);
153                }
154                sourceElement.setValueAsString(theValue);
155        }
156
157        public static Set<String> getAutoVersionReferencesAtPath(IBaseMetaType theMeta, String theResourceType) {
158                return ExtensionUtil.getExtensionPrimitiveValues(
159                                                theMeta, HapiExtensions.EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH)
160                                .stream()
161                                .map(path -> String.format("%s.%s", theResourceType, path))
162                                .collect(Collectors.toSet());
163        }
164}