
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 public static IBase addIssueWithMessageId( 268 FhirContext myCtx, 269 IBaseOperationOutcome theOperationOutcome, 270 String theSeverity, 271 String theMessage, 272 String theMessageId, 273 String theLocation, 274 String theCode) { 275 IBase issue = addIssue(myCtx, theOperationOutcome, theSeverity, theMessage, theLocation, theCode); 276 if (isNotBlank(theMessageId)) { 277 addDetailsToIssue(myCtx, issue, Constants.JAVA_VALIDATOR_DETAILS_SYSTEM, theMessageId); 278 } 279 280 return issue; 281 } 282 283 public static void addDetailsToIssue(FhirContext theFhirContext, IBase theIssue, String theSystem, String theCode) { 284 addDetailsToIssue(theFhirContext, theIssue, theSystem, theCode, null); 285 } 286 287 public static void addDetailsToIssue( 288 FhirContext theFhirContext, IBase theIssue, String theSystem, String theCode, String theText) { 289 BaseRuntimeElementCompositeDefinition<?> issueElement = 290 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition(theIssue.getClass()); 291 BaseRuntimeChildDefinition detailsChildDef = issueElement.getChildByName("details"); 292 BaseRuntimeElementCompositeDefinition<?> ccDef = 293 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("CodeableConcept"); 294 ICompositeType codeableConcept = (ICompositeType) ccDef.newInstance(); 295 296 if (isNotBlank(theSystem) || isNotBlank(theCode)) { 297 BaseRuntimeElementCompositeDefinition<?> codingDef = 298 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("Coding"); 299 ICompositeType coding = (ICompositeType) codingDef.newInstance(); 300 301 // System 302 if (isNotBlank(theSystem)) { 303 IPrimitiveType<?> system = (IPrimitiveType<?>) 304 theFhirContext.getElementDefinition("uri").newInstance(); 305 system.setValueAsString(theSystem); 306 codingDef.getChildByName("system").getMutator().addValue(coding, system); 307 } 308 309 // Code 310 if (isNotBlank(theCode)) { 311 IPrimitiveType<?> code = (IPrimitiveType<?>) 312 theFhirContext.getElementDefinition("code").newInstance(); 313 code.setValueAsString(theCode); 314 codingDef.getChildByName("code").getMutator().addValue(coding, code); 315 } 316 317 ccDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding); 318 } 319 320 if (isNotBlank(theText)) { 321 IPrimitiveType<?> textElem = (IPrimitiveType<?>) 322 ccDef.getChildByName("text").getChildByName("text").newInstance(theText); 323 ccDef.getChildByName("text").getMutator().addValue(codeableConcept, textElem); 324 } 325 326 detailsChildDef.getMutator().addValue(theIssue, codeableConcept); 327 } 328 329 public static void addIssueLineExtensionToIssue(FhirContext theCtx, IBase theIssue, String theLine) { 330 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 331 ExtensionUtil.setExtension( 332 theCtx, 333 theIssue, 334 "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line", 335 "integer", 336 theLine); 337 } 338 } 339 340 public static void addIssueColExtensionToIssue(FhirContext theCtx, IBase theIssue, String theColumn) { 341 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 342 ExtensionUtil.setExtension( 343 theCtx, 344 theIssue, 345 "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col", 346 "integer", 347 theColumn); 348 } 349 } 350 351 public static void addMessageIdExtensionToIssue(FhirContext theCtx, IBase theIssue, String theMessageId) { 352 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 353 ExtensionUtil.setExtension( 354 theCtx, 355 theIssue, 356 "http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id", 357 "string", 358 theMessageId); 359 } 360 } 361}