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 org.apache.commons.compress.utils.Sets; 008import org.apache.commons.lang3.Validate; 009import org.hl7.fhir.instance.model.api.IBase; 010import org.hl7.fhir.instance.model.api.IBaseResource; 011import org.hl7.fhir.instance.model.api.IPrimitiveType; 012import org.hl7.fhir.r4.model.CodeSystem; 013import org.hl7.fhir.r4.model.StructureDefinition; 014import org.hl7.fhir.r4.model.ValueSet; 015 016import javax.annotation.Nonnull; 017import java.util.ArrayList; 018import java.util.Collection; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Optional; 024import java.util.Set; 025 026import static org.apache.commons.lang3.StringUtils.isNotBlank; 027 028/** 029 * This class is an implementation of {@link IValidationSupport} which may be pre-populated 030 * with a collection of validation resources to be used by the validator. 031 */ 032public class PrePopulatedValidationSupport extends BaseStaticResourceValidationSupport implements IValidationSupport { 033 034 private final Map<String, IBaseResource> myCodeSystems; 035 private final Map<String, IBaseResource> myStructureDefinitions; 036 private final Map<String, IBaseResource> myValueSets; 037 private final Map<String, byte[]> myBinaries; 038 039 /** 040 * Constructor 041 */ 042 public PrePopulatedValidationSupport(FhirContext theContext) { 043 this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>()); 044 } 045 046 /** 047 * Constructor 048 * 049 * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and 050 * values are the resource itself. 051 * @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are 052 * the resource itself. 053 * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are 054 * the resource itself. 055 **/ 056 public PrePopulatedValidationSupport( 057 FhirContext theFhirContext, 058 Map<String, IBaseResource> theStructureDefinitions, 059 Map<String, IBaseResource> theValueSets, 060 Map<String, IBaseResource> theCodeSystems) { 061 this(theFhirContext, theStructureDefinitions, theValueSets, theCodeSystems, new HashMap<>()); 062 } 063 064 /** 065 * Constructor 066 * 067 * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and 068 * values are the resource itself. 069 * @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are 070 * the resource itself. 071 * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are 072 * the resource itself. 073 * @param theBinaries The binary files to be returned by this module. Keys are the unique filename for the binary, and values 074 * are the contents of the file as a byte array. 075 */ 076 public PrePopulatedValidationSupport( 077 FhirContext theFhirContext, 078 Map<String, IBaseResource> theStructureDefinitions, 079 Map<String, IBaseResource> theValueSets, 080 Map<String, IBaseResource> theCodeSystems, 081 Map<String, byte[]> theBinaries) { 082 super(theFhirContext); 083 Validate.notNull(theFhirContext, "theFhirContext must not be null"); 084 Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null"); 085 Validate.notNull(theValueSets, "theValueSets must not be null"); 086 Validate.notNull(theCodeSystems, "theCodeSystems must not be null"); 087 Validate.notNull(theBinaries, "theBinaries must not be null"); 088 myStructureDefinitions = theStructureDefinitions; 089 myValueSets = theValueSets; 090 myCodeSystems = theCodeSystems; 091 myBinaries = theBinaries; 092 } 093 094 public void addBinary(byte[] theBinary, String theBinaryKey) { 095 Validate.notNull(theBinary, "theBinaryKey must not be null"); 096 Validate.notNull(theBinary, "the" + theBinaryKey + " must not be null"); 097 myBinaries.put(theBinaryKey, theBinary); 098 } 099 100 /** 101 * Add a new CodeSystem resource which will be available to the validator. Note that 102 * {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this 103 * value will be used as the logical URL. 104 * <p> 105 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 106 * it will be stored in three ways: 107 * <ul> 108 * <li>Extension</li> 109 * <li>StructureDefinition/Extension</li> 110 * <li>http://hl7.org/StructureDefinition/Extension</li> 111 * </ul> 112 * </p> 113 */ 114 public void addCodeSystem(IBaseResource theCodeSystem) { 115 Set<String> urls = processResourceAndReturnUrls(theCodeSystem, "CodeSystem"); 116 addToMap(theCodeSystem, myCodeSystems, urls); 117 } 118 119 private Set<String> processResourceAndReturnUrls(IBaseResource theResource, String theResourceName) { 120 Validate.notNull(theResource, "the" + theResourceName + " must not be null"); 121 RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theResource); 122 String actualResourceName = resourceDef.getName(); 123 Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName); 124 125 Optional<IBase> urlValue = resourceDef.getChildByName("url").getAccessor().getFirstValueOrNull(theResource); 126 String url = urlValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null); 127 128 Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null"); 129 Validate.notBlank(url, "the" + theResourceName + ".getUrl() must return a value"); 130 131 String urlWithoutVersion; 132 int pipeIdx = url.indexOf('|'); 133 if (pipeIdx != -1) { 134 urlWithoutVersion = url.substring(0, pipeIdx); 135 } else { 136 urlWithoutVersion = url; 137 } 138 139 HashSet<String> retVal = Sets.newHashSet(url, urlWithoutVersion); 140 141 Optional<IBase> versionValue = resourceDef.getChildByName("version").getAccessor().getFirstValueOrNull(theResource); 142 String version = versionValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null); 143 if (isNotBlank(version)) { 144 retVal.add(urlWithoutVersion + "|" + version); 145 } 146 147 return retVal; 148 } 149 150 /** 151 * Add a new StructureDefinition resource which will be available to the validator. Note that 152 * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this 153 * value will be used as the logical URL. 154 * <p> 155 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 156 * it will be stored in three ways: 157 * <ul> 158 * <li>Extension</li> 159 * <li>StructureDefinition/Extension</li> 160 * <li>http://hl7.org/StructureDefinition/Extension</li> 161 * </ul> 162 * </p> 163 */ 164 public void addStructureDefinition(IBaseResource theStructureDefinition) { 165 Set<String> url = processResourceAndReturnUrls(theStructureDefinition, "StructureDefinition"); 166 addToMap(theStructureDefinition, myStructureDefinitions, url); 167 } 168 169 private <T extends IBaseResource> void addToMap(T theResource, Map<String, T> theMap, Collection<String> theUrls) { 170 for (String urls : theUrls) { 171 if (isNotBlank(urls)) { 172 theMap.put(urls, theResource); 173 174 int lastSlashIdx = urls.lastIndexOf('/'); 175 if (lastSlashIdx != -1) { 176 theMap.put(urls.substring(lastSlashIdx + 1), theResource); 177 int previousSlashIdx = urls.lastIndexOf('/', lastSlashIdx - 1); 178 if (previousSlashIdx != -1) { 179 theMap.put(urls.substring(previousSlashIdx + 1), theResource); 180 } 181 } 182 } 183 } 184 } 185 186 /** 187 * Add a new ValueSet resource which will be available to the validator. Note that 188 * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this 189 * value will be used as the logical URL. 190 * <p> 191 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 192 * it will be stored in three ways: 193 * <ul> 194 * <li>Extension</li> 195 * <li>StructureDefinition/Extension</li> 196 * <li>http://hl7.org/StructureDefinition/Extension</li> 197 * </ul> 198 * </p> 199 */ 200 public void addValueSet(IBaseResource theValueSet) { 201 Set<String> urls = processResourceAndReturnUrls(theValueSet, "ValueSet"); 202 addToMap(theValueSet, myValueSets, urls); 203 } 204 205 206 /** 207 * @param theResource The resource. This method delegates to the type-specific methods (e.g. {@link #addCodeSystem(IBaseResource)}) 208 * and will do nothing if the resource type is not supported by this class. 209 * @since 5.5.0 210 */ 211 public void addResource(@Nonnull IBaseResource theResource) { 212 Validate.notNull(theResource, "theResource must not be null"); 213 214 switch (getFhirContext().getResourceType(theResource)) { 215 case "StructureDefinition": 216 addStructureDefinition(theResource); 217 break; 218 case "CodeSystem": 219 addCodeSystem(theResource); 220 break; 221 case "ValueSet": 222 addValueSet(theResource); 223 break; 224 } 225 } 226 227 @Override 228 public List<IBaseResource> fetchAllConformanceResources() { 229 ArrayList<IBaseResource> retVal = new ArrayList<>(); 230 retVal.addAll(myCodeSystems.values()); 231 retVal.addAll(myStructureDefinitions.values()); 232 retVal.addAll(myValueSets.values()); 233 return retVal; 234 } 235 236 @Override 237 public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() { 238 return toList(myStructureDefinitions); 239 } 240 241 @Override 242 public IBaseResource fetchCodeSystem(String theSystem) { 243 return myCodeSystems.get(theSystem); 244 } 245 246 @Override 247 public IBaseResource fetchValueSet(String theUri) { 248 return myValueSets.get(theUri); 249 } 250 251 @Override 252 public IBaseResource fetchStructureDefinition(String theUrl) { 253 return myStructureDefinitions.get(theUrl); 254 } 255 256 @Override 257 public byte[] fetchBinary(String theBinaryKey) { return myBinaries.get(theBinaryKey); } 258 259 @Override 260 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 261 return myCodeSystems.containsKey(theSystem); 262 } 263 264 @Override 265 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 266 return myValueSets.containsKey(theValueSetUrl); 267 } 268}