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}