001package org.hl7.fhir.dstu3.hapi.ctx; 002 003import ca.uhn.fhir.i18n.Msg; 004import ca.uhn.fhir.context.FhirContext; 005import ca.uhn.fhir.context.support.ConceptValidationOptions; 006import ca.uhn.fhir.context.support.IValidationSupport; 007import ca.uhn.fhir.context.support.ValidationSupportContext; 008import ca.uhn.fhir.rest.api.Constants; 009import ca.uhn.fhir.util.CoverageIgnore; 010import com.github.benmanes.caffeine.cache.Cache; 011import com.github.benmanes.caffeine.cache.Caffeine; 012import org.apache.commons.lang3.Validate; 013import org.apache.commons.lang3.time.DateUtils; 014import org.hl7.fhir.dstu3.context.IWorkerContext; 015import org.hl7.fhir.dstu3.formats.IParser; 016import org.hl7.fhir.dstu3.formats.ParserType; 017import org.hl7.fhir.dstu3.model.CodeSystem; 018import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; 019import org.hl7.fhir.dstu3.model.CodeableConcept; 020import org.hl7.fhir.dstu3.model.Coding; 021import org.hl7.fhir.dstu3.model.ConceptMap; 022import org.hl7.fhir.dstu3.model.ExpansionProfile; 023import org.hl7.fhir.dstu3.model.MetadataResource; 024import org.hl7.fhir.dstu3.model.Resource; 025import org.hl7.fhir.dstu3.model.ResourceType; 026import org.hl7.fhir.dstu3.model.StructureDefinition; 027import org.hl7.fhir.dstu3.model.ValueSet; 028import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; 029import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; 030import org.hl7.fhir.dstu3.terminologies.ValueSetExpander; 031import org.hl7.fhir.dstu3.utils.INarrativeGenerator; 032import org.hl7.fhir.dstu3.utils.validation.IResourceValidator; 033import org.hl7.fhir.exceptions.FHIRException; 034import org.hl7.fhir.utilities.i18n.I18nBase; 035import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 036import org.hl7.fhir.utilities.validation.ValidationOptions; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collections; 043import java.util.HashSet; 044import java.util.List; 045import java.util.Set; 046import java.util.concurrent.TimeUnit; 047 048import static org.apache.commons.lang3.StringUtils.isNotBlank; 049 050public final class HapiWorkerContext extends I18nBase implements IWorkerContext { 051 private static final Logger ourLog = LoggerFactory.getLogger(HapiWorkerContext.class); 052 private final FhirContext myCtx; 053 private final Cache<String, Resource> myFetchedResourceCache; 054 private IValidationSupport myValidationSupport; 055 private ExpansionProfile myExpansionProfile; 056 057 public HapiWorkerContext(FhirContext theCtx, IValidationSupport theValidationSupport) { 058 Validate.notNull(theCtx, "theCtx must not be null"); 059 Validate.notNull(theValidationSupport, "theValidationSupport must not be null"); 060 myCtx = theCtx; 061 myValidationSupport = theValidationSupport; 062 063 long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND; 064 if (System.getProperties().containsKey(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) { 065 timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)); 066 } 067 myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build(); 068 069 // Set a default locale 070 setValidationMessageLanguage(getLocale()); 071 } 072 073 @Override 074 @CoverageIgnore 075 public List<MetadataResource> allConformanceResources() { 076 throw new UnsupportedOperationException(Msg.code(610)); 077 } 078 079 @Override 080 public List<StructureDefinition> allStructures() { 081 return myValidationSupport.fetchAllStructureDefinitions(); 082 } 083 084 @Override 085 public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc, boolean theHierarchical) { 086 ValueSet input = new ValueSet(); 087 input.getCompose().addInclude(theInc); 088 IValidationSupport.ValueSetExpansionOutcome output = myValidationSupport.expandValueSet(new ValidationSupportContext(myValidationSupport), null, input); 089 ValueSet outputValueSet = (ValueSet) output.getValueSet(); 090 if (outputValueSet != null) { 091 return outputValueSet.getExpansion(); 092 } else { 093 return null; 094 } 095 } 096 097 @Override 098 public StructureDefinition fetchTypeDefinition(String theCode) { 099 return fetchResource(org.hl7.fhir.dstu3.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + theCode); 100 } 101 102 @Override 103 public CodeSystem fetchCodeSystem(String theSystem) { 104 if (myValidationSupport == null) { 105 return null; 106 } else { 107 return (CodeSystem) myValidationSupport.fetchCodeSystem(theSystem); 108 } 109 } 110 111 @Override 112 public <T extends Resource> T fetchResource(Class<T> theClass, String theUri) { 113 Validate.notBlank(theUri, "theUri must not be null or blank"); 114 if (myValidationSupport == null) { 115 return null; 116 } else { 117 try { 118 //noinspection unchecked 119 return (T) myFetchedResourceCache.get(theUri, t -> { 120 T resource = myValidationSupport.fetchResource(theClass, theUri); 121 if (resource == null) { 122 throw new IllegalArgumentException(Msg.code(611)); 123 } 124 return resource; 125 }); 126 } catch (IllegalArgumentException e) { 127 return null; 128 } 129 } 130 } 131 132 @Override 133 public <T extends Resource> T fetchResourceWithException(Class<T> theClass_, String theUri) throws FHIRException { 134 T retVal = fetchResource(theClass_, theUri); 135 if (retVal == null) { 136 throw new FHIRException(Msg.code(612) + "Unable to fetch " + theUri); 137 } 138 return retVal; 139 } 140 141 @Override 142 public List<ConceptMap> findMapsForSource(String theUrl) { 143 throw new UnsupportedOperationException(Msg.code(613)); 144 } 145 146 @Override 147 public ValueSetExpander.ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical) { 148 throw new UnsupportedOperationException(Msg.code(614)); 149 } 150 151 @Override 152 public String getAbbreviation(String theName) { 153 throw new UnsupportedOperationException(Msg.code(615)); 154 } 155 156 @Override 157 public ExpansionProfile getExpansionProfile() { 158 return myExpansionProfile; 159 } 160 161 @Override 162 public void setExpansionProfile(ExpansionProfile theExpProfile) { 163 myExpansionProfile = theExpProfile; 164 } 165 166 @Override 167 public INarrativeGenerator getNarrativeGenerator(String thePrefix, String theBasePath) { 168 throw new UnsupportedOperationException(Msg.code(616)); 169 } 170 171 @Override 172 public IResourceValidator newValidator() throws FHIRException { 173 throw new UnsupportedOperationException(Msg.code(617)); 174 } 175 176 @Override 177 public IParser getParser(ParserType theType) { 178 throw new UnsupportedOperationException(Msg.code(618)); 179 } 180 181 @Override 182 public IParser getParser(String theType) { 183 throw new UnsupportedOperationException(Msg.code(619)); 184 } 185 186 @Override 187 public List<String> getResourceNames() { 188 List<String> result = new ArrayList<>(); 189 for (ResourceType next : ResourceType.values()) { 190 result.add(next.name()); 191 } 192 Collections.sort(result); 193 return result; 194 } 195 196 @Override 197 public Set<String> getResourceNamesAsSet() { 198 return new HashSet<>(getResourceNames()); 199 } 200 201 @Override 202 public List<String> getTypeNames() { 203 throw new UnsupportedOperationException(Msg.code(620)); 204 } 205 206 @Override 207 public String getVersion() { 208 return myCtx.getVersion().getVersion().getFhirVersionString(); 209 } 210 211 @Override 212 @CoverageIgnore 213 public boolean hasCache() { 214 throw new UnsupportedOperationException(Msg.code(621)); 215 } 216 217 @Override 218 public <T extends Resource> boolean hasResource(Class<T> theClass_, String theUri) { 219 throw new UnsupportedOperationException(Msg.code(622)); 220 } 221 222 @Override 223 public boolean isNoTerminologyServer() { 224 return false; 225 } 226 227 @Override 228 public IParser newJsonParser() { 229 throw new UnsupportedOperationException(Msg.code(623)); 230 } 231 232 @Override 233 public IParser newXmlParser() { 234 throw new UnsupportedOperationException(Msg.code(624)); 235 } 236 237 @Override 238 public String oid2Uri(String theCode) { 239 throw new UnsupportedOperationException(Msg.code(625)); 240 } 241 242 @Override 243 public void setLogger(ILoggingService theLogger) { 244 throw new UnsupportedOperationException(Msg.code(626)); 245 } 246 247 @Override 248 public boolean supportsSystem(String theSystem) { 249 if (myValidationSupport == null) { 250 return false; 251 } else { 252 return myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), theSystem); 253 } 254 } 255 256 @Override 257 public Set<String> typeTails() { 258 return new HashSet<>(Arrays.asList("Integer", "UnsignedInt", "PositiveInt", "Decimal", "DateTime", "Date", "Time", "Instant", "String", "Uri", "Oid", "Uuid", "Id", "Boolean", "Code", 259 "Markdown", "Base64Binary", "Coding", "CodeableConcept", "Attachment", "Identifier", "Quantity", "SampledData", "Range", "Period", "Ratio", "HumanName", "Address", "ContactPoint", 260 "Timing", "Reference", "Annotation", "Signature", "Meta")); 261 } 262 263 @Override 264 public ValidationResult validateCode(CodeableConcept theCode, ValueSet theVs) { 265 for (Coding next : theCode.getCoding()) { 266 ValidationResult retVal = validateCode(next, theVs); 267 if (retVal.isOk()) { 268 return retVal; 269 } 270 } 271 272 return new ValidationResult(IssueSeverity.ERROR, null); 273 } 274 275 @Override 276 public ValidationResult validateCode(Coding theCode, ValueSet theVs) { 277 String system = theCode.getSystem(); 278 String code = theCode.getCode(); 279 String display = theCode.getDisplay(); 280 return validateCode(system, code, display, theVs); 281 } 282 283 @Override 284 public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) { 285 ValidationOptions options = new ValidationOptions(); 286 IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(new ValidationSupportContext(myValidationSupport), convertConceptValidationOptions(options), theSystem, theCode, theDisplay, null); 287 if (result == null) { 288 return null; 289 } 290 291 IssueSeverity severity = null; 292 if (result.getSeverity() != null) { 293 severity = IssueSeverity.fromCode(result.getSeverityCode()); 294 } 295 ConceptDefinitionComponent definition = new ConceptDefinitionComponent().setCode(result.getCode()); 296 return new ValidationResult(severity, result.getMessage(), definition); 297 } 298 299 public static ConceptValidationOptions convertConceptValidationOptions(ValidationOptions theOptions) { 300 ConceptValidationOptions retVal = new ConceptValidationOptions(); 301 if (theOptions.isGuessSystem()) { 302 retVal = retVal.setInferSystem(true); 303 } 304 return retVal; 305 } 306 307 @Override 308 public ValidationResult validateCode(String theSystem, String theCode, String theDisplay, ConceptSetComponent theVsi) { 309 throw new UnsupportedOperationException(Msg.code(627)); 310 } 311 312 @Override 313 public ValidationResult validateCode(String theSystem, String theCode, String theDisplay, ValueSet theVs) { 314 315 IValidationSupport.CodeValidationResult outcome; 316 ValidationOptions options = new ValidationOptions(); 317 if (isNotBlank(theVs.getUrl())) { 318 outcome = myValidationSupport.validateCode(new ValidationSupportContext(myValidationSupport), convertConceptValidationOptions(options), theSystem, theCode, theDisplay, theVs.getUrl()); 319 } else { 320 outcome = myValidationSupport.validateCodeInValueSet(new ValidationSupportContext(myValidationSupport), convertConceptValidationOptions(options), theSystem, theCode, theDisplay, theVs); 321 } 322 323 if (outcome != null && outcome.isOk()) { 324 ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); 325 definition.setCode(theCode); 326 definition.setDisplay(outcome.getDisplay()); 327 return new ValidationResult(definition); 328 } 329 330 return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + Constants.codeSystemWithDefaultDescription(theSystem) + "]"); 331 } 332 333}