001/*-
002 * #%L
003 * HAPI FHIR - Core Library
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.context.support;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.FhirVersionEnum;
024import ca.uhn.fhir.util.ILockable;
025import ca.uhn.fhir.util.ReflectionUtil;
026import jakarta.annotation.Nonnull;
027import jakarta.annotation.Nullable;
028import org.apache.commons.lang3.Validate;
029import org.hl7.fhir.instance.model.api.IBase;
030import org.hl7.fhir.instance.model.api.IBaseResource;
031import org.hl7.fhir.instance.model.api.IPrimitiveType;
032
033import java.util.Collections;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037import java.util.Optional;
038
039/**
040 * This class returns the vocabulary that is shipped with the base FHIR
041 * specification.
042 *
043 * Note that this class is version aware. For example, a request for
044 * <code>http://foo-codesystem|123</code> will only return a value if
045 * the built in resource if the version matches. Unversioned URLs
046 * should generally be used, and will return whatever version is
047 * present.
048 */
049public class DefaultProfileValidationSupport implements IValidationSupport {
050
051        private static final Map<FhirVersionEnum, IValidationSupport> ourImplementations =
052                        Collections.synchronizedMap(new HashMap<>());
053
054        /**
055         * Userdata key indicating the source package ID for this package
056         */
057        public static final String SOURCE_PACKAGE_ID =
058                        DefaultProfileValidationSupport.class.getName() + "_SOURCE_PACKAGE_ID";
059
060        private final FhirContext myCtx;
061        /**
062         * This module just delegates all calls to a concrete implementation which will
063         * be in this field. Which implementation gets used depends on the FHIR version.
064         */
065        private final IValidationSupport myDelegate;
066
067        private final Runnable myFlush;
068
069        /**
070         * Constructor
071         *
072         * @param theFhirContext The context to use
073         */
074        public DefaultProfileValidationSupport(@Nonnull FhirContext theFhirContext) {
075                Validate.notNull(theFhirContext, "FhirContext must not be null");
076                myCtx = theFhirContext;
077
078                IValidationSupport strategy;
079                synchronized (ourImplementations) {
080                        strategy = ourImplementations.get(theFhirContext.getVersion().getVersion());
081
082                        if (strategy == null) {
083                                if (theFhirContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R5)) {
084                                        /*
085                                         * I don't love that we use reflection here, but this class is in
086                                         * hapi-fhir-base, and the class we're creating is in
087                                         * hapi-fhir-validation. There are complicated dependency chains that
088                                         * make this hard to clean up. At some point it'd be nice to figure out
089                                         * a cleaner solution though.
090                                         */
091                                        strategy = ReflectionUtil.newInstance(
092                                                        "org.hl7.fhir.common.hapi.validation.support.DefaultProfileValidationSupportNpmStrategy",
093                                                        IValidationSupport.class,
094                                                        new Class[] {FhirContext.class},
095                                                        new Object[] {theFhirContext});
096                                        ((ILockable) strategy).lock();
097                                } else {
098                                        strategy = new DefaultProfileValidationSupportBundleStrategy(theFhirContext);
099                                }
100                                ourImplementations.put(theFhirContext.getVersion().getVersion(), strategy);
101                        }
102                }
103
104                myDelegate = strategy;
105                if (myDelegate instanceof DefaultProfileValidationSupportBundleStrategy) {
106                        myFlush = () -> ((DefaultProfileValidationSupportBundleStrategy) myDelegate).flush();
107                } else {
108                        myFlush = () -> {};
109                }
110        }
111
112        @Override
113        public String getName() {
114                return myCtx.getVersion().getVersion() + " FHIR Standard Profile Validation Support";
115        }
116
117        @Override
118        public List<IBaseResource> fetchAllConformanceResources() {
119                List<IBaseResource> retVal = myDelegate.fetchAllConformanceResources();
120                addPackageInformation(retVal);
121                return retVal;
122        }
123
124        @Override
125        public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
126                List<T> retVal = myDelegate.fetchAllStructureDefinitions();
127                addPackageInformation(retVal);
128                return retVal;
129        }
130
131        @Nullable
132        @Override
133        public <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
134                List<T> retVal = myDelegate.fetchAllNonBaseStructureDefinitions();
135                addPackageInformation(retVal);
136                return retVal;
137        }
138
139        @Override
140        public IBaseResource fetchCodeSystem(String theSystem) {
141                IBaseResource retVal = myDelegate.fetchCodeSystem(theSystem);
142                addPackageInformation(retVal);
143                return retVal;
144        }
145
146        @Override
147        public IBaseResource fetchStructureDefinition(String theUrl) {
148                IBaseResource retVal = myDelegate.fetchStructureDefinition(theUrl);
149                addPackageInformation(retVal);
150                return retVal;
151        }
152
153        @Override
154        public IBaseResource fetchValueSet(String theUrl) {
155                IBaseResource retVal = myDelegate.fetchValueSet(theUrl);
156                addPackageInformation(retVal);
157                return retVal;
158        }
159
160        public void flush() {
161                myFlush.run();
162        }
163
164        @Override
165        public FhirContext getFhirContext() {
166                return myCtx;
167        }
168
169        @Nullable
170        public static String getConformanceResourceUrl(FhirContext theFhirContext, IBaseResource theResource) {
171                String urlValueString = null;
172                Optional<IBase> urlValue = theFhirContext
173                                .getResourceDefinition(theResource)
174                                .getChildByName("url")
175                                .getAccessor()
176                                .getFirstValueOrNull(theResource);
177                if (urlValue.isPresent()) {
178                        IPrimitiveType<?> urlValueType = (IPrimitiveType<?>) urlValue.get();
179                        urlValueString = urlValueType.getValueAsString();
180                }
181                return urlValueString;
182        }
183
184        private <T extends IBaseResource> void addPackageInformation(List<T> theResources) {
185                if (theResources != null) {
186                        theResources.forEach(this::addPackageInformation);
187                }
188        }
189
190        private void addPackageInformation(IBaseResource theResource) {
191                if (theResource != null) {
192                        String sourcePackageId = null;
193                        switch (myCtx.getVersion().getVersion()) {
194                                case DSTU2:
195                                case DSTU2_HL7ORG:
196                                        sourcePackageId = "hl7.fhir.r2.core";
197                                        break;
198                                case DSTU2_1:
199                                        return;
200                                case DSTU3:
201                                        sourcePackageId = "hl7.fhir.r3.core";
202                                        break;
203                                case R4:
204                                        sourcePackageId = "hl7.fhir.r4.core";
205                                        break;
206                                case R4B:
207                                        sourcePackageId = "hl7.fhir.r4b.core";
208                                        break;
209                                case R5:
210                                        sourcePackageId = "hl7.fhir.r5.core";
211                                        break;
212                        }
213
214                        Validate.notNull(
215                                        sourcePackageId,
216                                        "Don't know how to handle package ID: %s",
217                                        myCtx.getVersion().getVersion());
218
219                        theResource.setUserData(SOURCE_PACKAGE_ID, sourcePackageId);
220                }
221        }
222}