001package ca.uhn.fhir.context.support; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2020 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 025import ca.uhn.fhir.util.ParametersUtil; 026import org.hl7.fhir.instance.model.api.IBase; 027import org.hl7.fhir.instance.model.api.IBaseParameters; 028import org.hl7.fhir.instance.model.api.IBaseResource; 029import org.hl7.fhir.instance.model.api.IPrimitiveType; 030 031import javax.annotation.Nonnull; 032import java.util.ArrayList; 033import java.util.Collections; 034import java.util.List; 035import java.util.Set; 036import java.util.stream.Collectors; 037 038import static org.apache.commons.lang3.StringUtils.isNotBlank; 039 040/** 041 * This interface is a version-independent representation of the 042 * various functions that can be provided by validation and terminology 043 * services. 044 * <p> 045 * Implementations are not required to implement all of the functions 046 * in this interface; in fact it is expected that most won't. Any 047 * methods which are not implemented may simply return <code>null</code> 048 * and calling code is expected to be able to handle this. 049 * </p> 050 */ 051public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST> { 052 053 /** 054 * Expands the given portion of a ValueSet 055 * 056 * @param theInclude The portion to include 057 * @return The expansion 058 */ 059 EVS_OUT expandValueSet(FhirContext theContext, EVS_IN theInclude); 060 061 /** 062 * Load and return all conformance resources associated with this 063 * validation support module. This method may return null if it doesn't 064 * make sense for a given module. 065 */ 066 List<IBaseResource> fetchAllConformanceResources(FhirContext theContext); 067 068 /** 069 * Load and return all possible structure definitions 070 */ 071 List<SDT> fetchAllStructureDefinitions(FhirContext theContext); 072 073 /** 074 * Fetch a code system by ID 075 * 076 * @param theSystem The code system 077 * @return The valueset (must not be null, but can be an empty ValueSet) 078 */ 079 CST fetchCodeSystem(FhirContext theContext, String theSystem); 080 081 /** 082 * Loads a resource needed by the validation (a StructureDefinition, or a 083 * ValueSet) 084 * 085 * @param theContext The HAPI FHIR Context object current in use by the validator 086 * @param theClass The type of the resource to load 087 * @param theUri The resource URI 088 * @return Returns the resource, or <code>null</code> if no resource with the 089 * given URI can be found 090 */ 091 <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri); 092 093 SDT fetchStructureDefinition(FhirContext theCtx, String theUrl); 094 095 /** 096 * Returns <code>true</code> if codes in the given code system can be expanded 097 * or validated 098 * 099 * @param theSystem The URI for the code system, e.g. <code>"http://loinc.org"</code> 100 * @return Returns <code>true</code> if codes in the given code system can be 101 * validated 102 */ 103 boolean isCodeSystemSupported(FhirContext theContext, String theSystem); 104 105 /** 106 * Fetch the given ValueSet by URL 107 */ 108 IBaseResource fetchValueSet(FhirContext theContext, String theValueSetUrl); 109 110 /** 111 * Validates that the given code exists and if possible returns a display 112 * name. This method is called to check codes which are found in "example" 113 * binding fields (e.g. <code>Observation.code</code> in the default profile. 114 * 115 * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>" 116 * @param theCode The code, e.g. "<code>1234-5</code>" 117 * @param theDisplay The display name, if it should also be validated 118 * @return Returns a validation result object 119 */ 120 CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl); 121 122 /** 123 * Validates that the given code exists and if possible returns a display 124 * name. This method is called to check codes which are found in "example" 125 * binding fields (e.g. <code>Observation.code</code> in the default profile. 126 * 127 * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>" 128 * @param theCode The code, e.g. "<code>1234-5</code>" 129 * @param theDisplay The display name, if it should also be validated 130 * @param theValueSet The ValueSet to validate against. Must not be null, and must be a ValueSet resource. 131 * @return Returns a validation result object, or <code>null</code> if this validation support module can not handle this kind of request 132 */ 133 default CodeValidationResult validateCodeInValueSet(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { return null; } 134 135 /** 136 * Look up a code using the system and code value 137 * 138 * @param theContext The FHIR context 139 * @param theSystem The CodeSystem URL 140 * @param theCode The code 141 */ 142 LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode); 143 144 class ConceptDesignation { 145 private String myLanguage; 146 private String myUseSystem; 147 private String myUseCode; 148 private String myUseDisplay; 149 private String myValue; 150 151 public String getLanguage() { 152 return myLanguage; 153 } 154 155 public ConceptDesignation setLanguage(String theLanguage) { 156 myLanguage = theLanguage; 157 return this; 158 } 159 160 public String getUseSystem() { 161 return myUseSystem; 162 } 163 164 public ConceptDesignation setUseSystem(String theUseSystem) { 165 myUseSystem = theUseSystem; 166 return this; 167 } 168 169 public String getUseCode() { 170 return myUseCode; 171 } 172 173 public ConceptDesignation setUseCode(String theUseCode) { 174 myUseCode = theUseCode; 175 return this; 176 } 177 178 public String getUseDisplay() { 179 return myUseDisplay; 180 } 181 182 public ConceptDesignation setUseDisplay(String theUseDisplay) { 183 myUseDisplay = theUseDisplay; 184 return this; 185 } 186 187 public String getValue() { 188 return myValue; 189 } 190 191 public ConceptDesignation setValue(String theValue) { 192 myValue = theValue; 193 return this; 194 } 195 } 196 197 abstract class BaseConceptProperty { 198 private final String myPropertyName; 199 200 /** 201 * Constructor 202 */ 203 protected BaseConceptProperty(String thePropertyName) { 204 myPropertyName = thePropertyName; 205 } 206 207 public String getPropertyName() { 208 return myPropertyName; 209 } 210 } 211 212 class StringConceptProperty extends BaseConceptProperty { 213 private final String myValue; 214 215 /** 216 * Constructor 217 * 218 * @param theName The name 219 */ 220 public StringConceptProperty(String theName, String theValue) { 221 super(theName); 222 myValue = theValue; 223 } 224 225 public String getValue() { 226 return myValue; 227 } 228 } 229 230 class CodingConceptProperty extends BaseConceptProperty { 231 private final String myCode; 232 private final String myCodeSystem; 233 private final String myDisplay; 234 235 /** 236 * Constructor 237 * 238 * @param theName The name 239 */ 240 public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) { 241 super(theName); 242 myCodeSystem = theCodeSystem; 243 myCode = theCode; 244 myDisplay = theDisplay; 245 } 246 247 public String getCode() { 248 return myCode; 249 } 250 251 public String getCodeSystem() { 252 return myCodeSystem; 253 } 254 255 public String getDisplay() { 256 return myDisplay; 257 } 258 } 259 260 class CodeValidationResult { 261 private IBase myDefinition; 262 private String myMessage; 263 private Enum mySeverity; 264 private String myCodeSystemName; 265 private String myCodeSystemVersion; 266 private List<BaseConceptProperty> myProperties; 267 private String myDisplay; 268 269 public CodeValidationResult(IBase theDefinition) { 270 this.myDefinition = theDefinition; 271 } 272 273 public CodeValidationResult(Enum theSeverity, String message) { 274 this.mySeverity = theSeverity; 275 this.myMessage = message; 276 } 277 278 public CodeValidationResult(Enum theSeverity, String theMessage, IBase theDefinition, String theDisplay) { 279 this.mySeverity = theSeverity; 280 this.myMessage = theMessage; 281 this.myDefinition = theDefinition; 282 this.myDisplay = theDisplay; 283 } 284 285 public String getDisplay() { 286 return myDisplay; 287 } 288 289 public IBase asConceptDefinition() { 290 return myDefinition; 291 } 292 293 String getCodeSystemName() { 294 return myCodeSystemName; 295 } 296 297 public void setCodeSystemName(String theCodeSystemName) { 298 myCodeSystemName = theCodeSystemName; 299 } 300 301 public String getCodeSystemVersion() { 302 return myCodeSystemVersion; 303 } 304 305 public void setCodeSystemVersion(String theCodeSystemVersion) { 306 myCodeSystemVersion = theCodeSystemVersion; 307 } 308 309 public String getMessage() { 310 return myMessage; 311 } 312 313 public List<BaseConceptProperty> getProperties() { 314 return myProperties; 315 } 316 317 public void setProperties(List<BaseConceptProperty> theProperties) { 318 myProperties = theProperties; 319 } 320 321 public Enum getSeverity() { 322 return mySeverity; 323 } 324 325 public boolean isOk() { 326 return myDefinition != null; 327 } 328 329 public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) { 330 LookupCodeResult retVal = new LookupCodeResult(); 331 retVal.setSearchedForSystem(theSearchedForSystem); 332 retVal.setSearchedForCode(theSearchedForCode); 333 if (isOk()) { 334 retVal.setFound(true); 335 retVal.setCodeDisplay(myDisplay); 336 retVal.setCodeSystemDisplayName(getCodeSystemName()); 337 retVal.setCodeSystemVersion(getCodeSystemVersion()); 338 } 339 return retVal; 340 } 341 342 } 343 344 class LookupCodeResult { 345 346 private String myCodeDisplay; 347 private boolean myCodeIsAbstract; 348 private String myCodeSystemDisplayName; 349 private String myCodeSystemVersion; 350 private boolean myFound; 351 private String mySearchedForCode; 352 private String mySearchedForSystem; 353 private List<IContextValidationSupport.BaseConceptProperty> myProperties; 354 private List<ConceptDesignation> myDesignations; 355 356 /** 357 * Constructor 358 */ 359 public LookupCodeResult() { 360 super(); 361 } 362 363 public List<BaseConceptProperty> getProperties() { 364 if (myProperties == null) { 365 myProperties = new ArrayList<>(); 366 } 367 return myProperties; 368 } 369 370 public void setProperties(List<IContextValidationSupport.BaseConceptProperty> theProperties) { 371 myProperties = theProperties; 372 } 373 374 @Nonnull 375 public List<ConceptDesignation> getDesignations() { 376 if (myDesignations == null) { 377 myDesignations = new ArrayList<>(); 378 } 379 return myDesignations; 380 } 381 382 public String getCodeDisplay() { 383 return myCodeDisplay; 384 } 385 386 public void setCodeDisplay(String theCodeDisplay) { 387 myCodeDisplay = theCodeDisplay; 388 } 389 390 public String getCodeSystemDisplayName() { 391 return myCodeSystemDisplayName; 392 } 393 394 public void setCodeSystemDisplayName(String theCodeSystemDisplayName) { 395 myCodeSystemDisplayName = theCodeSystemDisplayName; 396 } 397 398 public String getCodeSystemVersion() { 399 return myCodeSystemVersion; 400 } 401 402 public void setCodeSystemVersion(String theCodeSystemVersion) { 403 myCodeSystemVersion = theCodeSystemVersion; 404 } 405 406 public String getSearchedForCode() { 407 return mySearchedForCode; 408 } 409 410 public LookupCodeResult setSearchedForCode(String theSearchedForCode) { 411 mySearchedForCode = theSearchedForCode; 412 return this; 413 } 414 415 public String getSearchedForSystem() { 416 return mySearchedForSystem; 417 } 418 419 public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) { 420 mySearchedForSystem = theSearchedForSystem; 421 return this; 422 } 423 424 public boolean isCodeIsAbstract() { 425 return myCodeIsAbstract; 426 } 427 428 public void setCodeIsAbstract(boolean theCodeIsAbstract) { 429 myCodeIsAbstract = theCodeIsAbstract; 430 } 431 432 public boolean isFound() { 433 return myFound; 434 } 435 436 public LookupCodeResult setFound(boolean theFound) { 437 myFound = theFound; 438 return this; 439 } 440 441 public void throwNotFoundIfAppropriate() { 442 if (isFound() == false) { 443 throw new ResourceNotFoundException("Unable to find code[" + getSearchedForCode() + "] in system[" + getSearchedForSystem() + "]"); 444 } 445 } 446 447 public IBaseParameters toParameters(FhirContext theContext, List<? extends IPrimitiveType<String>> theProperties) { 448 449 IBaseParameters retVal = ParametersUtil.newInstance(theContext); 450 if (isNotBlank(getCodeSystemDisplayName())) { 451 ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName()); 452 } 453 if (isNotBlank(getCodeSystemVersion())) { 454 ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion()); 455 } 456 ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay()); 457 ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract()); 458 459 if (myProperties != null) { 460 461 Set<String> properties = Collections.emptySet(); 462 if (theProperties != null) { 463 properties = theProperties 464 .stream() 465 .map(IPrimitiveType::getValueAsString) 466 .collect(Collectors.toSet()); 467 } 468 469 for (IContextValidationSupport.BaseConceptProperty next : myProperties) { 470 471 if (!properties.isEmpty()) { 472 if (!properties.contains(next.getPropertyName())) { 473 continue; 474 } 475 } 476 477 IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property"); 478 ParametersUtil.addPartCode(theContext, property, "code", next.getPropertyName()); 479 480 if (next instanceof IContextValidationSupport.StringConceptProperty) { 481 IContextValidationSupport.StringConceptProperty prop = (IContextValidationSupport.StringConceptProperty) next; 482 ParametersUtil.addPartString(theContext, property, "value", prop.getValue()); 483 } else if (next instanceof IContextValidationSupport.CodingConceptProperty) { 484 IContextValidationSupport.CodingConceptProperty prop = (IContextValidationSupport.CodingConceptProperty) next; 485 ParametersUtil.addPartCoding(theContext, property, "value", prop.getCodeSystem(), prop.getCode(), prop.getDisplay()); 486 } else { 487 throw new IllegalStateException("Don't know how to handle " + next.getClass()); 488 } 489 } 490 } 491 492 if (myDesignations != null) { 493 for (ConceptDesignation next : myDesignations) { 494 495 IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation"); 496 ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage()); 497 ParametersUtil.addPartCoding(theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay()); 498 ParametersUtil.addPartString(theContext, property, "value", next.getValue()); 499 } 500 } 501 502 return retVal; 503 } 504 505 public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) { 506 return new LookupCodeResult() 507 .setFound(false) 508 .setSearchedForSystem(theSearchedForSystem) 509 .setSearchedForCode(theSearchedForCode); 510 } 511 } 512 513}