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}