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.ValueSetExpansionComponent; 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.TranslationServices; 031import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 032 033import java.util.*; 034import java.util.concurrent.TimeUnit; 035 036import static org.apache.commons.lang3.StringUtils.isBlank; 037import static org.apache.commons.lang3.StringUtils.isNotBlank; 038 039public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander, ValueSetExpanderFactory { 040 private final FhirContext myCtx; 041 private final Cache<String, Resource> myFetchedResourceCache; 042 private IValidationSupport myValidationSupport; 043 private ExpansionProfile myExpansionProfile; 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, 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<String>(); 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<String>(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(null, 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 if (expandedValueSet == null) { 234 expandedValueSet = expand(theVs, null); 235 } 236 237 for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) { 238 String nextCode = next.getCode(); 239 if (!caseSensitive) { 240 nextCode = nextCode.toUpperCase(); 241 } 242 243 if (nextCode.equals(wantCode)) { 244 if (theSystem == null || next.getSystem().equals(theSystem)) { 245 ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); 246 definition.setCode(next.getCode()); 247 definition.setDisplay(next.getDisplay()); 248 ValidationResult retVal = new ValidationResult(definition); 249 return retVal; 250 } 251 } 252 } 253 254 return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]"); 255 } 256 257 @Override 258 @CoverageIgnore 259 public List<MetadataResource> allConformanceResources() { 260 throw new UnsupportedOperationException(); 261 } 262 263 @Override 264 @CoverageIgnore 265 public boolean hasCache() { 266 throw new UnsupportedOperationException(); 267 } 268 269 @Override 270 public ValueSetExpansionOutcome expand(ValueSet theSource, ExpansionProfile theProfile) { 271 ValueSetExpansionOutcome vso; 272 try { 273 vso = getExpander().expand(theSource, theProfile); 274 } catch (InvalidRequestException e) { 275 throw e; 276 } catch (Exception e) { 277 throw new InternalErrorException(e); 278 } 279 if (vso.getError() != null) { 280 throw new InvalidRequestException(vso.getError()); 281 } else { 282 return vso; 283 } 284 } 285 286 @Override 287 public ExpansionProfile getExpansionProfile() { 288 return myExpansionProfile; 289 } 290 291 @Override 292 public void setExpansionProfile(ExpansionProfile theExpProfile) { 293 myExpansionProfile = theExpProfile; 294 } 295 296 @Override 297 public ValueSetExpansionOutcome expandVS(ValueSet theSource, boolean theCacheOk, boolean theHeiarchical) { 298 throw new UnsupportedOperationException(); 299 } 300 301 @Override 302 public ValueSetExpansionComponent expandVS(ConceptSetComponent theInc, boolean theHeiarchical) throws TerminologyServiceException { 303 return myValidationSupport.expandValueSet(myCtx, theInc); 304 } 305 306 @Override 307 public void setLogger(ILoggingService theLogger) { 308 throw new UnsupportedOperationException(); 309 } 310 311 @Override 312 public String getVersion() { 313 return myCtx.getVersion().getVersion().getFhirVersionString(); 314 } 315 316 @Override 317 public UcumService getUcumService() { 318 throw new UnsupportedOperationException(); 319 } 320 321 @Override 322 public boolean isNoTerminologyServer() { 323 return false; 324 } 325 326 @Override 327 public TranslationServices translator() { 328 throw new UnsupportedOperationException(); 329 } 330 331 @Override 332 public List<StructureMap> listTransforms() { 333 throw new UnsupportedOperationException(); 334 } 335 336 @Override 337 public StructureMap getTransform(String url) { 338 throw new UnsupportedOperationException(); 339 } 340 341 @Override 342 public List<String> getTypeNames() { 343 throw new UnsupportedOperationException(); 344 } 345 346 @Override 347 public <T extends org.hl7.fhir.r4.model.Resource> T fetchResource(Class<T> theClass, String theUri) { 348 if (myValidationSupport == null) { 349 return null; 350 } else { 351 @SuppressWarnings("unchecked") 352 T retVal = (T) myFetchedResourceCache.get(theUri, t -> { 353 return myValidationSupport.fetchResource(myCtx, theClass, theUri); 354 }); 355 return retVal; 356 } 357 } 358 359 @Override 360 public <T extends org.hl7.fhir.r4.model.Resource> T fetchResourceWithException(Class<T> theClass, String theUri) throws FHIRException { 361 T retVal = fetchResource(theClass, theUri); 362 if (retVal == null) { 363 throw new FHIRException("Could not find resource: " + theUri); 364 } 365 return retVal; 366 } 367 368 @Override 369 public org.hl7.fhir.r4.model.Resource fetchResourceById(String theType, String theUri) { 370 throw new UnsupportedOperationException(); 371 } 372 373 @Override 374 public <T extends org.hl7.fhir.r4.model.Resource> boolean hasResource(Class<T> theClass_, String theUri) { 375 throw new UnsupportedOperationException(); 376 } 377 378 @Override 379 public void cacheResource(org.hl7.fhir.r4.model.Resource theRes) throws FHIRException { 380 throw new UnsupportedOperationException(); 381 } 382 383 @Override 384 public Set<String> getResourceNamesAsSet() { 385 return myCtx.getResourceNames(); 386 } 387 388 @Override 389 public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent theBinding, boolean theCacheOk, boolean theHeiarchical) throws FHIRException { 390 throw new UnsupportedOperationException(); 391 } 392 393}