
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 /** 055 * Add an issue to an OperationOutcome 056 * 057 * @param theCtx The fhir context 058 * @param theOperationOutcome The OO resource to add to 059 * @param theSeverity The severity (fatal | error | warning | information) 060 * @param theDiagnostics The diagnostics string (this was called "details" in FHIR DSTU2 but was renamed to diagnostics in DSTU3) 061 * @param theCode 062 * @return Returns the newly added issue 063 */ 064 public static IBase addIssue( 065 FhirContext theCtx, 066 IBaseOperationOutcome theOperationOutcome, 067 String theSeverity, 068 String theDiagnostics, 069 String theLocation, 070 String theCode) { 071 return addIssue( 072 theCtx, theOperationOutcome, theSeverity, theDiagnostics, theLocation, theCode, null, null, null); 073 } 074 075 public static IBase addIssue( 076 FhirContext theCtx, 077 IBaseOperationOutcome theOperationOutcome, 078 String theSeverity, 079 String theDiagnostics, 080 String theLocation, 081 String theCode, 082 @Nullable String theDetailSystem, 083 @Nullable String theDetailCode, 084 @Nullable String theDetailDescription) { 085 IBase issue = createIssue(theCtx, theOperationOutcome); 086 populateDetails( 087 theCtx, 088 issue, 089 theSeverity, 090 theDiagnostics, 091 theLocation, 092 theCode, 093 theDetailSystem, 094 theDetailCode, 095 theDetailDescription); 096 return issue; 097 } 098 099 private static IBase createIssue(FhirContext theCtx, IBaseResource theOutcome) { 100 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 101 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 102 BaseRuntimeElementCompositeDefinition<?> issueElement = 103 (BaseRuntimeElementCompositeDefinition<?>) issueChild.getChildByName("issue"); 104 105 IBase issue = issueElement.newInstance(); 106 issueChild.getMutator().addValue(theOutcome, issue); 107 return issue; 108 } 109 110 /** 111 * @deprecated Use {@link #getFirstIssueDiagnostics(FhirContext, IBaseOperationOutcome)} instead. This 112 * method has always been misnamed for historical reasons. 113 */ 114 @Deprecated(forRemoval = true, since = "8.2.0") 115 public static String getFirstIssueDetails(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 116 return getFirstIssueDiagnostics(theCtx, theOutcome); 117 } 118 119 public static String getFirstIssueDiagnostics(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 120 return getIssueStringPart(theCtx, theOutcome, "diagnostics", 0); 121 } 122 123 public static String getIssueDiagnostics(FhirContext theCtx, IBaseOperationOutcome theOutcome, int theIndex) { 124 return getIssueStringPart(theCtx, theOutcome, "diagnostics", theIndex); 125 } 126 127 public static String getFirstIssueLocation(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 128 return getIssueStringPart(theCtx, theOutcome, "location", 0); 129 } 130 131 private static String getIssueStringPart( 132 FhirContext theCtx, IBaseOperationOutcome theOutcome, String theName, int theIndex) { 133 if (theOutcome == null) { 134 return null; 135 } 136 137 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 138 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 139 140 List<IBase> issues = issueChild.getAccessor().getValues(theOutcome); 141 if (issues.size() <= theIndex) { 142 return null; 143 } 144 145 IBase issue = issues.get(theIndex); 146 BaseRuntimeElementCompositeDefinition<?> issueElement = 147 (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(issue.getClass()); 148 BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName(theName); 149 150 List<IBase> details = detailsChild.getAccessor().getValues(issue); 151 if (details.isEmpty()) { 152 return null; 153 } 154 return ((IPrimitiveType<?>) details.get(0)).getValueAsString(); 155 } 156 157 /** 158 * Returns true if the given OperationOutcome has 1 or more Operation.issue repetitions 159 */ 160 public static boolean hasIssues(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 161 if (theOutcome == null) { 162 return false; 163 } 164 return getIssueCount(theCtx, theOutcome) > 0; 165 } 166 167 public static int getIssueCount(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 168 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 169 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 170 return issueChild.getAccessor().getValues(theOutcome).size(); 171 } 172 173 public static boolean hasIssuesOfSeverity( 174 FhirContext theCtx, IBaseOperationOutcome theOutcome, String theSeverity) { 175 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 176 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 177 List<IBase> issues = issueChild.getAccessor().getValues(theOutcome); 178 179 if (issues.isEmpty()) { 180 return false; // if there are no issues at all, there are no issues of the required severity 181 } 182 183 IBase firstIssue = issues.get(0); 184 BaseRuntimeElementCompositeDefinition<?> issueElement = 185 (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(firstIssue.getClass()); 186 BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity"); 187 188 return issues.stream() 189 .flatMap(t -> severityChild.getAccessor().getValues(t).stream()) 190 .map(t -> (IPrimitiveType<?>) t) 191 .map(IPrimitiveType::getValueAsString) 192 .anyMatch(theSeverity::equals); 193 } 194 195 public static IBaseOperationOutcome newInstance(FhirContext theCtx) { 196 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition("OperationOutcome"); 197 try { 198 return (IBaseOperationOutcome) ooDef.getImplementingClass().newInstance(); 199 } catch (InstantiationException e) { 200 throw new InternalErrorException(Msg.code(1803) + "Unable to instantiate OperationOutcome", e); 201 } catch (IllegalAccessException e) { 202 throw new InternalErrorException(Msg.code(1804) + "Unable to instantiate OperationOutcome", e); 203 } 204 } 205 206 private static void populateDetails( 207 FhirContext theCtx, 208 IBase theIssue, 209 String theSeverity, 210 String theDiagnostics, 211 String theLocation, 212 String theCode, 213 String theDetailSystem, 214 String theDetailCode, 215 String theDetailDescription) { 216 BaseRuntimeElementCompositeDefinition<?> issueElement = 217 (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(theIssue.getClass()); 218 BaseRuntimeChildDefinition diagnosticsChild; 219 diagnosticsChild = issueElement.getChildByName("diagnostics"); 220 221 BaseRuntimeChildDefinition codeChild = issueElement.getChildByName("code"); 222 IPrimitiveType<?> codeElem = (IPrimitiveType<?>) 223 codeChild.getChildByName("code").newInstance(codeChild.getInstanceConstructorArguments()); 224 codeElem.setValueAsString(theCode); 225 codeChild.getMutator().addValue(theIssue, codeElem); 226 227 BaseRuntimeElementDefinition<?> stringDef = diagnosticsChild.getChildByName(diagnosticsChild.getElementName()); 228 BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity"); 229 230 IPrimitiveType<?> severityElem = (IPrimitiveType<?>) 231 severityChild.getChildByName("severity").newInstance(severityChild.getInstanceConstructorArguments()); 232 severityElem.setValueAsString(theSeverity); 233 severityChild.getMutator().addValue(theIssue, severityElem); 234 235 IPrimitiveType<?> string = (IPrimitiveType<?>) stringDef.newInstance(); 236 string.setValueAsString(theDiagnostics); 237 diagnosticsChild.getMutator().setValue(theIssue, string); 238 239 addLocationToIssue(theCtx, theIssue, theLocation); 240 241 if (isNotBlank(theDetailSystem)) { 242 BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName("details"); 243 if (detailsChild != null) { 244 BaseRuntimeElementDefinition<?> codeableConceptDef = theCtx.getElementDefinition("CodeableConcept"); 245 IBase codeableConcept = codeableConceptDef.newInstance(); 246 247 BaseRuntimeElementDefinition<?> codingDef = theCtx.getElementDefinition("Coding"); 248 IBaseCoding coding = (IBaseCoding) codingDef.newInstance(); 249 coding.setSystem(theDetailSystem); 250 coding.setCode(theDetailCode); 251 coding.setDisplay(theDetailDescription); 252 253 codeableConceptDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding); 254 255 detailsChild.getMutator().addValue(theIssue, codeableConcept); 256 } 257 } 258 } 259 260 public static void addLocationToIssue(FhirContext theContext, IBase theIssue, String theLocation) { 261 if (isNotBlank(theLocation)) { 262 BaseRuntimeElementCompositeDefinition<?> issueElement = 263 (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theIssue.getClass()); 264 BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location"); 265 IPrimitiveType<?> locationElem = (IPrimitiveType<?>) locationChild 266 .getChildByName("location") 267 .newInstance(locationChild.getInstanceConstructorArguments()); 268 locationElem.setValueAsString(theLocation); 269 locationChild.getMutator().addValue(theIssue, locationElem); 270 } 271 } 272 273 /** 274 * Given an instance of <code>OperationOutcome.issue</code>, adds a new instance of 275 * <code>OperationOutcome.issue.expression</code> with the given string value. 276 * 277 * @param theContext The FhirContext for the appropriate FHIR version 278 * @param theIssue The <code>OperationOutcome.issue</code> to add to 279 * @param theLocationExpression The string to use as content 280 */ 281 public static void addExpressionToIssue(FhirContext theContext, IBase theIssue, String theLocationExpression) { 282 if (isNotBlank(theLocationExpression) 283 && theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) { 284 BaseRuntimeElementCompositeDefinition<?> issueElement = 285 (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theIssue.getClass()); 286 BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("expression"); 287 IPrimitiveType<?> locationElem = (IPrimitiveType<?>) locationChild 288 .getChildByName("expression") 289 .newInstance(locationChild.getInstanceConstructorArguments()); 290 locationElem.setValueAsString(theLocationExpression); 291 locationChild.getMutator().addValue(theIssue, locationElem); 292 } 293 } 294 295 public static IBase addIssueWithMessageId( 296 FhirContext myCtx, 297 IBaseOperationOutcome theOperationOutcome, 298 String theSeverity, 299 String theMessage, 300 String theMessageId, 301 String theLocation, 302 String theCode) { 303 IBase issue = addIssue(myCtx, theOperationOutcome, theSeverity, theMessage, theLocation, theCode); 304 if (isNotBlank(theMessageId)) { 305 addDetailsToIssue(myCtx, issue, Constants.JAVA_VALIDATOR_DETAILS_SYSTEM, theMessageId); 306 } 307 308 return issue; 309 } 310 311 public static void addDetailsToIssue(FhirContext theFhirContext, IBase theIssue, String theSystem, String theCode) { 312 addDetailsToIssue(theFhirContext, theIssue, theSystem, theCode, null); 313 } 314 315 public static void addDetailsToIssue( 316 FhirContext theFhirContext, IBase theIssue, String theSystem, String theCode, String theText) { 317 BaseRuntimeElementCompositeDefinition<?> issueElement = 318 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition(theIssue.getClass()); 319 BaseRuntimeChildDefinition detailsChildDef = issueElement.getChildByName("details"); 320 BaseRuntimeElementCompositeDefinition<?> ccDef = 321 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("CodeableConcept"); 322 ICompositeType codeableConcept = (ICompositeType) ccDef.newInstance(); 323 324 if (isNotBlank(theSystem) || isNotBlank(theCode)) { 325 BaseRuntimeElementCompositeDefinition<?> codingDef = 326 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("Coding"); 327 ICompositeType coding = (ICompositeType) codingDef.newInstance(); 328 329 // System 330 if (isNotBlank(theSystem)) { 331 IPrimitiveType<?> system = (IPrimitiveType<?>) 332 theFhirContext.getElementDefinition("uri").newInstance(); 333 system.setValueAsString(theSystem); 334 codingDef.getChildByName("system").getMutator().addValue(coding, system); 335 } 336 337 // Code 338 if (isNotBlank(theCode)) { 339 IPrimitiveType<?> code = (IPrimitiveType<?>) 340 theFhirContext.getElementDefinition("code").newInstance(); 341 code.setValueAsString(theCode); 342 codingDef.getChildByName("code").getMutator().addValue(coding, code); 343 } 344 345 ccDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding); 346 } 347 348 if (isNotBlank(theText)) { 349 IPrimitiveType<?> textElem = (IPrimitiveType<?>) 350 ccDef.getChildByName("text").getChildByName("text").newInstance(theText); 351 ccDef.getChildByName("text").getMutator().addValue(codeableConcept, textElem); 352 } 353 354 detailsChildDef.getMutator().addValue(theIssue, codeableConcept); 355 } 356 357 public static void addIssueLineExtensionToIssue(FhirContext theCtx, IBase theIssue, String theLine) { 358 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 359 ExtensionUtil.setExtension( 360 theCtx, 361 theIssue, 362 "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line", 363 "integer", 364 theLine); 365 } 366 } 367 368 public static void addIssueColExtensionToIssue(FhirContext theCtx, IBase theIssue, String theColumn) { 369 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 370 ExtensionUtil.setExtension( 371 theCtx, 372 theIssue, 373 "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col", 374 "integer", 375 theColumn); 376 } 377 } 378 379 public static void addMessageIdExtensionToIssue(FhirContext theCtx, IBase theIssue, String theMessageId) { 380 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 381 ExtensionUtil.setExtension( 382 theCtx, 383 theIssue, 384 "http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id", 385 "string", 386 theMessageId); 387 } 388 } 389 390 public static IBaseOperationOutcome createOperationOutcome( 391 String theSeverity, 392 String theMessage, 393 String theCode, 394 FhirContext theFhirContext, 395 @Nullable StorageResponseCodeEnum theStorageResponseCode) { 396 IBaseOperationOutcome oo = newInstance(theFhirContext); 397 String detailSystem = null; 398 String detailCode = null; 399 String detailDescription = null; 400 if (theStorageResponseCode != null) { 401 detailSystem = theStorageResponseCode.getSystem(); 402 detailCode = theStorageResponseCode.getCode(); 403 detailDescription = theStorageResponseCode.getDisplay(); 404 } 405 addIssue( 406 theFhirContext, 407 oo, 408 theSeverity, 409 theMessage, 410 null, 411 theCode, 412 detailSystem, 413 detailCode, 414 detailDescription); 415 return oo; 416 } 417}