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