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.lang3.Validate;
008import org.hl7.fhir.instance.model.api.IBase;
009import org.hl7.fhir.instance.model.api.IBaseResource;
010import org.hl7.fhir.instance.model.api.IPrimitiveType;
011import org.hl7.fhir.r4.model.CodeSystem;
012import org.hl7.fhir.r4.model.StructureDefinition;
013import org.hl7.fhir.r4.model.ValueSet;
014
015import java.util.ArrayList;
016import java.util.HashMap;
017import java.util.List;
018import java.util.Map;
019import java.util.Optional;
020
021import static org.apache.commons.lang3.StringUtils.isNotBlank;
022
023/**
024 * This class is an implementation of {@link IValidationSupport} which may be pre-populated
025 * with a collection of validation resources to be used by the validator.
026 */
027public class PrePopulatedValidationSupport extends BaseStaticResourceValidationSupport implements IValidationSupport {
028
029        private final FhirContext myFhirContext;
030        private final Map<String, IBaseResource> myCodeSystems;
031        private final Map<String, IBaseResource> myStructureDefinitions;
032        private final Map<String, IBaseResource> myValueSets;
033
034        /**
035         * Constructor
036         */
037        public PrePopulatedValidationSupport(FhirContext theContext) {
038                this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>());
039        }
040
041
042        /**
043         * Constructor
044         *
045         * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and
046         *                                values are the resource itself.
047         * @param theValueSets            The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are
048         *                                the resource itself.
049         * @param theCodeSystems          The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are
050         *                                the resource itself.
051         */
052        public PrePopulatedValidationSupport(FhirContext theFhirContext, Map<String, IBaseResource> theStructureDefinitions, Map<String, IBaseResource> theValueSets, Map<String, IBaseResource> theCodeSystems) {
053                super(theFhirContext);
054                Validate.notNull(theFhirContext, "theFhirContext must not be null");
055                Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null");
056                Validate.notNull(theValueSets, "theValueSets must not be null");
057                Validate.notNull(theCodeSystems, "theCodeSystems must not be null");
058                myFhirContext = theFhirContext;
059                myStructureDefinitions = theStructureDefinitions;
060                myValueSets = theValueSets;
061                myCodeSystems = theCodeSystems;
062        }
063
064        /**
065         * Add a new CodeSystem resource which will be available to the validator. Note that
066         * {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this
067         * value will be used as the logical URL.
068         * <p>
069         * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension),
070         * it will be stored in three ways:
071         * <ul>
072         * <li>Extension</li>
073         * <li>StructureDefinition/Extension</li>
074         * <li>http://hl7.org/StructureDefinition/Extension</li>
075         * </ul>
076         * </p>
077         */
078        public void addCodeSystem(IBaseResource theCodeSystem) {
079                String url = processResourceAndReturnUrl(theCodeSystem, "CodeSystem");
080                addToMap(theCodeSystem, myCodeSystems, url);
081        }
082
083        private String processResourceAndReturnUrl(IBaseResource theCodeSystem, String theResourceName) {
084                Validate.notNull(theCodeSystem, "the" + theResourceName + " must not be null");
085                RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theCodeSystem);
086                String actualResourceName = resourceDef.getName();
087                Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName);
088
089                Optional<IBase> urlValue = resourceDef.getChildByName("url").getAccessor().getFirstValueOrNull(theCodeSystem);
090                String url = urlValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null);
091
092                Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null");
093                Validate.notBlank(url, "the" + theResourceName + ".getUrl() must return a value");
094                return url;
095        }
096
097        /**
098         * Add a new StructureDefinition resource which will be available to the validator. Note that
099         * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this
100         * value will be used as the logical URL.
101         * <p>
102         * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension),
103         * it will be stored in three ways:
104         * <ul>
105         * <li>Extension</li>
106         * <li>StructureDefinition/Extension</li>
107         * <li>http://hl7.org/StructureDefinition/Extension</li>
108         * </ul>
109         * </p>
110         */
111        public void addStructureDefinition(IBaseResource theStructureDefinition) {
112                String url = processResourceAndReturnUrl(theStructureDefinition, "StructureDefinition");
113                addToMap(theStructureDefinition, myStructureDefinitions, url);
114        }
115
116        private <T extends IBaseResource> void addToMap(T theStructureDefinition, Map<String, T> map, String theUrl) {
117                if (isNotBlank(theUrl)) {
118                        map.put(theUrl, theStructureDefinition);
119
120                        int lastSlashIdx = theUrl.lastIndexOf('/');
121                        if (lastSlashIdx != -1) {
122                                map.put(theUrl.substring(lastSlashIdx + 1), theStructureDefinition);
123                                int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 1);
124                                if (previousSlashIdx != -1) {
125                                        map.put(theUrl.substring(previousSlashIdx + 1), theStructureDefinition);
126                                }
127                        }
128
129                }
130        }
131
132        /**
133         * Add a new ValueSet resource which will be available to the validator. Note that
134         * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this
135         * value will be used as the logical URL.
136         * <p>
137         * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension),
138         * it will be stored in three ways:
139         * <ul>
140         * <li>Extension</li>
141         * <li>StructureDefinition/Extension</li>
142         * <li>http://hl7.org/StructureDefinition/Extension</li>
143         * </ul>
144         * </p>
145         */
146        public void addValueSet(ValueSet theValueSet) {
147                String url = processResourceAndReturnUrl(theValueSet, "ValueSet");
148                addToMap(theValueSet, myValueSets, url);
149        }
150
151
152        @Override
153        public List<IBaseResource> fetchAllConformanceResources() {
154                ArrayList<IBaseResource> retVal = new ArrayList<>();
155                retVal.addAll(myCodeSystems.values());
156                retVal.addAll(myStructureDefinitions.values());
157                retVal.addAll(myValueSets.values());
158                return retVal;
159        }
160
161        @Override
162        public List<IBaseResource> fetchAllStructureDefinitions() {
163                return toList(myStructureDefinitions);
164        }
165
166        @Override
167        public IBaseResource fetchCodeSystem(String theSystem) {
168                return myCodeSystems.get(theSystem);
169        }
170
171        @Override
172        public IBaseResource fetchValueSet(String theUri) {
173                return myValueSets.get(theUri);
174        }
175
176        @Override
177        public IBaseResource fetchStructureDefinition(String theUrl) {
178                return myStructureDefinitions.get(theUrl);
179        }
180
181        @Override
182        public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
183                return myCodeSystems.containsKey(theSystem);
184        }
185
186        @Override
187        public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
188                return myValueSets.containsKey(theValueSetUrl);
189        }
190
191}