001package ca.uhn.fhir.util;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 * http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
025import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
026import ca.uhn.fhir.context.FhirContext;
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 org.hl7.fhir.instance.model.api.IBase;
032import org.hl7.fhir.instance.model.api.IBaseCoding;
033import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
034import org.hl7.fhir.instance.model.api.IBaseResource;
035import org.hl7.fhir.instance.model.api.ICompositeType;
036import org.hl7.fhir.instance.model.api.IPrimitiveType;
037
038import javax.annotation.Nullable;
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(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) {
058                return addIssue(theCtx, theOperationOutcome, theSeverity, theDetails, theLocation, theCode, null, null, null);
059        }
060
061        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) {
062                IBase issue = createIssue(theCtx, theOperationOutcome);
063                populateDetails(theCtx, issue, theSeverity, theDetails, theLocation, theCode, theDetailSystem, theDetailCode, theDetailDescription);
064                return issue;
065        }
066
067        private static IBase createIssue(FhirContext theCtx, IBaseResource theOutcome) {
068                RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome);
069                BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue");
070                BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) issueChild.getChildByName("issue");
071
072                IBase issue = issueElement.newInstance();
073                issueChild.getMutator().addValue(theOutcome, issue);
074                return issue;
075        }
076
077        public static String getFirstIssueDetails(FhirContext theCtx, IBaseOperationOutcome theOutcome) {
078                return getFirstIssueStringPart(theCtx, theOutcome, "diagnostics");
079        }
080
081        public static String getFirstIssueLocation(FhirContext theCtx, IBaseOperationOutcome theOutcome) {
082                return getFirstIssueStringPart(theCtx, theOutcome, "location");
083        }
084
085        private static String getFirstIssueStringPart(FhirContext theCtx, IBaseOperationOutcome theOutcome, String name) {
086                if (theOutcome == null) {
087                        return null;
088                }
089
090                RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome);
091                BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue");
092
093                List<IBase> issues = issueChild.getAccessor().getValues(theOutcome);
094                if (issues.isEmpty()) {
095                        return null;
096                }
097
098                IBase issue = issues.get(0);
099                BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(issue.getClass());
100                BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName(name);
101
102                List<IBase> details = detailsChild.getAccessor().getValues(issue);
103                if (details.isEmpty()) {
104                        return null;
105                }
106                return ((IPrimitiveType<?>) details.get(0)).getValueAsString();
107        }
108
109        /**
110         * Returns true if the given OperationOutcome has 1 or more Operation.issue repetitions
111         */
112        public static boolean hasIssues(FhirContext theCtx, IBaseOperationOutcome theOutcome) {
113                if (theOutcome == null) {
114                        return false;
115                }
116                return getIssueCount(theCtx, theOutcome) > 0;
117        }
118
119        public static int getIssueCount(FhirContext theCtx, IBaseOperationOutcome theOutcome) {
120                RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome);
121                BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue");
122                return issueChild.getAccessor().getValues(theOutcome).size();
123        }
124
125        public static boolean hasIssuesOfSeverity(FhirContext theCtx, IBaseOperationOutcome theOutcome, String theSeverity) {
126                RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition(theOutcome);
127                BaseRuntimeChildDefinition issueChild = ooDef.getChildByName("issue");
128                List<IBase> issues = issueChild.getAccessor().getValues(theOutcome);
129
130                if (issues.isEmpty()) {
131                        return false;   // if there are no issues at all, there are no issues of the required severity
132                }
133
134                IBase firstIssue = issues.get(0);
135                BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(firstIssue.getClass());
136                BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity");
137
138                return issues.stream()
139                        .flatMap(t -> severityChild.getAccessor().getValues(t).stream())
140                        .map(t -> (IPrimitiveType<?>) t)
141                        .map(IPrimitiveType::getValueAsString)
142                        .anyMatch(theSeverity::equals);
143        }
144
145        public static IBaseOperationOutcome newInstance(FhirContext theCtx) {
146                RuntimeResourceDefinition ooDef = theCtx.getResourceDefinition("OperationOutcome");
147                try {
148                        return (IBaseOperationOutcome) ooDef.getImplementingClass().newInstance();
149                } catch (InstantiationException e) {
150                        throw new InternalErrorException(Msg.code(1803) + "Unable to instantiate OperationOutcome", e);
151                } catch (IllegalAccessException e) {
152                        throw new InternalErrorException(Msg.code(1804) + "Unable to instantiate OperationOutcome", e);
153                }
154        }
155
156        private static void populateDetails(FhirContext theCtx, IBase theIssue, String theSeverity, String theDetails, String theLocation, String theCode, String theDetailSystem, String theDetailCode, String theDetailDescription) {
157                BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(theIssue.getClass());
158                BaseRuntimeChildDefinition diagnosticsChild;
159                diagnosticsChild = issueElement.getChildByName("diagnostics");
160
161                BaseRuntimeChildDefinition codeChild = issueElement.getChildByName("code");
162                IPrimitiveType<?> codeElem = (IPrimitiveType<?>) codeChild.getChildByName("code").newInstance(codeChild.getInstanceConstructorArguments());
163                codeElem.setValueAsString(theCode);
164                codeChild.getMutator().addValue(theIssue, codeElem);
165
166                BaseRuntimeElementDefinition<?> stringDef = diagnosticsChild.getChildByName(diagnosticsChild.getElementName());
167                BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity");
168
169                IPrimitiveType<?> severityElem = (IPrimitiveType<?>) severityChild.getChildByName("severity").newInstance(severityChild.getInstanceConstructorArguments());
170                severityElem.setValueAsString(theSeverity);
171                severityChild.getMutator().addValue(theIssue, severityElem);
172
173                IPrimitiveType<?> string = (IPrimitiveType<?>) stringDef.newInstance();
174                string.setValueAsString(theDetails);
175                diagnosticsChild.getMutator().setValue(theIssue, string);
176
177                addLocationToIssue(theCtx, theIssue, theLocation);
178
179                if (isNotBlank(theDetailSystem)) {
180                        BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName("details");
181                        if (detailsChild != null) {
182                                BaseRuntimeElementDefinition<?> codeableConceptDef = theCtx.getElementDefinition("CodeableConcept");
183                                IBase codeableConcept = codeableConceptDef.newInstance();
184
185                                BaseRuntimeElementDefinition<?> codingDef = theCtx.getElementDefinition("Coding");
186                                IBaseCoding coding = (IBaseCoding) codingDef.newInstance();
187                                coding.setSystem(theDetailSystem);
188                                coding.setCode(theDetailCode);
189                                coding.setDisplay(theDetailDescription);
190
191                                codeableConceptDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding);
192
193                                detailsChild.getMutator().addValue(theIssue, codeableConcept);
194                        }
195                }
196        }
197
198        public static void addLocationToIssue(FhirContext theContext, IBase theIssue, String theLocation) {
199                if (isNotBlank(theLocation)) {
200                        BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theIssue.getClass());
201                        BaseRuntimeChildDefinition locationChild = issueElement.getChildByName("location");
202                        IPrimitiveType<?> locationElem = (IPrimitiveType<?>) locationChild.getChildByName("location").newInstance(locationChild.getInstanceConstructorArguments());
203                        locationElem.setValueAsString(theLocation);
204                        locationChild.getMutator().addValue(theIssue, locationElem);
205                }
206        }
207
208        public static IBase addIssueWithMessageId(FhirContext myCtx, IBaseOperationOutcome theOperationOutcome, String severity, String message, String messageId, String location, String theCode) {
209                IBase issue = addIssue(myCtx, theOperationOutcome, severity, message, location, theCode);
210                BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) myCtx.getElementDefinition(issue.getClass());
211                BaseRuntimeChildDefinition detailsChildDef = issueElement.getChildByName("details");
212
213                IPrimitiveType<?> system = (IPrimitiveType<?>) myCtx.getElementDefinition("uri").newInstance();
214                system.setValueAsString(Constants.JAVA_VALIDATOR_DETAILS_SYSTEM);
215                IPrimitiveType<?> code = (IPrimitiveType<?>) myCtx.getElementDefinition("code").newInstance();
216                code.setValueAsString(messageId);
217
218                BaseRuntimeElementCompositeDefinition<?> codingDef = (BaseRuntimeElementCompositeDefinition<?>) myCtx.getElementDefinition("Coding");
219                ICompositeType coding = (ICompositeType) codingDef.newInstance();
220                codingDef.getChildByName("system").getMutator().addValue(coding, system);
221                codingDef.getChildByName("code").getMutator().addValue(coding, code);
222                BaseRuntimeElementCompositeDefinition<?> ccDef = (BaseRuntimeElementCompositeDefinition<?>) myCtx.getElementDefinition("CodeableConcept");
223                ICompositeType codeableConcept = (ICompositeType) ccDef.newInstance();
224                ccDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding);
225
226                detailsChildDef.getMutator().addValue(issue, codeableConcept);
227                return issue;
228        }
229}