001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2023 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.RuntimeResourceDefinition; 027import ca.uhn.fhir.i18n.Msg; 028import ca.uhn.fhir.rest.api.Constants; 029import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 030import org.hl7.fhir.instance.model.api.IBase; 031import org.hl7.fhir.instance.model.api.IBaseCoding; 032import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 033import org.hl7.fhir.instance.model.api.IBaseResource; 034import org.hl7.fhir.instance.model.api.ICompositeType; 035import org.hl7.fhir.instance.model.api.IPrimitiveType; 036 037import javax.annotation.Nullable; 038import java.util.List; 039 040import static org.apache.commons.lang3.StringUtils.isNotBlank; 041 042/** 043 * Utilities for dealing with OperationOutcome resources across various model versions 044 */ 045public class OperationOutcomeUtil { 046 047 /** 048 * Add an issue to an OperationOutcome 049 * @param theCtx The fhir context 050 * @param theOperationOutcome The OO resource to add to 051 * @param theSeverity The severity (fatal | error | warning | information) 052 * @param theDetails The details string 053 * @param theCode 054 * @return Returns the newly added issue 055 */ 056 public static IBase addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) { 057 return addIssue(theCtx, theOperationOutcome, theSeverity, theDetails, theLocation, theCode, null, null, null); 058 } 059 060 public static IBase addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode, @Nullable String theDetailSystem, @Nullable String theDetailCode, @Nullable String theDetailDescription) { 061 IBase issue = createIssue(theCtx, theOperationOutcome); 062 populateDetails(theCtx, issue, theSeverity, theDetails, theLocation, theCode, theDetailSystem, theDetailCode, theDetailDescription); 063 return issue; 064 } 065 066 private static IBase createIssue(FhirContext theCtx, IBaseResource theOutcome) { 067 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 068 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 069 BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) issueChild.getChildByName("issue"); 070 071 IBase issue = issueElement.newInstance(); 072 issueChild.getMutator().addValue(theOutcome, issue); 073 return issue; 074 } 075 076 public static String getFirstIssueDetails(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 077 return getFirstIssueStringPart(theCtx, theOutcome, "diagnostics"); 078 } 079 080 public static String getFirstIssueLocation(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 081 return getFirstIssueStringPart(theCtx, theOutcome, "location"); 082 } 083 084 private static String getFirstIssueStringPart(FhirContext theCtx, IBaseOperationOutcome theOutcome, String name) { 085 if (theOutcome == null) { 086 return null; 087 } 088 089 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 090 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 091 092 List<IBase> issues = issueChild.getAccessor().getValues(theOutcome); 093 if (issues.isEmpty()) { 094 return null; 095 } 096 097 IBase issue = issues.get(0); 098 BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(issue.getClass()); 099 BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName(name); 100 101 List<IBase> details = detailsChild.getAccessor().getValues(issue); 102 if (details.isEmpty()) { 103 return null; 104 } 105 return ((IPrimitiveType<?>) details.get(0)).getValueAsString(); 106 } 107 108 /** 109 * Returns true if the given OperationOutcome has 1 or more Operation.issue repetitions 110 */ 111 public static boolean hasIssues(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 112 if (theOutcome == null) { 113 return false; 114 } 115 return getIssueCount(theCtx, theOutcome) > 0; 116 } 117 118 public static int getIssueCount(FhirContext theCtx, IBaseOperationOutcome theOutcome) { 119 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 120 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 121 return issueChild.getAccessor().getValues(theOutcome).size(); 122 } 123 124 public static boolean hasIssuesOfSeverity(FhirContext theCtx, IBaseOperationOutcome theOutcome, String theSeverity) { 125 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome); 126 BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue"); 127 List<IBase> issues = issueChild.getAccessor().getValues(theOutcome); 128 129 if (issues.isEmpty()) { 130 return false; // if there are no issues at all, there are no issues of the required severity 131 } 132 133 IBase firstIssue = issues.get(0); 134 BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(firstIssue.getClass()); 135 BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity"); 136 137 return issues.stream() 138 .flatMap(t -> severityChild.getAccessor().getValues(t).stream()) 139 .map(t -> (IPrimitiveType<?>) t) 140 .map(IPrimitiveType::getValueAsString) 141 .anyMatch(theSeverity::equals); 142 } 143 144 public static IBaseOperationOutcome newInstance(FhirContext theCtx) { 145 RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition("OperationOutcome"); 146 try { 147 return (IBaseOperationOutcome) ooDef.getImplementingClass().newInstance(); 148 } catch (InstantiationException e) { 149 throw new InternalErrorException(Msg.code(1803) + "Unable to instantiate OperationOutcome", e); 150 } catch (IllegalAccessException e) { 151 throw new InternalErrorException(Msg.code(1804) + "Unable to instantiate OperationOutcome", e); 152 } 153 } 154 155 private static void populateDetails(FhirContext theCtx, IBase theIssue, String theSeverity, String theDetails, String theLocation, String theCode, String theDetailSystem, String theDetailCode, String theDetailDescription) { 156 BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(theIssue.getClass()); 157 BaseRuntimeChildDefinition diagnosticsChild; 158 diagnosticsChild = issueElement.getChildByName("diagnostics"); 159 160 BaseRuntimeChildDefinition codeChild = issueElement.getChildByName("code"); 161 IPrimitiveType<?> codeElem = (IPrimitiveType<?>) codeChild.getChildByName("code").newInstance(codeChild.getInstanceConstructorArguments()); 162 codeElem.setValueAsString(theCode); 163 codeChild.getMutator().addValue(theIssue, codeElem); 164 165 BaseRuntimeElementDefinition<?> stringDef = diagnosticsChild.getChildByName(diagnosticsChild.getElementName()); 166 BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity"); 167 168 IPrimitiveType<?> severityElem = (IPrimitiveType<?>) severityChild.getChildByName("severity").newInstance(severityChild.getInstanceConstructorArguments()); 169 severityElem.setValueAsString(theSeverity); 170 severityChild.getMutator().addValue(theIssue, severityElem); 171 172 IPrimitiveType<?> string = (IPrimitiveType<?>) stringDef.newInstance(); 173 string.setValueAsString(theDetails); 174 diagnosticsChild.getMutator().setValue(theIssue, string); 175 176 addLocationToIssue(theCtx, theIssue, theLocation); 177 178 if (isNotBlank(theDetailSystem)) { 179 BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName("details"); 180 if (detailsChild != null) { 181 BaseRuntimeElementDefinition<?> codeableConceptDef = theCtx.getElementDefinition("CodeableConcept"); 182 IBase codeableConcept = codeableConceptDef.newInstance(); 183 184 BaseRuntimeElementDefinition<?> codingDef = theCtx.getElementDefinition("Coding"); 185 IBaseCoding coding = (IBaseCoding) codingDef.newInstance(); 186 coding.setSystem(theDetailSystem); 187 coding.setCode(theDetailCode); 188 coding.setDisplay(theDetailDescription); 189 190 codeableConceptDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding); 191 192 detailsChild.getMutator().addValue(theIssue, codeableConcept); 193 } 194 } 195 } 196 197 public static void addLocationToIssue(FhirContext theContext, IBase theIssue, String theLocation) { 198 if (isNotBlank(theLocation)) { 199 BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theIssue.getClass()); 200 BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location"); 201 IPrimitiveType<?> locationElem = (IPrimitiveType<?>) locationChild.getChildByName("location").newInstance(locationChild.getInstanceConstructorArguments()); 202 locationElem.setValueAsString(theLocation); 203 locationChild.getMutator().addValue(theIssue, locationElem); 204 } 205 } 206 207 public static IBase addIssueWithMessageId(FhirContext myCtx, IBaseOperationOutcome theOperationOutcome, String severity, String message, String messageId, String location, String theCode) { 208 IBase issue = addIssue(myCtx, theOperationOutcome, severity, message, location, theCode); 209 BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) myCtx.getElementDefinition(issue.getClass()); 210 BaseRuntimeChildDefinition detailsChildDef = issueElement.getChildByName("details"); 211 212 IPrimitiveType<?> system = (IPrimitiveType<?>) myCtx.getElementDefinition("uri").newInstance(); 213 system.setValueAsString(Constants.JAVA_VALIDATOR_DETAILS_SYSTEM); 214 IPrimitiveType<?> code = (IPrimitiveType<?>) myCtx.getElementDefinition("code").newInstance(); 215 code.setValueAsString(messageId); 216 217 BaseRuntimeElementCompositeDefinition<?> codingDef = (BaseRuntimeElementCompositeDefinition<?>) myCtx.getElementDefinition("Coding"); 218 ICompositeType coding = (ICompositeType) codingDef.newInstance(); 219 codingDef.getChildByName("system").getMutator().addValue(coding, system); 220 codingDef.getChildByName("code").getMutator().addValue(coding, code); 221 BaseRuntimeElementCompositeDefinition<?> ccDef = (BaseRuntimeElementCompositeDefinition<?>) myCtx.getElementDefinition("CodeableConcept"); 222 ICompositeType codeableConcept = (ICompositeType) ccDef.newInstance(); 223 ccDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding); 224 225 detailsChildDef.getMutator().addValue(issue, codeableConcept); 226 return issue; 227 } 228}