001package org.hl7.fhir.r4.hapi.ctx; 002 003import ca.uhn.fhir.context.FhirContext; 004import ca.uhn.fhir.rest.api.Constants; 005import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 006import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 007import ca.uhn.fhir.util.CoverageIgnore; 008import com.github.benmanes.caffeine.cache.Cache; 009import com.github.benmanes.caffeine.cache.Caffeine; 010import org.apache.commons.lang3.Validate; 011import org.apache.commons.lang3.time.DateUtils; 012import org.fhir.ucum.UcumService; 013import org.hl7.fhir.exceptions.DefinitionException; 014import org.hl7.fhir.exceptions.FHIRException; 015import org.hl7.fhir.exceptions.TerminologyServiceException; 016import org.hl7.fhir.r4.context.IWorkerContext; 017import org.hl7.fhir.r4.formats.IParser; 018import org.hl7.fhir.r4.formats.ParserType; 019import org.hl7.fhir.r4.hapi.ctx.IValidationSupport.CodeValidationResult; 020import org.hl7.fhir.r4.model.*; 021import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; 022import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent; 023import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent; 024import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; 025import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 026import org.hl7.fhir.r4.terminologies.ValueSetExpander; 027import org.hl7.fhir.r4.terminologies.ValueSetExpanderFactory; 028import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple; 029import org.hl7.fhir.r4.utils.IResourceValidator; 030import org.hl7.fhir.utilities.TerminologyServiceOptions; 031import org.hl7.fhir.utilities.TranslationServices; 032import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 033 034import java.util.*; 035import java.util.concurrent.TimeUnit; 036 037import static org.apache.commons.lang3.StringUtils.isBlank; 038import static org.apache.commons.lang3.StringUtils.isNotBlank; 039 040public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander, ValueSetExpanderFactory { 041 private final FhirContext myCtx; 042 private final Cache<String, Resource> myFetchedResourceCache; 043 private IValidationSupport myValidationSupport; 044 private Parameters myExpansionProfile; 045 private String myOverrideVersionNs; 046 047 public HapiWorkerContext(FhirContext theCtx, IValidationSupport theValidationSupport) { 048 Validate.notNull(theCtx, "theCtx must not be null"); 049 Validate.notNull(theValidationSupport, "theValidationSupport must not be null"); 050 myCtx = theCtx; 051 myValidationSupport = theValidationSupport; 052 053 long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND; 054 if (System.getProperties().containsKey(ca.uhn.fhir.rest.api.Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) { 055 timeoutMillis = Long.parseLong(System.getProperty(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)); 056 } 057 058 myFetchedResourceCache = Caffeine.newBuilder().expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS).build(); 059 } 060 061 @Override 062 public List<StructureDefinition> allStructures() { 063 return myValidationSupport.fetchAllStructureDefinitions(myCtx); 064 } 065 066 @Override 067 public List<StructureDefinition> getStructures() { 068 return allStructures(); 069 } 070 071 @Override 072 public CodeSystem fetchCodeSystem(String theSystem) { 073 if (myValidationSupport == null) { 074 return null; 075 } else { 076 return myValidationSupport.fetchCodeSystem(myCtx, theSystem); 077 } 078 } 079 080 @Override 081 public List<ConceptMap> findMapsForSource(String theUrl) { 082 throw new UnsupportedOperationException(); 083 } 084 085 @Override 086 public String getAbbreviation(String theName) { 087 throw new UnsupportedOperationException(); 088 } 089 090 @Override 091 public ValueSetExpander getExpander() { 092 ValueSetExpanderSimple retVal = new ValueSetExpanderSimple(this); 093 retVal.setMaxExpansionSize(Integer.MAX_VALUE); 094 return retVal; 095 } 096 097 @Override 098 public org.hl7.fhir.r4.utils.INarrativeGenerator getNarrativeGenerator(String thePrefix, String theBasePath) { 099 throw new UnsupportedOperationException(); 100 } 101 102 @Override 103 public IParser getParser(ParserType theType) { 104 throw new UnsupportedOperationException(); 105 } 106 107 @Override 108 public IParser getParser(String theType) { 109 throw new UnsupportedOperationException(); 110 } 111 112 @Override 113 public List<String> getResourceNames() { 114 List<String> result = new ArrayList<>(); 115 for (ResourceType next : ResourceType.values()) { 116 result.add(next.name()); 117 } 118 Collections.sort(result); 119 return result; 120 } 121 122 @Override 123 public IParser newJsonParser() { 124 throw new UnsupportedOperationException(); 125 } 126 127 @Override 128 public IResourceValidator newValidator() { 129 throw new UnsupportedOperationException(); 130 } 131 132 @Override 133 public IParser newXmlParser() { 134 throw new UnsupportedOperationException(); 135 } 136 137 @Override 138 public String oid2Uri(String theCode) { 139 throw new UnsupportedOperationException(); 140 } 141 142 @Override 143 public boolean supportsSystem(String theSystem) { 144 if (myValidationSupport == null) { 145 return false; 146 } else { 147 return myValidationSupport.isCodeSystemSupported(myCtx, theSystem); 148 } 149 } 150 151 @Override 152 public Set<String> typeTails() { 153 return new HashSet<>(Arrays.asList("Integer", "UnsignedInt", "PositiveInt", "Decimal", "DateTime", "Date", "Time", "Instant", "String", "Uri", "Oid", "Uuid", "Id", "Boolean", "Code", 154 "Markdown", "Base64Binary", "Coding", "CodeableConcept", "Attachment", "Identifier", "Quantity", "SampledData", "Range", "Period", "Ratio", "HumanName", "Address", "ContactPoint", 155 "Timing", "Reference", "Annotation", "Signature", "Meta")); 156 } 157 158 @Override 159 public ValidationResult validateCode(TerminologyServiceOptions theOptions, CodeableConcept theCode, ValueSet theVs) { 160 for (Coding next : theCode.getCoding()) { 161 ValidationResult retVal = validateCode(theOptions, next, theVs); 162 if (retVal != null && retVal.isOk()) { 163 return retVal; 164 } 165 } 166 167 return new ValidationResult(IssueSeverity.ERROR, null); 168 } 169 170 @Override 171 public ValidationResult validateCode(TerminologyServiceOptions theOptions, Coding theCode, ValueSet theVs) { 172 String system = theCode.getSystem(); 173 String code = theCode.getCode(); 174 String display = theCode.getDisplay(); 175 return validateCode(theOptions, system, code, display, theVs); 176 } 177 178 @Override 179 public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay) { 180 CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay); 181 if (result == null) { 182 return null; 183 } 184 return new ValidationResult(result.getSeverity(), result.getMessage(), result.asConceptDefinition()); 185 } 186 187 @Override 188 public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay, ConceptSetComponent theVsi) { 189 throw new UnsupportedOperationException(); 190 } 191 192 @Override 193 public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay, ValueSet theVs) { 194 195 if (theVs != null && isNotBlank(theCode)) { 196 for (ConceptSetComponent next : theVs.getCompose().getInclude()) { 197 if (isBlank(theSystem) || theSystem.equals(next.getSystem())) { 198 for (ConceptReferenceComponent nextCode : next.getConcept()) { 199 if (theCode.equals(nextCode.getCode())) { 200 CodeType code = new CodeType(theCode); 201 return new ValidationResult(new ConceptDefinitionComponent(code)); 202 } 203 } 204 } 205 } 206 } 207 208 boolean caseSensitive = true; 209 if (isNotBlank(theSystem)) { 210 CodeSystem system = fetchCodeSystem(theSystem); 211 if (system == null) { 212 return new ValidationResult(IssueSeverity.INFORMATION, "Code " + theSystem + "/" + theCode + " was not validated because the code system is not present"); 213 } 214 215 if (system.hasCaseSensitive()) { 216 caseSensitive = system.getCaseSensitive(); 217 } 218 } 219 220 String wantCode = theCode; 221 if (!caseSensitive) { 222 wantCode = wantCode.toUpperCase(); 223 } 224 225 ValueSetExpansionOutcome expandedValueSet = null; 226 227 /* 228 * The following valueset is a special case, since the BCP codesystem is very difficult to expand 229 */ 230 if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getId())) { 231 ValueSet expansion = new ValueSet(); 232 for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) { 233 for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) { 234 expansion.getExpansion().addContains().setCode(nextConcept.getCode()).setDisplay(nextConcept.getDisplay()); 235 } 236 } 237 expandedValueSet = new ValueSetExpansionOutcome(expansion); 238 } 239 240 /* 241 * The following valueset is a special case, since the mime types codesystem is very difficult to expand 242 */ 243 if (theVs != null && "http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getId())) { 244 ValueSet expansion = new ValueSet(); 245 expansion.getExpansion().addContains().setCode(theCode).setSystem(theSystem).setDisplay(theDisplay); 246 expandedValueSet = new ValueSetExpansionOutcome(expansion); 247 } 248 249 if (expandedValueSet == null) { 250 expandedValueSet = expand(theVs, null); 251 } 252 253 for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) { 254 String nextCode = next.getCode(); 255 if (!caseSensitive) { 256 nextCode = nextCode.toUpperCase(); 257 } 258 259 if (nextCode.equals(wantCode)) { 260 if (theSystem == null || next.getSystem().equals(theSystem)) { 261 ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); 262 definition.setCode(next.getCode()); 263 definition.setDisplay(next.getDisplay()); 264 ValidationResult retVal = new ValidationResult(definition); 265 return retVal; 266 } 267 } 268 } 269 270 return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]"); 271 } 272 273 @Override 274 public ValidationResult validateCode(TerminologyServiceOptions theOptions, String code, ValueSet vs) { 275 return validateCode(theOptions, null, code, null, vs); 276 } 277 278 @Override 279 @CoverageIgnore 280 public List<MetadataResource> allConformanceResources() { 281 throw new UnsupportedOperationException(); 282 } 283 284 @Override 285 public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException { 286 throw new UnsupportedOperationException(); 287 } 288 289 @Override 290 public Parameters getExpansionParameters() { 291 return myExpansionProfile; 292 } 293 294 @Override 295 public void setExpansionProfile(Parameters theExpParameters) { 296 myExpansionProfile = theExpParameters; 297 } 298 299 @Override 300 @CoverageIgnore 301 public boolean hasCache() { 302 throw new UnsupportedOperationException(); 303 } 304 305 @Override 306 public ValueSetExpansionOutcome expand(ValueSet theSource, Parameters theProfile) { 307 ValueSetExpansionOutcome vso; 308 try { 309 vso = getExpander().expand(theSource, theProfile); 310 } catch (InvalidRequestException e) { 311 throw e; 312 } catch (Exception e) { 313 throw new InternalErrorException(e); 314 } 315 if (vso.getError() != null) { 316 throw new InvalidRequestException(vso.getError()); 317 } else { 318 return vso; 319 } 320 } 321 322 @Override 323 public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHeiarchical) { 324 throw new UnsupportedOperationException(); 325 } 326 327 @Override 328 public ValueSetExpansionOutcome expandVS(ConceptSetComponent theInc, boolean theHeiarchical) throws TerminologyServiceException { 329 return myValidationSupport.expandValueSet(myCtx, theInc); 330 } 331 332 @Override 333 public void setLogger(ILoggingService theLogger) { 334 throw new UnsupportedOperationException(); 335 } 336 337 @Override 338 public ILoggingService getLogger() { 339 throw new UnsupportedOperationException(); 340 } 341 342 @Override 343 public String getVersion() { 344 return myCtx.getVersion().getVersion().getFhirVersionString(); 345 } 346 347 @Override 348 public UcumService getUcumService() { 349 throw new UnsupportedOperationException(); 350 } 351 352 @Override 353 public boolean isNoTerminologyServer() { 354 return false; 355 } 356 357 @Override 358 public TranslationServices translator() { 359 throw new UnsupportedOperationException(); 360 } 361 362 @Override 363 public List<StructureMap> listTransforms() { 364 throw new UnsupportedOperationException(); 365 } 366 367 @Override 368 public StructureMap getTransform(String url) { 369 throw new UnsupportedOperationException(); 370 } 371 372 @Override 373 public String getOverrideVersionNs() { 374 return myOverrideVersionNs; 375 } 376 377 @Override 378 public void setOverrideVersionNs(String value) { 379 myOverrideVersionNs = value; 380 } 381 382 @Override 383 public StructureDefinition fetchTypeDefinition(String typeName) { 384 return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); 385 } 386 387 @Override 388 public void setUcumService(UcumService ucumService) { 389 throw new UnsupportedOperationException(); 390 } 391 392 @Override 393 public List<String> getTypeNames() { 394 throw new UnsupportedOperationException(); 395 } 396 397 @Override 398 public <T extends org.hl7.fhir.r4.model.Resource> T fetchResource(Class<T> theClass, String theUri) { 399 if (myValidationSupport == null) { 400 return null; 401 } else { 402 @SuppressWarnings("unchecked") 403 T retVal = (T) myFetchedResourceCache.get(theUri, t -> { 404 return myValidationSupport.fetchResource(myCtx, theClass, theUri); 405 }); 406 return retVal; 407 } 408 } 409 410 @Override 411 public <T extends org.hl7.fhir.r4.model.Resource> T fetchResourceWithException(Class<T> theClass, String theUri) throws FHIRException { 412 T retVal = fetchResource(theClass, theUri); 413 if (retVal == null) { 414 throw new FHIRException("Could not find resource: " + theUri); 415 } 416 return retVal; 417 } 418 419 @Override 420 public org.hl7.fhir.r4.model.Resource fetchResourceById(String theType, String theUri) { 421 throw new UnsupportedOperationException(); 422 } 423 424 @Override 425 public <T extends org.hl7.fhir.r4.model.Resource> boolean hasResource(Class<T> theClass_, String theUri) { 426 throw new UnsupportedOperationException(); 427 } 428 429 @Override 430 public void cacheResource(org.hl7.fhir.r4.model.Resource theRes) throws FHIRException { 431 throw new UnsupportedOperationException(); 432 } 433 434 @Override 435 public Set<String> getResourceNamesAsSet() { 436 return myCtx.getResourceNames(); 437 } 438 439 @Override 440 public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent theBinding, boolean theCacheOk, boolean theHeiarchical) throws FHIRException { 441 throw new UnsupportedOperationException(); 442 } 443 444}