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.lang3.Validate; 008import org.hl7.fhir.instance.model.api.IBase; 009import org.hl7.fhir.instance.model.api.IBaseResource; 010import org.hl7.fhir.instance.model.api.IPrimitiveType; 011import org.hl7.fhir.r4.model.CodeSystem; 012import org.hl7.fhir.r4.model.StructureDefinition; 013import org.hl7.fhir.r4.model.ValueSet; 014 015import java.util.ArrayList; 016import java.util.HashMap; 017import java.util.List; 018import java.util.Map; 019import java.util.Optional; 020 021import static org.apache.commons.lang3.StringUtils.isNotBlank; 022 023/** 024 * This class is an implementation of {@link IValidationSupport} which may be pre-populated 025 * with a collection of validation resources to be used by the validator. 026 */ 027public class PrePopulatedValidationSupport extends BaseStaticResourceValidationSupport implements IValidationSupport { 028 029 private final FhirContext myFhirContext; 030 private final Map<String, IBaseResource> myCodeSystems; 031 private final Map<String, IBaseResource> myStructureDefinitions; 032 private final Map<String, IBaseResource> myValueSets; 033 034 /** 035 * Constructor 036 */ 037 public PrePopulatedValidationSupport(FhirContext theContext) { 038 this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>()); 039 } 040 041 042 /** 043 * Constructor 044 * 045 * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and 046 * values are the resource itself. 047 * @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are 048 * the resource itself. 049 * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are 050 * the resource itself. 051 */ 052 public PrePopulatedValidationSupport(FhirContext theFhirContext, Map<String, IBaseResource> theStructureDefinitions, Map<String, IBaseResource> theValueSets, Map<String, IBaseResource> theCodeSystems) { 053 super(theFhirContext); 054 Validate.notNull(theFhirContext, "theFhirContext must not be null"); 055 Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null"); 056 Validate.notNull(theValueSets, "theValueSets must not be null"); 057 Validate.notNull(theCodeSystems, "theCodeSystems must not be null"); 058 myFhirContext = theFhirContext; 059 myStructureDefinitions = theStructureDefinitions; 060 myValueSets = theValueSets; 061 myCodeSystems = theCodeSystems; 062 } 063 064 /** 065 * Add a new CodeSystem resource which will be available to the validator. Note that 066 * {@link CodeSystem#getUrl() the URL field) in this resource must contain a value as this 067 * value will be used as the logical URL. 068 * <p> 069 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 070 * it will be stored in three ways: 071 * <ul> 072 * <li>Extension</li> 073 * <li>StructureDefinition/Extension</li> 074 * <li>http://hl7.org/StructureDefinition/Extension</li> 075 * </ul> 076 * </p> 077 */ 078 public void addCodeSystem(IBaseResource theCodeSystem) { 079 String url = processResourceAndReturnUrl(theCodeSystem, "CodeSystem"); 080 addToMap(theCodeSystem, myCodeSystems, url); 081 } 082 083 private String processResourceAndReturnUrl(IBaseResource theCodeSystem, String theResourceName) { 084 Validate.notNull(theCodeSystem, "the" + theResourceName + " must not be null"); 085 RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theCodeSystem); 086 String actualResourceName = resourceDef.getName(); 087 Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName); 088 089 Optional<IBase> urlValue = resourceDef.getChildByName("url").getAccessor().getFirstValueOrNull(theCodeSystem); 090 String url = urlValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null); 091 092 Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null"); 093 Validate.notBlank(url, "the" + theResourceName + ".getUrl() must return a value"); 094 return url; 095 } 096 097 /** 098 * Add a new StructureDefinition resource which will be available to the validator. Note that 099 * {@link StructureDefinition#getUrl() the URL field) in this resource must contain a value as this 100 * value will be used as the logical URL. 101 * <p> 102 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 103 * it will be stored in three ways: 104 * <ul> 105 * <li>Extension</li> 106 * <li>StructureDefinition/Extension</li> 107 * <li>http://hl7.org/StructureDefinition/Extension</li> 108 * </ul> 109 * </p> 110 */ 111 public void addStructureDefinition(IBaseResource theStructureDefinition) { 112 String url = processResourceAndReturnUrl(theStructureDefinition, "StructureDefinition"); 113 addToMap(theStructureDefinition, myStructureDefinitions, url); 114 } 115 116 private <T extends IBaseResource> void addToMap(T theStructureDefinition, Map<String, T> map, String theUrl) { 117 if (isNotBlank(theUrl)) { 118 map.put(theUrl, theStructureDefinition); 119 120 int lastSlashIdx = theUrl.lastIndexOf('/'); 121 if (lastSlashIdx != -1) { 122 map.put(theUrl.substring(lastSlashIdx + 1), theStructureDefinition); 123 int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 1); 124 if (previousSlashIdx != -1) { 125 map.put(theUrl.substring(previousSlashIdx + 1), theStructureDefinition); 126 } 127 } 128 129 } 130 } 131 132 /** 133 * Add a new ValueSet resource which will be available to the validator. Note that 134 * {@link ValueSet#getUrl() the URL field) in this resource must contain a value as this 135 * value will be used as the logical URL. 136 * <p> 137 * Note that if the URL is a canonical FHIR URL (e.g. http://hl7.org/StructureDefinition/Extension), 138 * it will be stored in three ways: 139 * <ul> 140 * <li>Extension</li> 141 * <li>StructureDefinition/Extension</li> 142 * <li>http://hl7.org/StructureDefinition/Extension</li> 143 * </ul> 144 * </p> 145 */ 146 public void addValueSet(ValueSet theValueSet) { 147 String url = processResourceAndReturnUrl(theValueSet, "ValueSet"); 148 addToMap(theValueSet, myValueSets, url); 149 } 150 151 152 @Override 153 public List<IBaseResource> fetchAllConformanceResources() { 154 ArrayList<IBaseResource> retVal = new ArrayList<>(); 155 retVal.addAll(myCodeSystems.values()); 156 retVal.addAll(myStructureDefinitions.values()); 157 retVal.addAll(myValueSets.values()); 158 return retVal; 159 } 160 161 @Override 162 public List<IBaseResource> fetchAllStructureDefinitions() { 163 return toList(myStructureDefinitions); 164 } 165 166 @Override 167 public IBaseResource fetchCodeSystem(String theSystem) { 168 return myCodeSystems.get(theSystem); 169 } 170 171 @Override 172 public IBaseResource fetchValueSet(String theUri) { 173 return myValueSets.get(theUri); 174 } 175 176 @Override 177 public IBaseResource fetchStructureDefinition(String theUrl) { 178 return myStructureDefinitions.get(theUrl); 179 } 180 181 @Override 182 public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { 183 return myCodeSystems.containsKey(theSystem); 184 } 185 186 @Override 187 public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { 188 return myValueSets.containsKey(theValueSetUrl); 189 } 190 191}