
001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.util; 021 022import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 025import ca.uhn.fhir.context.FhirContext; 026import ca.uhn.fhir.context.FhirVersionEnum; 027import ca.uhn.fhir.context.RuntimeResourceDefinition; 028import ca.uhn.fhir.i18n.Msg; 029import ca.uhn.fhir.model.api.StorageResponseCodeEnum; 030import ca.uhn.fhir.rest.api.Constants; 031import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 032import jakarta.annotation.Nullable; 033import org.hl7.fhir.instance.model.api.IBase; 034import org.hl7.fhir.instance.model.api.IBaseCoding; 035import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 036import org.hl7.fhir.instance.model.api.IBaseResource; 037import org.hl7.fhir.instance.model.api.ICompositeType; 038import org.hl7.fhir.instance.model.api.IPrimitiveType; 039 040import java.util.List; 041 042import static org.apache.commons.lang3.StringUtils.isNotBlank; 043 044/** 045 * Utilities for dealing with OperationOutcome resources across various model versions 046 */ 047public class OperationOutcomeUtil { 048 049 public static final String OO_SEVERITY_ERROR = "error"; 050 public static final String OO_SEVERITY_INFO = "information"; 051 public static final String OO_SEVERITY_WARN = "warning"; 052 public static final String OO_ISSUE_CODE_INFORMATIONAL = "informational"; 053 /** 054 * OperationOutcome.issue.code value: A required element is missing. 055 */ 056 public static final String OO_ISSUE_CODE_REQUIRED = "required"; 057 058 /** 059 * Note: This code was added in FHIR R5, so the {@link #addIssue(FhirContext, IBaseOperationOutcome, String, String, String, String) addIssue} 060 * methods here will automatically convert it to {@link #OO_ISSUE_CODE_INFORMATIONAL} for 061 * previous versions of FHIR. 062 * 063 * @since 8.6.0 064 */ 065 public static final String OO_ISSUE_CODE_SUCCESS = "success"; 066 067 /** 068 * @since 8.6.0 069 */ 070 public static final String OO_ISSUE_CODE_PROCESSING = "processing"; 071 072 /** 073 * Add an issue to an OperationOutcome 074 * 075 * @param theCtx The fhir context 076 * @param theOperationOutcome The OO resource to add to 077 * @param theSeverity The severity (fatal | error | warning | information) 078 * @param theDiagnostics The diagnostics string (this was called "details" in FHIR DSTU2 but was renamed to diagnostics in DSTU3) 079 * @param theCode A code, such as {@link #OO_ISSUE_CODE_INFORMATIONAL} or {@link #OO_ISSUE_CODE_SUCCESS} 080 * @return Returns the newly added issue 081 */ 082 public static IBase addIssue( 083 FhirContext theCtx, 084 IBaseOperationOutcome theOperationOutcome, 085 String theSeverity, 086 String theDiagnostics, 087 String theLocation, 088 String theCode) { 089 return addIssue( 090 theCtx, theOperationOutcome, theSeverity, theDiagnostics, theLocation, theCode, null, null, null); 091 } 092 093 public static IBase addIssue( 094 FhirContext theCtx, 095 IBaseOperationOutcome theOperationOutcome, 096 String theSeverity, 097 String theDiagnostics, 098 String theLocation, 099 String theCode, 100 @Nullable String theDetailSystem, 101 @Nullable String theDetailCode, 102 @Nullable String theDetailDescription) { 103 IBase issue = createIssue(theCtx, theOperationOutcome); 104 populateDetails( 105 theCtx, 106 issue, 107 theSeverity, 108 theDiagnostics, 109 theLocation, 110 theCode, 111 theDetailSystem, 112 theDetailCode, 113 theDetailDescription); 114 return issue; 115 } 116 117 private static IBase createIssue(FhirContext theCtx, IBaseResource theOutcome) { 118 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 119 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 120 BaseRuntimeElementCompositeDefinition<?> issueElement = 121 (BaseRuntimeElementCompositeDefinition<?>) issueChild.getChildByName("issue"); 122 123 IBase issue = issueElement.newInstance(); 124 issueChild.getMutator().addValue(theOutcome, issue); 125 return issue; 126 } 127 128 /** 129 * @deprecated Use {@link #getFirstIssueDiagnostics(FhirContext, IBaseOperationOutcome)} instead. This 130 * method has always been misnamed for historical reasons. 131 */ 132 @Deprecated(forRemoval = true, since = "8.2.0") 133 public static String getFirstIssueDetails(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 134 return getFirstIssueDiagnostics(theCtx, theOutcome); 135 } 136 137 public static String getFirstIssueDiagnostics(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 138 return getIssueStringPart(theCtx, theOutcome, "diagnostics", 0); 139 } 140 141 public static String getIssueDiagnostics(FhirContext theCtx, IBaseOperationOutcome theOutcome, int theIndex) { 142 return getIssueStringPart(theCtx, theOutcome, "diagnostics", theIndex); 143 } 144 145 public static String getFirstIssueLocation(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 146 return getIssueStringPart(theCtx, theOutcome, "location", 0); 147 } 148 149 private static String getIssueStringPart( 150 FhirContext theCtx, IBaseOperationOutcome theOutcome, String theName, int theIndex) { 151 if (theOutcome == null) { 152 return null; 153 } 154 155 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 156 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 157 158 List<IBase> issues = issueChild.getAccessor().getValues(theOutcome); 159 if (issues.size() <= theIndex) { 160 return null; 161 } 162 163 IBase issue = issues.get(theIndex); 164 BaseRuntimeElementCompositeDefinition<?> issueElement = 165 (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(issue.getClass()); 166 BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName(theName); 167 168 List<IBase> details = detailsChild.getAccessor().getValues(issue); 169 if (details.isEmpty()) { 170 return null; 171 } 172 return ((IPrimitiveType<?>) details.get(0)).getValueAsString(); 173 } 174 175 /** 176 * Returns true if the given OperationOutcome has 1 or more Operation.issue repetitions 177 */ 178 public static boolean hasIssues(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 179 if (theOutcome == null) { 180 return false; 181 } 182 return getIssueCount(theCtx, theOutcome) > 0; 183 } 184 185 public static int getIssueCount(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 186 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 187 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 188 return issueChild.getAccessor().getValues(theOutcome).size(); 189 } 190 191 public static boolean hasIssuesOfSeverity( 192 FhirContext theCtx, IBaseOperationOutcome theOutcome, String theSeverity) { 193 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 194 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 195 List<IBase> issues = issueChild.getAccessor().getValues(theOutcome); 196 197 if (issues.isEmpty()) { 198 return false; // if there are no issues at all, there are no issues of the required severity 199 } 200 201 IBase firstIssue = issues.get(0); 202 BaseRuntimeElementCompositeDefinition<?> issueElement = 203 (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(firstIssue.getClass()); 204 BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity"); 205 206 return issues.stream() 207 .flatMap(t -> severityChild.getAccessor().getValues(t).stream()) 208 .map(t -> (IPrimitiveType<?>) t) 209 .map(IPrimitiveType::getValueAsString) 210 .anyMatch(theSeverity::equals); 211 } 212 213 public static IBaseOperationOutcome newInstance(FhirContext theCtx) { 214 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition("OperationOutcome"); 215 try { 216 return (IBaseOperationOutcome) ooDef.getImplementingClass().newInstance(); 217 } catch (InstantiationException e) { 218 throw new InternalErrorException(Msg.code(1803) + "Unable to instantiate OperationOutcome", e); 219 } catch (IllegalAccessException e) { 220 throw new InternalErrorException(Msg.code(1804) + "Unable to instantiate OperationOutcome", e); 221 } 222 } 223 224 private static void populateDetails( 225 FhirContext theCtx, 226 IBase theIssue, 227 String theSeverity, 228 String theDiagnostics, 229 String theLocation, 230 String theCode, 231 String theDetailSystem, 232 String theDetailCode, 233 String theDetailDescription) { 234 BaseRuntimeElementCompositeDefinition<?> issueElement = 235 (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(theIssue.getClass()); 236 BaseRuntimeChildDefinition diagnosticsChild; 237 diagnosticsChild = issueElement.getChildByName("diagnostics"); 238 239 BaseRuntimeChildDefinition codeChild = issueElement.getChildByName("code"); 240 IPrimitiveType<?> codeElem = (IPrimitiveType<?>) 241 codeChild.getChildByName("code").newInstance(codeChild.getInstanceConstructorArguments()); 242 String code = theCode; 243 if (theCtx.getVersion().getVersion().isOlderThan(FhirVersionEnum.R5) && "success".equals(code)) { 244 // "success" was added in R5 so we switch back to "informational" for older versions 245 code = "informational"; 246 } 247 248 codeElem.setValueAsString(code); 249 codeChild.getMutator().addValue(theIssue, codeElem); 250 251 BaseRuntimeElementDefinition<?> stringDef = diagnosticsChild.getChildByName(diagnosticsChild.getElementName()); 252 BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity"); 253 254 IPrimitiveType<?> severityElem = (IPrimitiveType<?>) 255 severityChild.getChildByName("severity").newInstance(severityChild.getInstanceConstructorArguments()); 256 severityElem.setValueAsString(theSeverity); 257 severityChild.getMutator().addValue(theIssue, severityElem); 258 259 IPrimitiveType<?> string = (IPrimitiveType<?>) stringDef.newInstance(); 260 string.setValueAsString(theDiagnostics); 261 diagnosticsChild.getMutator().setValue(theIssue, string); 262 263 addLocationToIssue(theCtx, theIssue, theLocation); 264 265 if (isNotBlank(theDetailSystem)) { 266 BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName("details"); 267 if (detailsChild != null) { 268 BaseRuntimeElementDefinition<?> codeableConceptDef = theCtx.getElementDefinition("CodeableConcept"); 269 IBase codeableConcept = codeableConceptDef.newInstance(); 270 271 BaseRuntimeElementDefinition<?> codingDef = theCtx.getElementDefinition("Coding"); 272 IBaseCoding coding = (IBaseCoding) codingDef.newInstance(); 273 coding.setSystem(theDetailSystem); 274 coding.setCode(theDetailCode); 275 coding.setDisplay(theDetailDescription); 276 277 codeableConceptDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding); 278 279 detailsChild.getMutator().addValue(theIssue, codeableConcept); 280 } 281 } 282 } 283 284 public static void addLocationToIssue(FhirContext theContext, IBase theIssue, String theLocation) { 285 if (isNotBlank(theLocation)) { 286 BaseRuntimeElementCompositeDefinition<?> issueElement = 287 (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theIssue.getClass()); 288 BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location"); 289 IPrimitiveType<?> locationElem = (IPrimitiveType<?>) locationChild 290 .getChildByName("location") 291 .newInstance(locationChild.getInstanceConstructorArguments()); 292 locationElem.setValueAsString(theLocation); 293 locationChild.getMutator().addValue(theIssue, locationElem); 294 } 295 } 296 297 /** 298 * Given an instance of <code>OperationOutcome.issue</code>, adds a new instance of 299 * <code>OperationOutcome.issue.expression</code> with the given string value. 300 * 301 * @param theContext The FhirContext for the appropriate FHIR version 302 * @param theIssue The <code>OperationOutcome.issue</code> to add to 303 * @param theLocationExpression The string to use as content 304 */ 305 public static void addExpressionToIssue(FhirContext theContext, IBase theIssue, String theLocationExpression) { 306 if (isNotBlank(theLocationExpression) 307 && theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 308 BaseRuntimeElementCompositeDefinition<?> issueElement = 309 (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theIssue.getClass()); 310 BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("expression"); 311 IPrimitiveType<?> locationElem = (IPrimitiveType<?>) locationChild 312 .getChildByName("expression") 313 .newInstance(locationChild.getInstanceConstructorArguments()); 314 locationElem.setValueAsString(theLocationExpression); 315 locationChild.getMutator().addValue(theIssue, locationElem); 316 } 317 } 318 319 public static IBase addIssueWithMessageId( 320 FhirContext myCtx, 321 IBaseOperationOutcome theOperationOutcome, 322 String theSeverity, 323 String theMessage, 324 String theMessageId, 325 String theLocation, 326 String theCode) { 327 IBase issue = addIssue(myCtx, theOperationOutcome, theSeverity, theMessage, theLocation, theCode); 328 if (isNotBlank(theMessageId)) { 329 addDetailsToIssue(myCtx, issue, Constants.JAVA_VALIDATOR_DETAILS_SYSTEM, theMessageId); 330 } 331 332 return issue; 333 } 334 335 public static void addDetailsToIssue(FhirContext theFhirContext, IBase theIssue, String theSystem, String theCode) { 336 addDetailsToIssue(theFhirContext, theIssue, theSystem, theCode, null); 337 } 338 339 public static void addDetailsToIssue( 340 FhirContext theFhirContext, IBase theIssue, String theSystem, String theCode, String theText) { 341 BaseRuntimeElementCompositeDefinition<?> issueElement = 342 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition(theIssue.getClass()); 343 BaseRuntimeChildDefinition detailsChildDef = issueElement.getChildByName("details"); 344 BaseRuntimeElementCompositeDefinition<?> ccDef = 345 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("CodeableConcept"); 346 ICompositeType codeableConcept = (ICompositeType) ccDef.newInstance(); 347 348 if (isNotBlank(theSystem) || isNotBlank(theCode)) { 349 BaseRuntimeElementCompositeDefinition<?> codingDef = 350 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("Coding"); 351 ICompositeType coding = (ICompositeType) codingDef.newInstance(); 352 353 // System 354 if (isNotBlank(theSystem)) { 355 IPrimitiveType<?> system = (IPrimitiveType<?>) 356 theFhirContext.getElementDefinition("uri").newInstance(); 357 system.setValueAsString(theSystem); 358 codingDef.getChildByName("system").getMutator().addValue(coding, system); 359 } 360 361 // Code 362 if (isNotBlank(theCode)) { 363 IPrimitiveType<?> code = (IPrimitiveType<?>) 364 theFhirContext.getElementDefinition("code").newInstance(); 365 code.setValueAsString(theCode); 366 codingDef.getChildByName("code").getMutator().addValue(coding, code); 367 } 368 369 ccDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding); 370 } 371 372 if (isNotBlank(theText)) { 373 IPrimitiveType<?> textElem = (IPrimitiveType<?>) 374 ccDef.getChildByName("text").getChildByName("text").newInstance(theText); 375 ccDef.getChildByName("text").getMutator().addValue(codeableConcept, textElem); 376 } 377 378 detailsChildDef.getMutator().addValue(theIssue, codeableConcept); 379 } 380 381 public static void addIssueLineExtensionToIssue(FhirContext theCtx, IBase theIssue, String theLine) { 382 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 383 ExtensionUtil.setExtension( 384 theCtx, 385 theIssue, 386 "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line", 387 "integer", 388 theLine); 389 } 390 } 391 392 public static void addIssueColExtensionToIssue(FhirContext theCtx, IBase theIssue, String theColumn) { 393 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 394 ExtensionUtil.setExtension( 395 theCtx, 396 theIssue, 397 "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col", 398 "integer", 399 theColumn); 400 } 401 } 402 403 public static void addMessageIdExtensionToIssue(FhirContext theCtx, IBase theIssue, String theMessageId) { 404 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 405 ExtensionUtil.setExtension( 406 theCtx, 407 theIssue, 408 "http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id", 409 "string", 410 theMessageId); 411 } 412 } 413 414 public static IBaseOperationOutcome createOperationOutcome( 415 String theSeverity, 416 String theMessage, 417 String theCode, 418 FhirContext theFhirContext, 419 @Nullable StorageResponseCodeEnum theStorageResponseCode) { 420 IBaseOperationOutcome oo = newInstance(theFhirContext); 421 String detailSystem = null; 422 String detailCode = null; 423 String detailDescription = null; 424 if (theStorageResponseCode != null) { 425 detailSystem = theStorageResponseCode.getSystem(); 426 detailCode = theStorageResponseCode.getCode(); 427 detailDescription = theStorageResponseCode.getDisplay(); 428 } 429 addIssue( 430 theFhirContext, 431 oo, 432 theSeverity, 433 theMessage, 434 null, 435 theCode, 436 detailSystem, 437 detailCode, 438 detailDescription); 439 return oo; 440 } 441}