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