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