001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 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 BaseRuntimeElementCompositeDefinition<?> issueElement = 269 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition(theIssue.getClass()); 270 BaseRuntimeChildDefinition detailsChildDef = issueElement.getChildByName("details"); 271 272 BaseRuntimeElementCompositeDefinition<?> codingDef = 273 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("Coding"); 274 ICompositeType coding = (ICompositeType) codingDef.newInstance(); 275 276 // System 277 IPrimitiveType<?> system = 278 (IPrimitiveType<?>) theFhirContext.getElementDefinition("uri").newInstance(); 279 system.setValueAsString(theSystem); 280 codingDef.getChildByName("system").getMutator().addValue(coding, system); 281 282 // Code 283 IPrimitiveType<?> code = 284 (IPrimitiveType<?>) theFhirContext.getElementDefinition("code").newInstance(); 285 code.setValueAsString(theCode); 286 codingDef.getChildByName("code").getMutator().addValue(coding, code); 287 BaseRuntimeElementCompositeDefinition<?> ccDef = 288 (BaseRuntimeElementCompositeDefinition<?>) theFhirContext.getElementDefinition("CodeableConcept"); 289 290 ICompositeType codeableConcept = (ICompositeType) ccDef.newInstance(); 291 ccDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding); 292 detailsChildDef.getMutator().addValue(theIssue, codeableConcept); 293 } 294 295 public static void addIssueLineExtensionToIssue(FhirContext theCtx, IBase theIssue, String theLine) { 296 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 297 ExtensionUtil.setExtension( 298 theCtx, 299 theIssue, 300 "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line", 301 "integer", 302 theLine); 303 } 304 } 305 306 public static void addIssueColExtensionToIssue(FhirContext theCtx, IBase theIssue, String theColumn) { 307 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 308 ExtensionUtil.setExtension( 309 theCtx, 310 theIssue, 311 "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col", 312 "integer", 313 theColumn); 314 } 315 } 316 317 public static void addMessageIdExtensionToIssue(FhirContext theCtx, IBase theIssue, String theMessageId) { 318 if (theCtx.getVersion().getVersion() != FhirVersionEnum.DSTU2) { 319 ExtensionUtil.setExtension( 320 theCtx, 321 theIssue, 322 "http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id", 323 "string", 324 theMessageId); 325 } 326 } 327}