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