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}