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