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 038 /** 039 * Constructor 040 */ 041 public PrePopulatedValidationSupport(FhirContext theContext) { 042 this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>()); 043 } 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(FhirContext theFhirContext, Map<String, IBaseResource> theStructureDefinitions, Map<String, IBaseResource> theValueSets, Map<String, IBaseResource> theCodeSystems) { 057 super(theFhirContext); 058 Validate.notNull(theFhirContext, "theFhirContext must not be null"); 059 Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null"); 060 Validate.notNull(theValueSets, "theValueSets must not be null"); 061 Validate.notNull(theCodeSystems, "theCodeSystems must not be null"); 062 myStructureDefinitions = theStructureDefinitions; 063 myValueSets = theValueSets; 064 myCodeSystems = theCodeSystems; 065 } 066 067 /** 068 * Add a new CodeSystem resource which will be available to the validator. Note that 069 * {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this 070 * value will be used as the logical URL. 071 * <p> 072 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 073 * it will be stored in three ways: 074 * <ul> 075 * <li>Extension</li> 076 * <li>StructureDefinition/Extension</li> 077 * <li>http://hl7.org/StructureDefinition/Extension</li> 078 * </ul> 079 * </p> 080 */ 081 public void addCodeSystem(IBaseResource theCodeSystem) { 082 Set<String> urls = processResourceAndReturnUrls(theCodeSystem, "CodeSystem"); 083 addToMap(theCodeSystem, myCodeSystems, urls); 084 } 085 086 private Set<String> processResourceAndReturnUrls(IBaseResource theResource, String theResourceName) { 087 Validate.notNull(theResource, "the" + theResourceName + " must not be null"); 088 RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theResource); 089 String actualResourceName = resourceDef.getName(); 090 Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName); 091 092 Optional<IBase> urlValue = resourceDef.getChildByName("url").getAccessor().getFirstValueOrNull(theResource); 093 String url = urlValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null); 094 095 Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null"); 096 Validate.notBlank(url, "the" + theResourceName + ".getUrl() must return a value"); 097 098 String urlWithoutVersion; 099 int pipeIdx = url.indexOf('|'); 100 if (pipeIdx != -1) { 101 urlWithoutVersion = url.substring(0, pipeIdx); 102 } else { 103 urlWithoutVersion = url; 104 } 105 106 HashSet<String> retVal = Sets.newHashSet(url, urlWithoutVersion); 107 108 Optional<IBase> versionValue = resourceDef.getChildByName("version").getAccessor().getFirstValueOrNull(theResource); 109 String version = versionValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null); 110 if (isNotBlank(version)) { 111 retVal.add(urlWithoutVersion + "|" + version); 112 } 113 114 return retVal; 115 } 116 117 /** 118 * Add a new StructureDefinition resource which will be available to the validator. Note that 119 * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this 120 * value will be used as the logical URL. 121 * <p> 122 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 123 * it will be stored in three ways: 124 * <ul> 125 * <li>Extension</li> 126 * <li>StructureDefinition/Extension</li> 127 * <li>http://hl7.org/StructureDefinition/Extension</li> 128 * </ul> 129 * </p> 130 */ 131 public void addStructureDefinition(IBaseResource theStructureDefinition) { 132 Set<String> url = processResourceAndReturnUrls(theStructureDefinition, "StructureDefinition"); 133 addToMap(theStructureDefinition, myStructureDefinitions, url); 134 } 135 136 private <T extends IBaseResource> void addToMap(T theResource, Map<String, T> theMap, Collection<String> theUrls) { 137 for (String urls : theUrls) { 138 if (isNotBlank(urls)) { 139 theMap.put(urls, theResource); 140 141 int lastSlashIdx = urls.lastIndexOf('/'); 142 if (lastSlashIdx != -1) { 143 theMap.put(urls.substring(lastSlashIdx + 1), theResource); 144 int previousSlashIdx = urls.lastIndexOf('/', lastSlashIdx - 1); 145 if (previousSlashIdx != -1) { 146 theMap.put(urls.substring(previousSlashIdx + 1), theResource); 147 } 148 } 149 } 150 } 151 } 152 153 /** 154 * Add a new ValueSet resource which will be available to the validator. Note that 155 * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this 156 * value will be used as the logical URL. 157 * <p> 158 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 159 * it will be stored in three ways: 160 * <ul> 161 * <li>Extension</li> 162 * <li>StructureDefinition/Extension</li> 163 * <li>http://hl7.org/StructureDefinition/Extension</li> 164 * </ul> 165 * </p> 166 */ 167 public void addValueSet(IBaseResource theValueSet) { 168 Set<String> urls = processResourceAndReturnUrls(theValueSet, "ValueSet"); 169 addToMap(theValueSet, myValueSets, urls); 170 } 171 172 173 /** 174 * @param theResource The resource. This method delegates to the type-specific methods (e.g. {@link #addCodeSystem(IBaseResource)}) 175 * and will do nothing if the resource type is not supported by this class. 176 * @since 5.5.0 177 */ 178 public void addResource(@Nonnull IBaseResource theResource) { 179 Validate.notNull(theResource, "theResource must not be null"); 180 181 switch (getFhirContext().getResourceType(theResource)) { 182 case "StructureDefinition": 183 addStructureDefinition(theResource); 184 break; 185 case "CodeSystem": 186 addCodeSystem(theResource); 187 break; 188 case "ValueSet": 189 addValueSet(theResource); 190 break; 191 } 192 } 193 194 @Override 195 public List<IBaseResource> fetchAllConformanceResources() { 196 ArrayList<IBaseResource> retVal = new ArrayList<>(); 197 retVal.addAll(myCodeSystems.values()); 198 retVal.addAll(myStructureDefinitions.values()); 199 retVal.addAll(myValueSets.values()); 200 return retVal; 201 } 202 203 @Override 204 public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() { 205 return toList(myStructureDefinitions); 206 } 207 208 @Override 209 public IBaseResource fetchCodeSystem(String theSystem) { 210 return myCodeSystems.get(theSystem); 211 } 212 213 @Override 214 public IBaseResource fetchValueSet(String theUri) { 215 return myValueSets.get(theUri); 216 } 217 218 @Override 219 public IBaseResource fetchStructureDefinition(String theUrl) { 220 return myStructureDefinitions.get(theUrl); 221 } 222 223 @Override 224 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 225 return myCodeSystems.containsKey(theSystem); 226 } 227 228 @Override 229 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 230 return myValueSets.containsKey(theValueSetUrl); 231 } 232}