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.parser;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.parser.json.BaseJsonLikeValue.ScalarType;
024import ca.uhn.fhir.parser.json.BaseJsonLikeValue.ValueType;
025
026import static org.apache.commons.lang3.StringUtils.isBlank;
027
028/**
029 * The default error handler, which logs issues but does not abort parsing, with only two exceptions:
030 * <p>
031 * The {@link #invalidValue(ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation, String, String)}
032 * method will throw a {@link DataFormatException} by default since ignoring this type of error
033 * can lead to data loss (since invalid values are silently ignored). See
034 * {@link #setErrorOnInvalidValue(boolean)} for information on this.
035 * </p>
036 * 
037 * @see IParser#setParserErrorHandler(IParserErrorHandler)
038 * @see FhirContext#setParserErrorHandler(IParserErrorHandler)
039 *
040 * <p>
041 * The {@link #extensionContainsValueAndNestedExtensions(ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation)}
042 * method will throw a {@link DataFormatException} by default since ignoring this type of error will allow malformed
043 * resouces to be created and result in errors when attempts to read, update or delete the resource in the future.
044 *  See {@link #setErrorOnInvalidExtension(boolean)} for information on this.
045 * </p>
046 */
047public class LenientErrorHandler extends ParseErrorHandler implements IParserErrorHandler {
048
049        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LenientErrorHandler.class);
050        private static final StrictErrorHandler STRICT_ERROR_HANDLER = new StrictErrorHandler();
051        private boolean myErrorOnInvalidValue = true;
052        private boolean myErrorOnInvalidExtension = true;
053        private boolean myLogErrors;
054
055        /**
056         * Constructor which configures this handler to log all errors
057         */
058        public LenientErrorHandler() {
059                myLogErrors = true;
060        }
061
062        /**
063         * Constructor
064         * 
065         * @param theLogErrors
066         *           Should errors be logged?
067         * @since 1.2
068         */
069        public LenientErrorHandler(boolean theLogErrors) {
070                myLogErrors = theLogErrors;
071        }
072
073        @Override
074        public void containedResourceWithNoId(IParseLocation theLocation) {
075                if (myLogErrors) {
076                        ourLog.warn("Resource has contained child resource with no ID");
077                }
078        }
079
080        @Override
081        public void incorrectJsonType(IParseLocation theLocation, String theElementName, ValueType theExpected, ScalarType theExpectedScalarType, ValueType theFound, ScalarType theFoundScalarType) {
082                if (myLogErrors) {
083                        if (ourLog.isWarnEnabled()) {
084                                String message = createIncorrectJsonTypeMessage(theElementName, theExpected, theExpectedScalarType, theFound, theFoundScalarType);
085                                ourLog.warn(message);
086                        }
087                }
088        }
089
090        @Override
091        public void invalidValue(IParseLocation theLocation, String theValue, String theError) {
092                if (isBlank(theValue) || myErrorOnInvalidValue == false) {
093                        if (myLogErrors) {
094                                ourLog.warn("{}Invalid attribute value \"{}\": {}", describeLocation(theLocation), theValue, theError);
095                        }
096                } else {
097                        STRICT_ERROR_HANDLER.invalidValue(theLocation, theValue, theError);
098                }
099        }
100
101        /**
102         * If set to <code>false</code> (default is <code>true</code>) invalid values will be logged. By
103         * default, invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike
104         * other methods in this class which default to simply logging errors).
105         * <p>
106         * Note that empty values (e.g. <code>""</code>) will not lead to an error when this is set to
107         * <code>true</code>, only invalid values (e.g. a gender code of <code>foo</code>)
108         * </p>
109         * 
110         * @see #setErrorOnInvalidValue(boolean)
111         */
112        public boolean isErrorOnInvalidValue() {
113                return myErrorOnInvalidValue;
114        }
115
116        /**
117         * If set to <code>false</code> (default is <code>true</code>) invalid extensions will be logged. By
118         * default, invalid resource extensions cause this error handler to throw a {@link DataFormatException} (unlike
119         * other methods in this class which default to simply logging errors).
120         *
121         * @see #setErrorOnInvalidExtension(boolean)
122         */
123        public boolean isErrorOnInvalidExtension() {
124                return myErrorOnInvalidExtension;
125        }
126
127        @Override
128        public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
129                if (myLogErrors) {
130                        ourLog.warn("Resource is missing required element: {}", theElementName);
131                }
132        }
133
134        /**
135         * If set to <code>false</code> (default is <code>true</code>) invalid values will be logged. By
136         * default, invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike
137         * other methods in this class which default to simply logging errors).
138         * <p>
139         * Note that empty values (e.g. <code>""</code>) will not lead to an error when this is set to
140         * <code>true</code>, only invalid values (e.g. a gender code of <code>foo</code>)
141         * </p>
142         * 
143         * @return Returns a reference to <code>this</code> for easy method chaining
144         * @see #isErrorOnInvalidValue()
145         */
146        public LenientErrorHandler setErrorOnInvalidValue(boolean theErrorOnInvalidValue) {
147                myErrorOnInvalidValue = theErrorOnInvalidValue;
148                return this;
149        }
150
151        /**
152         * If set to <code>false</code> (default is <code>true</code>) invalid extensions will be logged. By
153         * default, invalid resource extensions cause this error handler to throw a {@link DataFormatException} (unlike
154         * other methods in this class which default to simply logging errors).
155         *
156         * @return Returns a reference to <code>this</code> for easy method chaining
157         * @see #isErrorOnInvalidExtension()
158         */
159        public LenientErrorHandler setErrorOnInvalidExtension(boolean theErrorOnInvalidExtension) {
160                myErrorOnInvalidExtension = theErrorOnInvalidExtension;
161                return this;
162        }
163
164        /**
165         * If this method is called, both invalid resource extensions and invalid attribute values will set to simply logging errors.
166         */
167        public LenientErrorHandler disableAllErrors() {
168                myErrorOnInvalidValue = false;
169                myErrorOnInvalidExtension = false;
170                return this;
171        }
172
173        @Override
174        public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
175                if (myLogErrors) {
176                        ourLog.warn("{}Multiple repetitions of non-repeatable element '{}' found while parsing", describeLocation(theLocation), theElementName);
177                }
178        }
179
180        @Override
181        public void unknownAttribute(IParseLocation theLocation, String theElementName) {
182                if (myLogErrors) {
183                        ourLog.warn("{}Unknown attribute '{}' found while parsing",describeLocation(theLocation),  theElementName);
184                }
185        }
186
187        @Override
188        public void unknownElement(IParseLocation theLocation, String theElementName) {
189                if (myLogErrors) {
190                        ourLog.warn("{}Unknown element '{}' found while parsing", describeLocation(theLocation), theElementName);
191                }
192        }
193
194        @Override
195        public void unknownReference(IParseLocation theLocation, String theReference) {
196                if (myLogErrors) {
197                        ourLog.warn("{}Resource has invalid reference: {}", describeLocation(theLocation), theReference);
198                }
199        }
200
201        @Override
202        public void extensionContainsValueAndNestedExtensions(IParseLocation theLocation){
203                if (myErrorOnInvalidExtension) {
204                        STRICT_ERROR_HANDLER.extensionContainsValueAndNestedExtensions(theLocation);
205                } else if (myLogErrors) {
206                        ourLog.warn("{}Extension contains both a value and nested extensions", describeLocation(theLocation));
207                }
208        }
209
210        public static String createIncorrectJsonTypeMessage(String theElementName, ValueType theExpected, ScalarType theExpectedScalarType, ValueType theFound, ScalarType theFoundScalarType) {
211                StringBuilder b = new StringBuilder();
212                b.append("Found incorrect type for element ");
213                b.append(theElementName);
214                b.append(" - Expected ");
215                b.append(theExpected.name());
216                if (theExpectedScalarType != null) {
217                        b.append(" (");
218                        b.append(theExpectedScalarType.name());
219                        b.append(")");
220                }
221                b.append(" and found ");
222                b.append(theFound.name());
223                if (theFoundScalarType != null) {
224                        b.append(" (");
225                        b.append(theFoundScalarType.name());
226                        b.append(")");
227                }
228                String message = b.toString();
229                return message;
230        }
231
232}