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