001package ca.uhn.fhir.context.support; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2019 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 * Validates that the given code exists and if possible returns a display 107 * name. This method is called to check codes which are found in "example" 108 * binding fields (e.g. <code>Observation.code</code> in the default profile. 109 * 110 * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>" 111 * @param theCode The code, e.g. "<code>1234-5</code>" 112 * @param theDisplay The display name, if it should also be validated 113 * @return Returns a validation result object 114 */ 115 CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay); 116 117 /** 118 * Look up a code using the system and code value 119 * 120 * @param theContext The FHIR context 121 * @param theSystem The CodeSystem URL 122 * @param theCode The code 123 */ 124 LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode); 125 126 class ConceptDesignation { 127 private String myLanguage; 128 private String myUseSystem; 129 private String myUseCode; 130 private String myUseDisplay; 131 private String myValue; 132 133 public String getLanguage() { 134 return myLanguage; 135 } 136 137 public ConceptDesignation setLanguage(String theLanguage) { 138 myLanguage = theLanguage; 139 return this; 140 } 141 142 public String getUseSystem() { 143 return myUseSystem; 144 } 145 146 public ConceptDesignation setUseSystem(String theUseSystem) { 147 myUseSystem = theUseSystem; 148 return this; 149 } 150 151 public String getUseCode() { 152 return myUseCode; 153 } 154 155 public ConceptDesignation setUseCode(String theUseCode) { 156 myUseCode = theUseCode; 157 return this; 158 } 159 160 public String getUseDisplay() { 161 return myUseDisplay; 162 } 163 164 public ConceptDesignation setUseDisplay(String theUseDisplay) { 165 myUseDisplay = theUseDisplay; 166 return this; 167 } 168 169 public String getValue() { 170 return myValue; 171 } 172 173 public ConceptDesignation setValue(String theValue) { 174 myValue = theValue; 175 return this; 176 } 177 } 178 179 abstract class BaseConceptProperty { 180 private final String myPropertyName; 181 182 /** 183 * Constructor 184 */ 185 protected BaseConceptProperty(String thePropertyName) { 186 myPropertyName = thePropertyName; 187 } 188 189 public String getPropertyName() { 190 return myPropertyName; 191 } 192 } 193 194 class StringConceptProperty extends BaseConceptProperty { 195 private final String myValue; 196 197 /** 198 * Constructor 199 * 200 * @param theName The name 201 */ 202 public StringConceptProperty(String theName, String theValue) { 203 super(theName); 204 myValue = theValue; 205 } 206 207 public String getValue() { 208 return myValue; 209 } 210 } 211 212 class CodingConceptProperty extends BaseConceptProperty { 213 private final String myCode; 214 private final String myCodeSystem; 215 private final String myDisplay; 216 217 /** 218 * Constructor 219 * 220 * @param theName The name 221 */ 222 public CodingConceptProperty(String theName, String theCodeSystem, String theCode, String theDisplay) { 223 super(theName); 224 myCodeSystem = theCodeSystem; 225 myCode = theCode; 226 myDisplay = theDisplay; 227 } 228 229 public String getCode() { 230 return myCode; 231 } 232 233 public String getCodeSystem() { 234 return myCodeSystem; 235 } 236 237 public String getDisplay() { 238 return myDisplay; 239 } 240 } 241 242 abstract class CodeValidationResult<CDCT, IST> { 243 private CDCT myDefinition; 244 private String myMessage; 245 private IST mySeverity; 246 private String myCodeSystemName; 247 private String myCodeSystemVersion; 248 private List<BaseConceptProperty> myProperties; 249 250 public CodeValidationResult(CDCT theNext) { 251 this.myDefinition = theNext; 252 } 253 254 public CodeValidationResult(IST severity, String message) { 255 this.mySeverity = severity; 256 this.myMessage = message; 257 } 258 259 public CodeValidationResult(IST severity, String message, CDCT definition) { 260 this.mySeverity = severity; 261 this.myMessage = message; 262 this.myDefinition = definition; 263 } 264 265 public CDCT asConceptDefinition() { 266 return myDefinition; 267 } 268 269 public String getCodeSystemName() { 270 return myCodeSystemName; 271 } 272 273 public void setCodeSystemName(String theCodeSystemName) { 274 myCodeSystemName = theCodeSystemName; 275 } 276 277 public String getCodeSystemVersion() { 278 return myCodeSystemVersion; 279 } 280 281 public void setCodeSystemVersion(String theCodeSystemVersion) { 282 myCodeSystemVersion = theCodeSystemVersion; 283 } 284 285 public String getMessage() { 286 return myMessage; 287 } 288 289 public List<BaseConceptProperty> getProperties() { 290 return myProperties; 291 } 292 293 public void setProperties(List<BaseConceptProperty> theProperties) { 294 myProperties = theProperties; 295 } 296 297 public IST getSeverity() { 298 return mySeverity; 299 } 300 301 public boolean isOk() { 302 return myDefinition != null; 303 } 304 305 public LookupCodeResult asLookupCodeResult(String theSearchedForSystem, String theSearchedForCode) { 306 LookupCodeResult retVal = new LookupCodeResult(); 307 retVal.setSearchedForSystem(theSearchedForSystem); 308 retVal.setSearchedForCode(theSearchedForCode); 309 if (isOk()) { 310 retVal.setFound(true); 311 retVal.setCodeDisplay(getDisplay()); 312 retVal.setCodeSystemDisplayName(getCodeSystemName()); 313 retVal.setCodeSystemVersion(getCodeSystemVersion()); 314 } 315 return retVal; 316 } 317 318 protected abstract String getDisplay(); 319 320 } 321 322 class LookupCodeResult { 323 324 private String myCodeDisplay; 325 private boolean myCodeIsAbstract; 326 private String myCodeSystemDisplayName; 327 private String myCodeSystemVersion; 328 private boolean myFound; 329 private String mySearchedForCode; 330 private String mySearchedForSystem; 331 private List<IContextValidationSupport.BaseConceptProperty> myProperties; 332 private List<ConceptDesignation> myDesignations; 333 334 /** 335 * Constructor 336 */ 337 public LookupCodeResult() { 338 super(); 339 } 340 341 public List<BaseConceptProperty> getProperties() { 342 if (myProperties == null) { 343 myProperties = new ArrayList<>(); 344 } 345 return myProperties; 346 } 347 348 public void setProperties(List<IContextValidationSupport.BaseConceptProperty> theProperties) { 349 myProperties = theProperties; 350 } 351 352 @Nonnull 353 public List<ConceptDesignation> getDesignations() { 354 if (myDesignations == null) { 355 myDesignations = new ArrayList<>(); 356 } 357 return myDesignations; 358 } 359 360 public String getCodeDisplay() { 361 return myCodeDisplay; 362 } 363 364 public void setCodeDisplay(String theCodeDisplay) { 365 myCodeDisplay = theCodeDisplay; 366 } 367 368 public String getCodeSystemDisplayName() { 369 return myCodeSystemDisplayName; 370 } 371 372 public void setCodeSystemDisplayName(String theCodeSystemDisplayName) { 373 myCodeSystemDisplayName = theCodeSystemDisplayName; 374 } 375 376 public String getCodeSystemVersion() { 377 return myCodeSystemVersion; 378 } 379 380 public void setCodeSystemVersion(String theCodeSystemVersion) { 381 myCodeSystemVersion = theCodeSystemVersion; 382 } 383 384 public String getSearchedForCode() { 385 return mySearchedForCode; 386 } 387 388 public LookupCodeResult setSearchedForCode(String theSearchedForCode) { 389 mySearchedForCode = theSearchedForCode; 390 return this; 391 } 392 393 public String getSearchedForSystem() { 394 return mySearchedForSystem; 395 } 396 397 public LookupCodeResult setSearchedForSystem(String theSearchedForSystem) { 398 mySearchedForSystem = theSearchedForSystem; 399 return this; 400 } 401 402 public boolean isCodeIsAbstract() { 403 return myCodeIsAbstract; 404 } 405 406 public void setCodeIsAbstract(boolean theCodeIsAbstract) { 407 myCodeIsAbstract = theCodeIsAbstract; 408 } 409 410 public boolean isFound() { 411 return myFound; 412 } 413 414 public LookupCodeResult setFound(boolean theFound) { 415 myFound = theFound; 416 return this; 417 } 418 419 public void throwNotFoundIfAppropriate() { 420 if (isFound() == false) { 421 throw new ResourceNotFoundException("Unable to find code[" + getSearchedForCode() + "] in system[" + getSearchedForSystem() + "]"); 422 } 423 } 424 425 public IBaseParameters toParameters(FhirContext theContext, List<? extends IPrimitiveType<String>> theProperties) { 426 427 IBaseParameters retVal = ParametersUtil.newInstance(theContext); 428 if (isNotBlank(getCodeSystemDisplayName())) { 429 ParametersUtil.addParameterToParametersString(theContext, retVal, "name", getCodeSystemDisplayName()); 430 } 431 if (isNotBlank(getCodeSystemVersion())) { 432 ParametersUtil.addParameterToParametersString(theContext, retVal, "version", getCodeSystemVersion()); 433 } 434 ParametersUtil.addParameterToParametersString(theContext, retVal, "display", getCodeDisplay()); 435 ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "abstract", isCodeIsAbstract()); 436 437 if (myProperties != null) { 438 439 Set<String> properties = Collections.emptySet(); 440 if (theProperties != null) { 441 properties = theProperties 442 .stream() 443 .map(IPrimitiveType::getValueAsString) 444 .collect(Collectors.toSet()); 445 } 446 447 for (IContextValidationSupport.BaseConceptProperty next : myProperties) { 448 449 if (!properties.isEmpty()) { 450 if (!properties.contains(next.getPropertyName())) { 451 continue; 452 } 453 } 454 455 IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "property"); 456 ParametersUtil.addPartCode(theContext, property, "code", next.getPropertyName()); 457 458 if (next instanceof IContextValidationSupport.StringConceptProperty) { 459 IContextValidationSupport.StringConceptProperty prop = (IContextValidationSupport.StringConceptProperty) next; 460 ParametersUtil.addPartString(theContext, property, "value", prop.getValue()); 461 } else if (next instanceof IContextValidationSupport.CodingConceptProperty) { 462 IContextValidationSupport.CodingConceptProperty prop = (IContextValidationSupport.CodingConceptProperty) next; 463 ParametersUtil.addPartCoding(theContext, property, "value", prop.getCodeSystem(), prop.getCode(), prop.getDisplay()); 464 } else { 465 throw new IllegalStateException("Don't know how to handle " + next.getClass()); 466 } 467 } 468 } 469 470 if (myDesignations != null) { 471 for (ConceptDesignation next : myDesignations) { 472 473 IBase property = ParametersUtil.addParameterToParameters(theContext, retVal, "designation"); 474 ParametersUtil.addPartCode(theContext, property, "language", next.getLanguage()); 475 ParametersUtil.addPartCoding(theContext, property, "use", next.getUseSystem(), next.getUseCode(), next.getUseDisplay()); 476 ParametersUtil.addPartString(theContext, property, "value", next.getValue()); 477 } 478 } 479 480 return retVal; 481 } 482 483 public static LookupCodeResult notFound(String theSearchedForSystem, String theSearchedForCode) { 484 return new LookupCodeResult() 485 .setFound(false) 486 .setSearchedForSystem(theSearchedForSystem) 487 .setSearchedForCode(theSearchedForCode); 488 } 489 } 490 491}