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