001package org.hl7.fhir.common.hapi.validation.support;
002
003import ca.uhn.fhir.context.ConfigurationException;
004import ca.uhn.fhir.context.FhirContext;
005import ca.uhn.fhir.context.FhirVersionEnum;
006import ca.uhn.fhir.context.support.ConceptValidationOptions;
007import ca.uhn.fhir.context.support.IValidationSupport;
008import ca.uhn.fhir.context.support.ValidationSupportContext;
009import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
010import org.apache.commons.lang3.Validate;
011import org.hl7.fhir.instance.model.api.IBaseResource;
012import org.hl7.fhir.instance.model.api.IPrimitiveType;
013
014import javax.annotation.Nonnull;
015import java.util.ArrayList;
016import java.util.HashSet;
017import java.util.List;
018import java.util.Set;
019
020import static org.apache.commons.lang3.StringUtils.isBlank;
021
022public class ValidationSupportChain implements IValidationSupport {
023
024        private List<IValidationSupport> myChain;
025
026        /**
027         * Constructor
028         */
029        public ValidationSupportChain() {
030                myChain = new ArrayList<>();
031        }
032
033        /**
034         * Constructor
035         */
036        public ValidationSupportChain(IValidationSupport... theValidationSupportModules) {
037                this();
038                for (IValidationSupport next : theValidationSupportModules) {
039                        if (next != null) {
040                                addValidationSupport(next);
041                        }
042                }
043        }
044
045        @Override
046        public void invalidateCaches() {
047                for (IValidationSupport next : myChain) {
048                        next.invalidateCaches();
049                }
050        }
051
052        @Override
053        public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
054                for (IValidationSupport next : myChain) {
055                        boolean retVal = next.isValueSetSupported(theValidationSupportContext, theValueSetUrl);
056                        if (retVal) {
057                                return true;
058                        }
059                }
060                return false;
061        }
062
063        @Override
064        public IBaseResource generateSnapshot(ValidationSupportContext theValidationSupportContext, IBaseResource theInput, String theUrl, String theWebUrl, String theProfileName) {
065                for (IValidationSupport next : myChain) {
066                        IBaseResource retVal = next.generateSnapshot(theValidationSupportContext, theInput, theUrl, theWebUrl, theProfileName);
067                        if (retVal != null) {
068                                return retVal;
069                        }
070                }
071                return null;
072        }
073
074        @Override
075        public FhirContext getFhirContext() {
076                if (myChain.size() == 0) {
077                        return null;
078                }
079                return myChain.get(0).getFhirContext();
080        }
081
082        /**
083         * Add a validation support module to the chain.
084         * <p>
085         * Note that this method is not thread-safe. All validation support modules should be added prior to use.
086         * </p>
087         *
088         * @param theValidationSupport The validation support. Must not be null, and must have a {@link #getFhirContext() FhirContext} that is configured for the same FHIR version as other entries in the chain.
089         */
090        public void addValidationSupport(IValidationSupport theValidationSupport) {
091                int index = myChain.size();
092                addValidationSupport(index, theValidationSupport);
093        }
094
095        /**
096         * Add a validation support module to the chain at the given index.
097         * <p>
098         * Note that this method is not thread-safe. All validation support modules should be added prior to use.
099         * </p>
100         *
101         * @param theIndex             The index to add to
102         * @param theValidationSupport The validation support. Must not be null, and must have a {@link #getFhirContext() FhirContext} that is configured for the same FHIR version as other entries in the chain.
103         */
104        public void addValidationSupport(int theIndex, IValidationSupport theValidationSupport) {
105                Validate.notNull(theValidationSupport, "theValidationSupport must not be null");
106
107                if (theValidationSupport.getFhirContext() == null) {
108                        String message = "Can not add validation support: getFhirContext() returns null";
109                        throw new ConfigurationException(message);
110                }
111
112                FhirContext existingFhirContext = getFhirContext();
113                if (existingFhirContext != null) {
114                        FhirVersionEnum newVersion = theValidationSupport.getFhirContext().getVersion().getVersion();
115                        FhirVersionEnum existingVersion = existingFhirContext.getVersion().getVersion();
116                        if (!existingVersion.equals(newVersion)) {
117                                String message = "Trying to add validation support of version " + newVersion + " to chain with " + myChain.size() + " entries of version " + existingVersion;
118                                throw new ConfigurationException(message);
119                        }
120                }
121
122                myChain.add(theIndex, theValidationSupport);
123        }
124
125        /**
126         * Removes an item from the chain. Note that this method is mostly intended for testing. Removing items from the chain while validation is
127         * actually occurring is not an expected use case for this class.
128         */
129        public void removeValidationSupport(IValidationSupport theValidationSupport) {
130                myChain.remove(theValidationSupport);
131        }
132
133        @Override
134        public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand) {
135                for (IValidationSupport next : myChain) {
136                        // TODO: test if code system is supported?
137                        ValueSetExpansionOutcome expanded = next.expandValueSet(theValidationSupportContext, theExpansionOptions, theValueSetToExpand);
138                        if (expanded != null) {
139                                return expanded;
140                        }
141                }
142                return null;
143        }
144
145        @Override
146        public List<IBaseResource> fetchAllConformanceResources() {
147                List<IBaseResource> retVal = new ArrayList<>();
148                for (IValidationSupport next : myChain) {
149                        List<IBaseResource> candidates = next.fetchAllConformanceResources();
150                        if (candidates != null) {
151                                retVal.addAll(candidates);
152                        }
153                }
154                return retVal;
155        }
156
157        @Override
158        public List<IBaseResource> fetchAllStructureDefinitions() {
159                ArrayList<IBaseResource> retVal = new ArrayList<>();
160                Set<String> urls = new HashSet<>();
161                for (IValidationSupport nextSupport : myChain) {
162                        List<IBaseResource> allStructureDefinitions = nextSupport.fetchAllStructureDefinitions();
163                        if (allStructureDefinitions != null) {
164                                for (IBaseResource next : allStructureDefinitions) {
165
166                                        IPrimitiveType<?> urlType = getFhirContext().newTerser().getSingleValueOrNull(next, "url", IPrimitiveType.class);
167                                        if (urlType == null || isBlank(urlType.getValueAsString()) || urls.add(urlType.getValueAsString())) {
168                                                retVal.add(next);
169                                        }
170                                }
171                        }
172                }
173                return retVal;
174        }
175
176        @Override
177        public IBaseResource fetchCodeSystem(String theSystem) {
178                for (IValidationSupport next : myChain) {
179                        IBaseResource retVal = next.fetchCodeSystem(theSystem);
180                        if (retVal != null) {
181                                return retVal;
182                        }
183                }
184                return null;
185        }
186
187        @Override
188        public IBaseResource fetchValueSet(String theUrl) {
189                for (IValidationSupport next : myChain) {
190                        IBaseResource retVal = next.fetchValueSet(theUrl);
191                        if (retVal != null) {
192                                return retVal;
193                        }
194                }
195                return null;
196        }
197
198
199        @Override
200        public <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) {
201                for (IValidationSupport next : myChain) {
202                        T retVal = next.fetchResource(theClass, theUri);
203                        if (retVal != null) {
204                                return retVal;
205                        }
206                }
207                return null;
208        }
209
210        @Override
211        public IBaseResource fetchStructureDefinition(String theUrl) {
212                for (IValidationSupport next : myChain) {
213                        IBaseResource retVal = next.fetchStructureDefinition(theUrl);
214                        if (retVal != null) {
215                                return retVal;
216                        }
217                }
218                return null;
219        }
220
221        @Override
222        public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
223                for (IValidationSupport next : myChain) {
224                        if (next.isCodeSystemSupported(theValidationSupportContext, theSystem)) {
225                                return true;
226                        }
227                }
228                return false;
229        }
230
231        @Override
232        public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
233                for (IValidationSupport next : myChain) {
234                        if (isBlank(theValueSetUrl) || next.isValueSetSupported(theValidationSupportContext, theValueSetUrl)) {
235                                if (theOptions.isInferSystem() || (theCodeSystem != null && next.isCodeSystemSupported(theValidationSupportContext, theCodeSystem))) {
236                                        CodeValidationResult retVal = next.validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl);
237                                        if (retVal != null) {
238                                                return retVal;
239                                        }
240                                }
241                        }
242                }
243                return null;
244        }
245
246        @Override
247        public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
248                for (IValidationSupport next : myChain) {
249                        String url = CommonCodeSystemsTerminologyService.getValueSetUrl(theValueSet);
250                        if (isBlank(url) || next.isValueSetSupported(theValidationSupportContext, url)) {
251                                CodeValidationResult retVal = next.validateCodeInValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSet);
252                                if (retVal != null) {
253                                        return retVal;
254                                }
255                        }
256                }
257                return null;
258        }
259
260        @Override
261        public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
262                for (IValidationSupport next : myChain) {
263                        if (next.isCodeSystemSupported(theValidationSupportContext, theSystem)) {
264                                return next.lookupCode(theValidationSupportContext, theSystem, theCode);
265                        }
266                }
267                return null;
268        }
269
270
271}