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