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.Nullable;
027import org.hl7.fhir.instance.model.api.IBase;
028import org.hl7.fhir.instance.model.api.IBaseResource;
029import org.hl7.fhir.instance.model.api.IPrimitiveType;
030
031import java.util.Collections;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Map;
035import java.util.Optional;
036
037/**
038 * This class returns the vocabulary that is shipped with the base FHIR
039 * specification.
040 *
041 * Note that this class is version aware. For example, a request for
042 * <code>http://foo-codesystem|123</code> will only return a value if
043 * the built in resource if the version matches. Unversioned URLs
044 * should generally be used, and will return whatever version is
045 * present.
046 */
047public class DefaultProfileValidationSupport implements IValidationSupport {
048
049        private static final Map<FhirVersionEnum, IValidationSupport> ourImplementations =
050                        Collections.synchronizedMap(new HashMap<>());
051        private final FhirContext myCtx;
052        /**
053         * This module just delegates all calls to a concrete implementation which will
054         * be in this field. Which implementation gets used depends on the FHIR version.
055         */
056        private final IValidationSupport myDelegate;
057
058        private final Runnable myFlush;
059
060        /**
061         * Constructor
062         *
063         * @param theFhirContext The context to use
064         */
065        public DefaultProfileValidationSupport(FhirContext theFhirContext) {
066                myCtx = theFhirContext;
067
068                IValidationSupport strategy;
069                synchronized (ourImplementations) {
070                        strategy = ourImplementations.get(theFhirContext.getVersion().getVersion());
071
072                        if (strategy == null) {
073                                if (theFhirContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R5)) {
074                                        /*
075                                         * I don't love that we use reflection here, but this class is in
076                                         * hapi-fhir-base, and the class we're creating is in
077                                         * hapi-fhir-validation. There are complicated dependency chains that
078                                         * make this hard to clean up. At some point it'd be nice to figure out
079                                         * a cleaner solution though.
080                                         */
081                                        strategy = ReflectionUtil.newInstance(
082                                                        "org.hl7.fhir.common.hapi.validation.support.DefaultProfileValidationSupportNpmStrategy",
083                                                        IValidationSupport.class,
084                                                        new Class[] {FhirContext.class},
085                                                        new Object[] {theFhirContext});
086                                        ((ILockable) strategy).lock();
087                                } else {
088                                        strategy = new DefaultProfileValidationSupportBundleStrategy(theFhirContext);
089                                }
090                                ourImplementations.put(theFhirContext.getVersion().getVersion(), strategy);
091                        }
092                }
093
094                myDelegate = strategy;
095                if (myDelegate instanceof DefaultProfileValidationSupportBundleStrategy) {
096                        myFlush = () -> ((DefaultProfileValidationSupportBundleStrategy) myDelegate).flush();
097                } else {
098                        myFlush = () -> {};
099                }
100        }
101
102        @Override
103        public String getName() {
104                return myCtx.getVersion().getVersion() + " FHIR Standard Profile Validation Support";
105        }
106
107        @Override
108        public List<IBaseResource> fetchAllConformanceResources() {
109                return myDelegate.fetchAllConformanceResources();
110        }
111
112        @Override
113        public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
114                return myDelegate.fetchAllStructureDefinitions();
115        }
116
117        @Nullable
118        @Override
119        public <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
120                return myDelegate.fetchAllNonBaseStructureDefinitions();
121        }
122
123        @Override
124        public IBaseResource fetchCodeSystem(String theSystem) {
125                return myDelegate.fetchCodeSystem(theSystem);
126        }
127
128        @Override
129        public IBaseResource fetchStructureDefinition(String theUrl) {
130                return myDelegate.fetchStructureDefinition(theUrl);
131        }
132
133        @Override
134        public IBaseResource fetchValueSet(String theUrl) {
135                return myDelegate.fetchValueSet(theUrl);
136        }
137
138        public void flush() {
139                myFlush.run();
140        }
141
142        @Override
143        public FhirContext getFhirContext() {
144                return myCtx;
145        }
146
147        @Nullable
148        public static String getConformanceResourceUrl(FhirContext theFhirContext, IBaseResource theResource) {
149                String urlValueString = null;
150                Optional<IBase> urlValue = theFhirContext
151                                .getResourceDefinition(theResource)
152                                .getChildByName("url")
153                                .getAccessor()
154                                .getFirstValueOrNull(theResource);
155                if (urlValue.isPresent()) {
156                        IPrimitiveType<?> urlValueType = (IPrimitiveType<?>) urlValue.get();
157                        urlValueString = urlValueType.getValueAsString();
158                }
159                return urlValueString;
160        }
161}