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 ca.uhn.fhir.util.ILockable; 008import jakarta.annotation.Nonnull; 009import jakarta.annotation.Nullable; 010import org.apache.commons.compress.utils.Sets; 011import org.apache.commons.lang3.Validate; 012import org.hl7.fhir.instance.model.api.IBase; 013import org.hl7.fhir.instance.model.api.IBaseResource; 014import org.hl7.fhir.instance.model.api.IPrimitiveType; 015import org.hl7.fhir.r4.model.CodeSystem; 016import org.hl7.fhir.r4.model.StructureDefinition; 017import org.hl7.fhir.r4.model.ValueSet; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Optional; 027import java.util.Set; 028import java.util.stream.Collectors; 029 030import static org.apache.commons.lang3.StringUtils.isNotBlank; 031 032/** 033 * This class is an implementation of {@link IValidationSupport} which may be pre-populated 034 * with a collection of validation resources to be used by the validator. 035 */ 036public class PrePopulatedValidationSupport extends BaseStaticResourceValidationSupport 037 implements IValidationSupport, ILockable { 038 039 private final Map<String, IBaseResource> myUrlToCodeSystems; 040 private final Map<String, IBaseResource> myUrlToStructureDefinitions; 041 private final Map<String, IBaseResource> myUrlToSearchParameters; 042 private final Map<String, IBaseResource> myUrlToValueSets; 043 private final List<IBaseResource> myCodeSystems; 044 private final List<IBaseResource> myStructureDefinitions; 045 private final List<IBaseResource> mySearchParameters; 046 private final List<IBaseResource> myValueSets; 047 private final Map<String, byte[]> myBinaries; 048 private boolean myLocked; 049 050 /** 051 * Constructor 052 */ 053 public PrePopulatedValidationSupport(FhirContext theContext) { 054 this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>()); 055 } 056 057 @Override 058 public String getName() { 059 return getFhirContext().getVersion().getVersion() + " Pre-populated Validation Support"; 060 } 061 062 /** 063 * Constructor 064 * 065 * @param theUrlToStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and 066 * values are the resource itself. 067 * @param theUrlToValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are 068 * the resource itself. 069 * @param theUrlToCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are 070 * the resource itself. 071 **/ 072 public PrePopulatedValidationSupport( 073 FhirContext theFhirContext, 074 Map<String, IBaseResource> theUrlToStructureDefinitions, 075 Map<String, IBaseResource> theUrlToValueSets, 076 Map<String, IBaseResource> theUrlToCodeSystems) { 077 this( 078 theFhirContext, 079 theUrlToStructureDefinitions, 080 theUrlToValueSets, 081 theUrlToCodeSystems, 082 new HashMap<>(), 083 new HashMap<>()); 084 } 085 086 /** 087 * Constructor 088 * 089 * @param theUrlToStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and 090 * values are the resource itself. 091 * @param theUrlToValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are 092 * the resource itself. 093 * @param theUrlToCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are 094 * the resource itself. 095 * @param theBinaries The binary files to be returned by this module. Keys are the unique filename for the binary, and values 096 * are the contents of the file as a byte array. 097 */ 098 public PrePopulatedValidationSupport( 099 FhirContext theFhirContext, 100 Map<String, IBaseResource> theUrlToStructureDefinitions, 101 Map<String, IBaseResource> theUrlToValueSets, 102 Map<String, IBaseResource> theUrlToCodeSystems, 103 Map<String, IBaseResource> theUrlToSearchParameters, 104 Map<String, byte[]> theBinaries) { 105 super(theFhirContext); 106 Validate.notNull(theFhirContext, "theFhirContext must not be null"); 107 Validate.notNull(theUrlToStructureDefinitions, "theStructureDefinitions must not be null"); 108 Validate.notNull(theUrlToValueSets, "theValueSets must not be null"); 109 Validate.notNull(theUrlToCodeSystems, "theCodeSystems must not be null"); 110 Validate.notNull(theUrlToSearchParameters, "theSearchParameters must not be null"); 111 Validate.notNull(theBinaries, "theBinaries must not be null"); 112 myUrlToStructureDefinitions = theUrlToStructureDefinitions; 113 myStructureDefinitions = 114 theUrlToStructureDefinitions.values().stream().distinct().collect(Collectors.toList()); 115 116 myUrlToValueSets = theUrlToValueSets; 117 myValueSets = theUrlToValueSets.values().stream().distinct().collect(Collectors.toList()); 118 119 myUrlToCodeSystems = theUrlToCodeSystems; 120 myCodeSystems = theUrlToCodeSystems.values().stream().distinct().collect(Collectors.toList()); 121 122 myUrlToSearchParameters = theUrlToSearchParameters; 123 mySearchParameters = 124 theUrlToSearchParameters.values().stream().distinct().collect(Collectors.toList()); 125 126 myBinaries = theBinaries; 127 } 128 129 public void addBinary(byte[] theBinary, String theBinaryKey) { 130 validateNotLocked(); 131 Validate.notNull(theBinary, "theBinaryKey must not be null"); 132 Validate.notNull(theBinary, "the" + theBinaryKey + " must not be null"); 133 myBinaries.put(theBinaryKey, theBinary); 134 } 135 136 private synchronized void validateNotLocked() { 137 Validate.isTrue(myLocked == false, "Can not add to validation support, module is locked"); 138 } 139 140 /** 141 * Add a new CodeSystem resource which will be available to the validator. Note that 142 * {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this 143 * value will be used as the logical URL. 144 * <p> 145 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 146 * it will be stored in three ways: 147 * <ul> 148 * <li>Extension</li> 149 * <li>StructureDefinition/Extension</li> 150 * <li>http://hl7.org/StructureDefinition/Extension</li> 151 * </ul> 152 * </p> 153 */ 154 public void addCodeSystem(IBaseResource theCodeSystem) { 155 validateNotLocked(); 156 Set<String> urls = processResourceAndReturnUrls(theCodeSystem, "CodeSystem"); 157 addToMap(theCodeSystem, myCodeSystems, myUrlToCodeSystems, urls); 158 } 159 160 private Set<String> processResourceAndReturnUrls(IBaseResource theResource, String theResourceName) { 161 Validate.notNull(theResource, "the" + theResourceName + " must not be null"); 162 RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theResource); 163 String actualResourceName = resourceDef.getName(); 164 Validate.isTrue( 165 actualResourceName.equals(theResourceName), 166 "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName); 167 168 Optional<IBase> urlValue = 169 resourceDef.getChildByName("url").getAccessor().getFirstValueOrNull(theResource); 170 String url = 171 urlValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null); 172 173 Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null"); 174 Validate.notBlank(url, "the" + theResourceName + ".getUrl() must return a value"); 175 176 String urlWithoutVersion; 177 int pipeIdx = url.indexOf('|'); 178 if (pipeIdx != -1) { 179 urlWithoutVersion = url.substring(0, pipeIdx); 180 } else { 181 urlWithoutVersion = url; 182 } 183 184 HashSet<String> retVal = Sets.newHashSet(url, urlWithoutVersion); 185 186 Optional<IBase> versionValue = 187 resourceDef.getChildByName("version").getAccessor().getFirstValueOrNull(theResource); 188 String version = versionValue 189 .map(t -> (((IPrimitiveType<?>) t).getValueAsString())) 190 .orElse(null); 191 if (isNotBlank(version)) { 192 retVal.add(urlWithoutVersion + "|" + version); 193 } 194 195 return retVal; 196 } 197 198 /** 199 * Add a new StructureDefinition resource which will be available to the validator. Note that 200 * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this 201 * value will be used as the logical URL. 202 * <p> 203 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 204 * it will be stored in three ways: 205 * <ul> 206 * <li>Extension</li> 207 * <li>StructureDefinition/Extension</li> 208 * <li>http://hl7.org/StructureDefinition/Extension</li> 209 * </ul> 210 * </p> 211 */ 212 public void addStructureDefinition(IBaseResource theStructureDefinition) { 213 validateNotLocked(); 214 Set<String> url = processResourceAndReturnUrls(theStructureDefinition, "StructureDefinition"); 215 addToMap(theStructureDefinition, myStructureDefinitions, myUrlToStructureDefinitions, url); 216 } 217 218 public void addSearchParameter(IBaseResource theSearchParameter) { 219 validateNotLocked(); 220 Set<String> url = processResourceAndReturnUrls(theSearchParameter, "SearchParameter"); 221 addToMap(theSearchParameter, mySearchParameters, myUrlToSearchParameters, url); 222 } 223 224 private <T extends IBaseResource> void addToMap( 225 T theResource, List<T> theList, Map<String, T> theMap, Collection<String> theUrls) { 226 theList.add(theResource); 227 for (String urls : theUrls) { 228 if (isNotBlank(urls)) { 229 theMap.put(urls, theResource); 230 231 int lastSlashIdx = urls.lastIndexOf('/'); 232 if (lastSlashIdx != -1) { 233 theMap.put(urls.substring(lastSlashIdx + 1), theResource); 234 int previousSlashIdx = urls.lastIndexOf('/', lastSlashIdx - 1); 235 if (previousSlashIdx != -1) { 236 theMap.put(urls.substring(previousSlashIdx + 1), theResource); 237 } 238 } 239 } 240 } 241 } 242 243 /** 244 * Add a new ValueSet resource which will be available to the validator. Note that 245 * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this 246 * value will be used as the logical URL. 247 * <p> 248 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 249 * it will be stored in three ways: 250 * <ul> 251 * <li>Extension</li> 252 * <li>StructureDefinition/Extension</li> 253 * <li>http://hl7.org/StructureDefinition/Extension</li> 254 * </ul> 255 * </p> 256 */ 257 public void addValueSet(IBaseResource theValueSet) { 258 validateNotLocked(); 259 Set<String> urls = processResourceAndReturnUrls(theValueSet, "ValueSet"); 260 addToMap(theValueSet, myValueSets, myUrlToValueSets, urls); 261 } 262 263 /** 264 * @param theResource The resource. This method delegates to the type-specific methods (e.g. {@link #addCodeSystem(IBaseResource)}) 265 * and will do nothing if the resource type is not supported by this class. 266 * @since 5.5.0 267 */ 268 public void addResource(@Nonnull IBaseResource theResource) { 269 validateNotLocked(); 270 Validate.notNull(theResource, "theResource must not be null"); 271 272 switch (getFhirContext().getResourceType(theResource)) { 273 case "SearchParameter": 274 addSearchParameter(theResource); 275 break; 276 case "StructureDefinition": 277 addStructureDefinition(theResource); 278 break; 279 case "CodeSystem": 280 addCodeSystem(theResource); 281 break; 282 case "ValueSet": 283 addValueSet(theResource); 284 break; 285 } 286 } 287 288 @Override 289 public List<IBaseResource> fetchAllConformanceResources() { 290 ArrayList<IBaseResource> retVal = new ArrayList<>(); 291 retVal.addAll(myCodeSystems); 292 retVal.addAll(myStructureDefinitions); 293 retVal.addAll(myValueSets); 294 return retVal; 295 } 296 297 @SuppressWarnings("unchecked") 298 @Nullable 299 @Override 300 public <T extends IBaseResource> List<T> fetchAllSearchParameters() { 301 return (List<T>) Collections.unmodifiableList(mySearchParameters); 302 } 303 304 @SuppressWarnings("unchecked") 305 @Override 306 public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() { 307 return (List<T>) Collections.unmodifiableList(myStructureDefinitions); 308 } 309 310 @Override 311 public IBaseResource fetchCodeSystem(String theSystem) { 312 return myUrlToCodeSystems.get(theSystem); 313 } 314 315 @Override 316 public IBaseResource fetchValueSet(String theUri) { 317 return myUrlToValueSets.get(theUri); 318 } 319 320 @Override 321 public IBaseResource fetchStructureDefinition(String theUrl) { 322 return myUrlToStructureDefinitions.get(theUrl); 323 } 324 325 @Override 326 public byte[] fetchBinary(String theBinaryKey) { 327 return myBinaries.get(theBinaryKey); 328 } 329 330 @Override 331 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 332 return myUrlToCodeSystems.containsKey(theSystem); 333 } 334 335 @Override 336 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 337 return myUrlToValueSets.containsKey(theValueSetUrl); 338 } 339 340 /** 341 * Returns a count of all known resources 342 */ 343 public int countAll() { 344 return myBinaries.size() 345 + myCodeSystems.size() 346 + myStructureDefinitions.size() 347 + myValueSets.size() 348 + myStructureDefinitions.size(); 349 } 350 351 @Override 352 public synchronized void lock() { 353 myLocked = true; 354 } 355}