001package org.hl7.fhir.common.hapi.validation.support;
002
003import ca.uhn.fhir.context.FhirContext;
004import ca.uhn.fhir.context.RuntimeResourceDefinition;
005import ca.uhn.fhir.context.support.IValidationSupport;
006import ca.uhn.fhir.context.support.ValidationSupportContext;
007import org.apache.commons.compress.utils.Sets;
008import org.apache.commons.lang3.Validate;
009import org.hl7.fhir.instance.model.api.IBase;
010import org.hl7.fhir.instance.model.api.IBaseResource;
011import org.hl7.fhir.instance.model.api.IPrimitiveType;
012import org.hl7.fhir.r4.model.CodeSystem;
013import org.hl7.fhir.r4.model.StructureDefinition;
014import org.hl7.fhir.r4.model.ValueSet;
015
016import javax.annotation.Nonnull;
017import java.util.ArrayList;
018import java.util.Collection;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024import java.util.Set;
025
026import static org.apache.commons.lang3.StringUtils.isNotBlank;
027
028/**
029 * This class is an implementation of {@link IValidationSupport} which may be pre-populated
030 * with a collection of validation resources to be used by the validator.
031 */
032public class PrePopulatedValidationSupport extends BaseStaticResourceValidationSupport implements IValidationSupport {
033
034        private final Map<String, IBaseResource> myCodeSystems;
035        private final Map<String, IBaseResource> myStructureDefinitions;
036        private final Map<String, IBaseResource> myValueSets;
037
038        /**
039         * Constructor
040         */
041        public PrePopulatedValidationSupport(FhirContext theContext) {
042                this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>());
043        }
044
045
046        /**
047         * Constructor
048         *
049         * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and
050         *                                values are the resource itself.
051         * @param theValueSets            The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are
052         *                                the resource itself.
053         * @param theCodeSystems          The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are
054         *                                the resource itself.
055         */
056        public PrePopulatedValidationSupport(FhirContext theFhirContext, Map<String, IBaseResource> theStructureDefinitions, Map<String, IBaseResource> theValueSets, Map<String, IBaseResource> theCodeSystems) {
057                super(theFhirContext);
058                Validate.notNull(theFhirContext, "theFhirContext must not be null");
059                Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null");
060                Validate.notNull(theValueSets, "theValueSets must not be null");
061                Validate.notNull(theCodeSystems, "theCodeSystems must not be null");
062                myStructureDefinitions = theStructureDefinitions;
063                myValueSets = theValueSets;
064                myCodeSystems = theCodeSystems;
065        }
066
067        /**
068         * Add a new CodeSystem resource which will be available to the validator. Note that
069         * {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this
070         * value will be used as the logical URL.
071         * <p>
072         * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension),
073         * it will be stored in three ways:
074         * <ul>
075         * <li>Extension</li>
076         * <li>StructureDefinition/Extension</li>
077         * <li>http://hl7.org/StructureDefinition/Extension</li>
078         * </ul>
079         * </p>
080         */
081        public void addCodeSystem(IBaseResource theCodeSystem) {
082                Set<String> urls = processResourceAndReturnUrls(theCodeSystem, "CodeSystem");
083                addToMap(theCodeSystem, myCodeSystems, urls);
084        }
085
086        private Set<String> processResourceAndReturnUrls(IBaseResource theResource, String theResourceName) {
087                Validate.notNull(theResource, "the" + theResourceName + " must not be null");
088                RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theResource);
089                String actualResourceName = resourceDef.getName();
090                Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName);
091
092                Optional<IBase> urlValue = resourceDef.getChildByName("url").getAccessor().getFirstValueOrNull(theResource);
093                String url = urlValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null);
094
095                Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null");
096                Validate.notBlank(url, "the" + theResourceName + ".getUrl() must return a value");
097
098                String urlWithoutVersion;
099                int pipeIdx = url.indexOf('|');
100                if (pipeIdx != -1) {
101                        urlWithoutVersion = url.substring(0, pipeIdx);
102                } else {
103                        urlWithoutVersion = url;
104                }
105
106                HashSet<String> retVal = Sets.newHashSet(url, urlWithoutVersion);
107
108                Optional<IBase> versionValue = resourceDef.getChildByName("version").getAccessor().getFirstValueOrNull(theResource);
109                String version = versionValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null);
110                if (isNotBlank(version)) {
111                        retVal.add(urlWithoutVersion + "|" + version);
112                }
113
114                return retVal;
115        }
116
117        /**
118         * Add a new StructureDefinition resource which will be available to the validator. Note that
119         * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this
120         * value will be used as the logical URL.
121         * <p>
122         * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension),
123         * it will be stored in three ways:
124         * <ul>
125         * <li>Extension</li>
126         * <li>StructureDefinition/Extension</li>
127         * <li>http://hl7.org/StructureDefinition/Extension</li>
128         * </ul>
129         * </p>
130         */
131        public void addStructureDefinition(IBaseResource theStructureDefinition) {
132                Set<String> url = processResourceAndReturnUrls(theStructureDefinition, "StructureDefinition");
133                addToMap(theStructureDefinition, myStructureDefinitions, url);
134        }
135
136        private <T extends IBaseResource> void addToMap(T theResource, Map<String, T> theMap, Collection<String> theUrls) {
137                for (String urls : theUrls) {
138                        if (isNotBlank(urls)) {
139                                theMap.put(urls, theResource);
140
141                                int lastSlashIdx = urls.lastIndexOf('/');
142                                if (lastSlashIdx != -1) {
143                                        theMap.put(urls.substring(lastSlashIdx + 1), theResource);
144                                        int previousSlashIdx = urls.lastIndexOf('/', lastSlashIdx - 1);
145                                        if (previousSlashIdx != -1) {
146                                                theMap.put(urls.substring(previousSlashIdx + 1), theResource);
147                                        }
148                                }
149                        }
150                }
151        }
152
153        /**
154         * Add a new ValueSet resource which will be available to the validator. Note that
155         * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this
156         * value will be used as the logical URL.
157         * <p>
158         * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension),
159         * it will be stored in three ways:
160         * <ul>
161         * <li>Extension</li>
162         * <li>StructureDefinition/Extension</li>
163         * <li>http://hl7.org/StructureDefinition/Extension</li>
164         * </ul>
165         * </p>
166         */
167        public void addValueSet(IBaseResource theValueSet) {
168                Set<String> urls = processResourceAndReturnUrls(theValueSet, "ValueSet");
169                addToMap(theValueSet, myValueSets, urls);
170        }
171
172
173        /**
174         * @param theResource The resource. This method delegates to the type-specific methods (e.g. {@link #addCodeSystem(IBaseResource)})
175         *                    and will do nothing if the resource type is not supported by this class.
176         * @since 5.5.0
177         */
178        public void addResource(@Nonnull IBaseResource theResource) {
179                Validate.notNull(theResource, "theResource must not be null");
180
181                switch (getFhirContext().getResourceType(theResource)) {
182                        case "StructureDefinition":
183                                addStructureDefinition(theResource);
184                                break;
185                        case "CodeSystem":
186                                addCodeSystem(theResource);
187                                break;
188                        case "ValueSet":
189                                addValueSet(theResource);
190                                break;
191                }
192        }
193
194        @Override
195        public List<IBaseResource> fetchAllConformanceResources() {
196                ArrayList<IBaseResource> retVal = new ArrayList<>();
197                retVal.addAll(myCodeSystems.values());
198                retVal.addAll(myStructureDefinitions.values());
199                retVal.addAll(myValueSets.values());
200                return retVal;
201        }
202
203        @Override
204        public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
205                return toList(myStructureDefinitions);
206        }
207
208        @Override
209        public IBaseResource fetchCodeSystem(String theSystem) {
210                return myCodeSystems.get(theSystem);
211        }
212
213        @Override
214        public IBaseResource fetchValueSet(String theUri) {
215                return myValueSets.get(theUri);
216        }
217
218        @Override
219        public IBaseResource fetchStructureDefinition(String theUrl) {
220                return myStructureDefinitions.get(theUrl);
221        }
222
223        @Override
224        public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
225                return myCodeSystems.containsKey(theSystem);
226        }
227
228        @Override
229        public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
230                return myValueSets.containsKey(theValueSetUrl);
231        }
232}