001package ca.uhn.fhir.parser;
002
003import static org.apache.commons.lang3.StringUtils.isBlank;
004
005import ca.uhn.fhir.context.FhirContext;
006import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType;
007import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
008
009/*
010 * #%L
011 * HAPI FHIR - Core Library
012 * %%
013 * Copyright (C) 2014 - 2021 Smile CDR, Inc.
014 * %%
015 * Licensed under the Apache License, Version 2.0 (the "License");
016 * you may not use this file except in compliance with the License.
017 * You may obtain a copy of the License at
018 *
019 * http://www.apache.org/licenses/LICENSE-2.0
020 *
021 * Unless required by applicable law or agreed to in writing, software
022 * distributed under the License is distributed on an "AS IS" BASIS,
023 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
024 * See the License for the specific language governing permissions and
025 * limitations under the License.
026 * #L%
027 */
028
029/**
030 * The default error handler, which logs issues but does not abort parsing, with only one exception:
031 * <p>
032 * The {@link #invalidValue(ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation, String, String)}
033 * method will throw a {@link DataFormatException} by default since ignoring this type of error
034 * can lead to data loss (since invalid values are silently ignored). See
035 * {@link #setErrorOnInvalidValue(boolean)} for information on this.
036 * </p>
037 * 
038 * @see IParser#setParserErrorHandler(IParserErrorHandler)
039 * @see FhirContext#setParserErrorHandler(IParserErrorHandler)
040 */
041public class LenientErrorHandler extends BaseErrorHandler implements IParserErrorHandler {
042
043        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LenientErrorHandler.class);
044        private static final StrictErrorHandler STRICT_ERROR_HANDLER = new StrictErrorHandler();
045        private boolean myErrorOnInvalidValue = true;
046        private boolean myLogErrors;
047
048        /**
049         * Constructor which configures this handler to log all errors
050         */
051        public LenientErrorHandler() {
052                myLogErrors = true;
053        }
054
055        /**
056         * Constructor
057         * 
058         * @param theLogErrors
059         *           Should errors be logged?
060         * @since 1.2
061         */
062        public LenientErrorHandler(boolean theLogErrors) {
063                myLogErrors = theLogErrors;
064        }
065
066        @Override
067        public void containedResourceWithNoId(IParseLocation theLocation) {
068                if (myLogErrors) {
069                        ourLog.warn("Resource has contained child resource with no ID");
070                }
071        }
072
073        @Override
074        public void incorrectJsonType(IParseLocation theLocation, String theElementName, ValueType theExpected, ScalarType theExpectedScalarType, ValueType theFound, ScalarType theFoundScalarType) {
075                if (myLogErrors) {
076                        if (ourLog.isWarnEnabled()) {
077                                String message = createIncorrectJsonTypeMessage(theElementName, theExpected, theExpectedScalarType, theFound, theFoundScalarType);
078                                ourLog.warn(message);
079                        }
080                }
081        }
082
083        @Override
084        public void invalidValue(IParseLocation theLocation, String theValue, String theError) {
085                if (isBlank(theValue) || myErrorOnInvalidValue == false) {
086                        if (myLogErrors) {
087                                ourLog.warn("{}Invalid attribute value \"{}\": {}", describeLocation(theLocation), theValue, theError);
088                        }
089                } else {
090                        STRICT_ERROR_HANDLER.invalidValue(theLocation, theValue, theError);
091                }
092        }
093
094        /**
095         * If set to <code>false</code> (default is <code>true</code>) invalid values will be logged. By
096         * default invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike
097         * other methods in this class which default to simply logging errors).
098         * <p>
099         * Note that empty values (e.g. <code>""</code>) will not lead to an error when this is set to
100         * <code>true</code>, only invalid values (e.g. a gender code of <code>foo</code>)
101         * </p>
102         * 
103         * @see #setErrorOnInvalidValue(boolean)
104         */
105        public boolean isErrorOnInvalidValue() {
106                return myErrorOnInvalidValue;
107        }
108
109        @Override
110        public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
111                if (myLogErrors) {
112                        ourLog.warn("Resource is missing required element: {}", theElementName);
113                }
114        }
115
116        /**
117         * If set to <code>false</code> (default is <code>true</code>) invalid values will be logged. By
118         * default invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike
119         * other methods in this class which default to simply logging errors).
120         * <p>
121         * Note that empty values (e.g. <code>""</code>) will not lead to an error when this is set to
122         * <code>true</code>, only invalid values (e.g. a gender code of <code>foo</code>)
123         * </p>
124         * 
125         * @return Returns a reference to <code>this</code> for easy method chaining
126         * @see #isErrorOnInvalidValue()
127         */
128        public LenientErrorHandler setErrorOnInvalidValue(boolean theErrorOnInvalidValue) {
129                myErrorOnInvalidValue = theErrorOnInvalidValue;
130                return this;
131        }
132
133        @Override
134        public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
135                if (myLogErrors) {
136                        ourLog.warn("{}Multiple repetitions of non-repeatable element '{}' found while parsing", describeLocation(theLocation), theElementName);
137                }
138        }
139
140        @Override
141        public void unknownAttribute(IParseLocation theLocation, String theElementName) {
142                if (myLogErrors) {
143                        ourLog.warn("{}Unknown attribute '{}' found while parsing",describeLocation(theLocation),  theElementName);
144                }
145        }
146
147        @Override
148        public void unknownElement(IParseLocation theLocation, String theElementName) {
149                if (myLogErrors) {
150                        ourLog.warn("{}Unknown element '{}' found while parsing", describeLocation(theLocation), theElementName);
151                }
152        }
153
154        @Override
155        public void unknownReference(IParseLocation theLocation, String theReference) {
156                if (myLogErrors) {
157                        ourLog.warn("{}Resource has invalid reference: {}", describeLocation(theLocation), theReference);
158                }
159        }
160
161        @Override
162        public void extensionContainsValueAndNestedExtensions(IParseLocation theLocation) {
163                if (myLogErrors) {
164                        ourLog.warn("{}Extension contains both a value and nested extensions", describeLocation(theLocation));
165                }
166        }
167
168        public static String createIncorrectJsonTypeMessage(String theElementName, ValueType theExpected, ScalarType theExpectedScalarType, ValueType theFound, ScalarType theFoundScalarType) {
169                StringBuilder b = new StringBuilder();
170                b.append("Found incorrect type for element ");
171                b.append(theElementName);
172                b.append(" - Expected ");
173                b.append(theExpected.name());
174                if (theExpectedScalarType != null) {
175                        b.append(" (");
176                        b.append(theExpectedScalarType.name());
177                        b.append(")");
178                }
179                b.append(" and found ");
180                b.append(theFound.name());
181                if (theFoundScalarType != null) {
182                        b.append(" (");
183                        b.append(theFoundScalarType.name());
184                        b.append(")");
185                }
186                String message = b.toString();
187                return message;
188        }
189
190}