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