
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(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", describeLocation(theLocation)); 077 } 078 } 079 080 @Override 081 public void incorrectJsonType( 082 IParseLocation theLocation, 083 String theElementName, 084 ValueType theExpected, 085 ScalarType theExpectedScalarType, 086 ValueType theFound, 087 ScalarType theFoundScalarType) { 088 if (myLogErrors) { 089 if (ourLog.isWarnEnabled()) { 090 String message = describeLocation(theLocation) 091 + createIncorrectJsonTypeMessage( 092 theElementName, theExpected, theExpectedScalarType, theFound, theFoundScalarType); 093 ourLog.warn(message); 094 } 095 } 096 } 097 098 @Override 099 public void invalidValue(IParseLocation theLocation, String theValue, String theError) { 100 if (isBlank(theValue) || myErrorOnInvalidValue == false) { 101 if (myLogErrors) { 102 ourLog.warn("{}Invalid attribute value \"{}\": {}", describeLocation(theLocation), theValue, theError); 103 } 104 } else { 105 STRICT_ERROR_HANDLER.invalidValue(theLocation, theValue, theError); 106 } 107 } 108 109 /** 110 * If set to <code>false</code> (default is <code>true</code>) invalid values will be logged. By 111 * default, invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike 112 * other methods in this class which default to simply logging errors). 113 * <p> 114 * Note that empty values (e.g. <code>""</code>) will not lead to an error when this is set to 115 * <code>true</code>, only invalid values (e.g. a gender code of <code>foo</code>) 116 * </p> 117 * 118 * @see #setErrorOnInvalidValue(boolean) 119 */ 120 public boolean isErrorOnInvalidValue() { 121 return myErrorOnInvalidValue; 122 } 123 124 /** 125 * If set to <code>false</code> (default is <code>true</code>) invalid extensions will be logged. By 126 * default, invalid resource extensions cause this error handler to throw a {@link DataFormatException} (unlike 127 * other methods in this class which default to simply logging errors). 128 * 129 * @see #setErrorOnInvalidExtension(boolean) 130 */ 131 public boolean isErrorOnInvalidExtension() { 132 return myErrorOnInvalidExtension; 133 } 134 135 @Override 136 public void missingRequiredElement(IParseLocation theLocation, String theElementName) { 137 if (myLogErrors) { 138 ourLog.warn("{}Resource is missing required element: {}", describeLocation(theLocation), theElementName); 139 } 140 } 141 142 /** 143 * If set to <code>false</code> (default is <code>true</code>) invalid values will be logged. By 144 * default, invalid attribute values cause this error handler to throw a {@link DataFormatException} (unlike 145 * other methods in this class which default to simply logging errors). 146 * <p> 147 * Note that empty values (e.g. <code>""</code>) will not lead to an error when this is set to 148 * <code>true</code>, only invalid values (e.g. a gender code of <code>foo</code>) 149 * </p> 150 * 151 * @return Returns a reference to <code>this</code> for easy method chaining 152 * @see #isErrorOnInvalidValue() 153 */ 154 public LenientErrorHandler setErrorOnInvalidValue(boolean theErrorOnInvalidValue) { 155 myErrorOnInvalidValue = theErrorOnInvalidValue; 156 return this; 157 } 158 159 /** 160 * If set to <code>false</code> (default is <code>true</code>) invalid extensions will be logged. By 161 * default, invalid resource extensions cause this error handler to throw a {@link DataFormatException} (unlike 162 * other methods in this class which default to simply logging errors). 163 * 164 * @return Returns a reference to <code>this</code> for easy method chaining 165 * @see #isErrorOnInvalidExtension() 166 */ 167 public LenientErrorHandler setErrorOnInvalidExtension(boolean theErrorOnInvalidExtension) { 168 myErrorOnInvalidExtension = theErrorOnInvalidExtension; 169 return this; 170 } 171 172 /** 173 * If this method is called, both invalid resource extensions and invalid attribute values will set to simply logging errors. 174 */ 175 public LenientErrorHandler disableAllErrors() { 176 myErrorOnInvalidValue = false; 177 myErrorOnInvalidExtension = false; 178 return this; 179 } 180 181 @Override 182 public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) { 183 if (myLogErrors) { 184 ourLog.warn( 185 "{}Multiple repetitions of non-repeatable element '{}' found while parsing", 186 describeLocation(theLocation), 187 theElementName); 188 } 189 } 190 191 @Override 192 public void unknownAttribute(IParseLocation theLocation, String theElementName) { 193 if (myLogErrors) { 194 ourLog.warn("{}Unknown attribute '{}' found while parsing", describeLocation(theLocation), theElementName); 195 } 196 } 197 198 @Override 199 public void unknownElement(IParseLocation theLocation, String theElementName) { 200 if (myLogErrors) { 201 ourLog.warn("{}Unknown element '{}' found while parsing", describeLocation(theLocation), theElementName); 202 } 203 } 204 205 @Override 206 public void unknownReference(IParseLocation theLocation, String theReference) { 207 if (myLogErrors) { 208 ourLog.warn("{}Resource has invalid reference: {}", describeLocation(theLocation), theReference); 209 } 210 } 211 212 @Override 213 public void extensionContainsValueAndNestedExtensions(IParseLocation theLocation) { 214 if (myErrorOnInvalidExtension) { 215 STRICT_ERROR_HANDLER.extensionContainsValueAndNestedExtensions(theLocation); 216 } else if (myLogErrors) { 217 ourLog.warn("{}Extension contains both a value and nested extensions", describeLocation(theLocation)); 218 } 219 } 220 221 public static String createIncorrectJsonTypeMessage( 222 String theElementName, 223 ValueType theExpected, 224 ScalarType theExpectedScalarType, 225 ValueType theFound, 226 ScalarType theFoundScalarType) { 227 StringBuilder b = new StringBuilder(); 228 b.append("Found incorrect type for element "); 229 b.append(theElementName); 230 b.append(" - Expected "); 231 b.append(theExpected.name()); 232 if (theExpectedScalarType != null) { 233 b.append(" ("); 234 b.append(theExpectedScalarType.name()); 235 b.append(")"); 236 } 237 b.append(" and found "); 238 b.append(theFound.name()); 239 if (theFoundScalarType != null) { 240 b.append(" ("); 241 b.append(theFoundScalarType.name()); 242 b.append(")"); 243 } 244 String message = b.toString(); 245 return message; 246 } 247}