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