1 package com.fasterxml.jackson.databind.cfg; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 import com.fasterxml.jackson.databind.DeserializationConfig; 7 import com.fasterxml.jackson.databind.DeserializationFeature; 8 import com.fasterxml.jackson.databind.MapperFeature; 9 import com.fasterxml.jackson.databind.type.LogicalType; 10 11 /** 12 * @since 2.12 13 */ 14 public class CoercionConfigs 15 implements java.io.Serializable 16 { 17 private static final long serialVersionUID = 1L; 18 19 private final static int TARGET_TYPE_COUNT = LogicalType.values().length; 20 21 /** 22 * Global default for cases not explicitly covered 23 */ 24 protected CoercionAction _defaultAction; 25 26 /** 27 * Default coercion definitions used if no overrides found 28 * by logical or physical type. 29 */ 30 protected final MutableCoercionConfig _defaultCoercions; 31 32 /** 33 * Coercion definitions by logical type ({@link LogicalType}) 34 */ 35 protected MutableCoercionConfig[] _perTypeCoercions; 36 37 /** 38 * Coercion definitions by physical type (Class). 39 */ 40 protected Map<Class<?>, MutableCoercionConfig> _perClassCoercions; 41 42 /* 43 /********************************************************************** 44 /* Life cycle 45 /********************************************************************** 46 */ 47 CoercionConfigs()48 public CoercionConfigs() { 49 this(CoercionAction.TryConvert, new MutableCoercionConfig(), 50 null, null); 51 } 52 CoercionConfigs(CoercionAction defaultAction, MutableCoercionConfig defaultCoercions, MutableCoercionConfig[] perTypeCoercions, Map<Class<?>, MutableCoercionConfig> perClassCoercions)53 protected CoercionConfigs(CoercionAction defaultAction, 54 MutableCoercionConfig defaultCoercions, 55 MutableCoercionConfig[] perTypeCoercions, 56 Map<Class<?>, MutableCoercionConfig> perClassCoercions) 57 { 58 _defaultCoercions = defaultCoercions; 59 _defaultAction = defaultAction; 60 _perTypeCoercions = perTypeCoercions; 61 _perClassCoercions = perClassCoercions; 62 } 63 64 /** 65 * Method called to create a non-shared copy of configuration settings, 66 * to be used by another {@link com.fasterxml.jackson.databind.ObjectMapper} 67 * instance. 68 * 69 * @return A non-shared copy of configuration settings 70 */ copy()71 public CoercionConfigs copy() 72 { 73 MutableCoercionConfig[] newPerType; 74 if (_perTypeCoercions == null) { 75 newPerType = null; 76 } else { 77 final int size = _perTypeCoercions.length; 78 newPerType = new MutableCoercionConfig[size]; 79 for (int i = 0; i < size; ++i) { 80 newPerType[i] = _copy(_perTypeCoercions[i]); 81 } 82 } 83 Map<Class<?>, MutableCoercionConfig> newPerClass; 84 if (_perClassCoercions == null) { 85 newPerClass = null; 86 } else { 87 newPerClass = new HashMap<>(); 88 for (Map.Entry<Class<?>, MutableCoercionConfig> entry : _perClassCoercions.entrySet()) { 89 newPerClass.put(entry.getKey(), entry.getValue().copy()); 90 } 91 } 92 return new CoercionConfigs(_defaultAction, _defaultCoercions.copy(), 93 newPerType, newPerClass); 94 } 95 _copy(MutableCoercionConfig src)96 private static MutableCoercionConfig _copy(MutableCoercionConfig src) { 97 if (src == null) { 98 return null; 99 } 100 return src.copy(); 101 } 102 103 /* 104 /********************************************************************** 105 /* Mutators: global defaults 106 /********************************************************************** 107 */ 108 defaultCoercions()109 public MutableCoercionConfig defaultCoercions() { 110 return _defaultCoercions; 111 } 112 113 /* 114 /********************************************************************** 115 /* Mutators: per type 116 /********************************************************************** 117 */ 118 findOrCreateCoercion(LogicalType type)119 public MutableCoercionConfig findOrCreateCoercion(LogicalType type) { 120 if (_perTypeCoercions == null) { 121 _perTypeCoercions = new MutableCoercionConfig[TARGET_TYPE_COUNT]; 122 } 123 MutableCoercionConfig config = _perTypeCoercions[type.ordinal()]; 124 if (config == null) { 125 _perTypeCoercions[type.ordinal()] = config = new MutableCoercionConfig(); 126 } 127 return config; 128 } 129 findOrCreateCoercion(Class<?> type)130 public MutableCoercionConfig findOrCreateCoercion(Class<?> type) { 131 if (_perClassCoercions == null) { 132 _perClassCoercions = new HashMap<>(); 133 } 134 MutableCoercionConfig config = _perClassCoercions.get(type); 135 if (config == null) { 136 config = new MutableCoercionConfig(); 137 _perClassCoercions.put(type, config); 138 } 139 return config; 140 } 141 142 /* 143 /********************************************************************** 144 /* Access 145 /********************************************************************** 146 */ 147 148 /** 149 * General-purpose accessor for finding what to do when specified coercion 150 * from shape that is now always allowed to be coerced from is requested. 151 * 152 * @param config Currently active deserialization configuration 153 * @param targetType Logical target type of coercion 154 * @param targetClass Physical target type of coercion 155 * @param inputShape Input shape to coerce from 156 * 157 * @return CoercionAction configured for specified coercion 158 * 159 * @since 2.12 160 */ findCoercion(DeserializationConfig config, LogicalType targetType, Class<?> targetClass, CoercionInputShape inputShape)161 public CoercionAction findCoercion(DeserializationConfig config, 162 LogicalType targetType, 163 Class<?> targetClass, CoercionInputShape inputShape) 164 { 165 // First, see if there is exact match for physical type 166 if ((_perClassCoercions != null) && (targetClass != null)) { 167 MutableCoercionConfig cc = _perClassCoercions.get(targetClass); 168 if (cc != null) { 169 CoercionAction act = cc.findAction(inputShape); 170 if (act != null) { 171 return act; 172 } 173 } 174 } 175 176 // If not, maybe by logical type 177 if ((_perTypeCoercions != null) && (targetType != null)) { 178 MutableCoercionConfig cc = _perTypeCoercions[targetType.ordinal()]; 179 if (cc != null) { 180 CoercionAction act = cc.findAction(inputShape); 181 if (act != null) { 182 return act; 183 } 184 } 185 } 186 187 // Barring that, default coercion for input shape? 188 CoercionAction act = _defaultCoercions.findAction(inputShape); 189 if (act != null) { 190 return act; 191 } 192 193 // Otherwise there are some legacy features that can provide answer 194 if (inputShape == CoercionInputShape.EmptyArray) { 195 // Default for setting is false 196 return config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT) ? 197 CoercionAction.AsNull : CoercionAction.Fail; 198 } 199 if ((inputShape == CoercionInputShape.Float) 200 && (targetType == LogicalType.Integer)) { 201 // Default for setting in 2.x is true 202 return config.isEnabled(DeserializationFeature.ACCEPT_FLOAT_AS_INT) ? 203 CoercionAction.TryConvert : CoercionAction.Fail; 204 } 205 206 // classic scalars are numbers, booleans; but date/time also considered 207 // scalar for this particular purpose 208 final boolean baseScalar = (targetType == LogicalType.Float) 209 || (targetType == LogicalType.Integer) 210 || (targetType == LogicalType.Boolean) 211 || (targetType == LogicalType.DateTime); 212 213 if (baseScalar) { 214 // Default for setting in 2.x is true 215 if (!config.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS)) { 216 return CoercionAction.Fail; 217 } 218 } 219 220 if (inputShape == CoercionInputShape.EmptyString) { 221 // Since coercion of scalar must be enabled (see check above), allow empty-string 222 // coercions by default even without this setting 223 if (baseScalar 224 // Default for setting is false 225 || config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)) { 226 return CoercionAction.AsNull; 227 } 228 // 09-Jun-2020, tatu: Seems necessary to support backwards-compatibility with 229 // 2.11, wrt "FromStringDeserializer" supported types 230 if (targetType == LogicalType.OtherScalar) { 231 return CoercionAction.TryConvert; 232 } 233 // But block from allowing structured types like POJOs, Maps etc 234 return CoercionAction.Fail; 235 } 236 237 // and all else failing, return default 238 return _defaultAction; 239 } 240 241 /** 242 * More specialized accessor called in case of input being a blank 243 * String (one consisting of only white space characters with length of at least one). 244 * Will basically first determine if "blank as empty" is allowed: if not, 245 * returns {@code actionIfBlankNotAllowed}, otherwise returns action for 246 * {@link CoercionInputShape#EmptyString}. 247 * 248 * @param config Currently active deserialization configuration 249 * @param targetType Logical target type of coercion 250 * @param targetClass Physical target type of coercion 251 * @param actionIfBlankNotAllowed Return value to use in case "blanks as empty" 252 * is not allowed 253 * 254 * @return CoercionAction configured for specified coercion from blank string 255 */ findCoercionFromBlankString(DeserializationConfig config, LogicalType targetType, Class<?> targetClass, CoercionAction actionIfBlankNotAllowed)256 public CoercionAction findCoercionFromBlankString(DeserializationConfig config, 257 LogicalType targetType, 258 Class<?> targetClass, 259 CoercionAction actionIfBlankNotAllowed) 260 { 261 Boolean acceptBlankAsEmpty = null; 262 CoercionAction action = null; 263 264 // First, see if there is exact match for physical type 265 if ((_perClassCoercions != null) && (targetClass != null)) { 266 MutableCoercionConfig cc = _perClassCoercions.get(targetClass); 267 if (cc != null) { 268 acceptBlankAsEmpty = cc.getAcceptBlankAsEmpty(); 269 action = cc.findAction(CoercionInputShape.EmptyString); 270 } 271 } 272 273 // If not, maybe by logical type 274 if ((_perTypeCoercions != null) && (targetType != null)) { 275 MutableCoercionConfig cc = _perTypeCoercions[targetType.ordinal()]; 276 if (cc != null) { 277 if (acceptBlankAsEmpty == null) { 278 acceptBlankAsEmpty = cc.getAcceptBlankAsEmpty(); 279 } 280 if (action == null) { 281 action = cc.findAction(CoercionInputShape.EmptyString); 282 } 283 } 284 } 285 286 // Barring that, default coercion for input shape? 287 if (acceptBlankAsEmpty == null) { 288 acceptBlankAsEmpty = _defaultCoercions.getAcceptBlankAsEmpty(); 289 } 290 if (action == null) { 291 action = _defaultCoercions.findAction(CoercionInputShape.EmptyString); 292 } 293 294 // First: if using blank as empty is no-go, return what caller specified 295 if (!Boolean.TRUE.equals(acceptBlankAsEmpty)) { 296 return actionIfBlankNotAllowed; 297 } 298 299 // Otherwise, if action found, return that 300 if (action != null) { 301 return action; 302 } 303 // If not, one specific legacy setting to consider... 304 return config.isEnabled(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) ? 305 CoercionAction.AsNull : CoercionAction.Fail; 306 } 307 } 308