1 package com.fasterxml.jackson.databind; 2 3 import java.io.Closeable; 4 import java.io.IOException; 5 import java.io.Serializable; 6 import java.util.*; 7 8 import com.fasterxml.jackson.annotation.JsonIgnore; 9 import com.fasterxml.jackson.core.*; 10 import com.fasterxml.jackson.databind.util.ClassUtil; 11 12 /** 13 * Checked exception used to signal fatal problems with mapping of 14 * content, distinct from low-level I/O problems (signaled using 15 * simple {@link java.io.IOException}s) or data encoding/decoding 16 * problems (signaled with {@link com.fasterxml.jackson.core.JsonParseException}, 17 * {@link com.fasterxml.jackson.core.JsonGenerationException}). 18 *<p> 19 * One additional feature is the ability to denote relevant path 20 * of references (during serialization/deserialization) to help in 21 * troubleshooting. 22 */ 23 public class JsonMappingException 24 extends JsonProcessingException 25 { 26 private static final long serialVersionUID = 1L; 27 28 /** 29 * Let's limit length of reference chain, to limit damage in cases 30 * of infinite recursion. 31 */ 32 final static int MAX_REFS_TO_LIST = 1000; 33 34 /* 35 /********************************************************** 36 /* Helper classes 37 /********************************************************** 38 */ 39 40 /** 41 * Simple bean class used to contain references. References 42 * can be added to indicate execution/reference path that 43 * lead to the problem that caused this exception to be 44 * thrown. 45 */ 46 public static class Reference implements Serializable 47 { 48 private static final long serialVersionUID = 2L; // changes between 2.7 and 2.8 49 50 // transient since 2.8 51 protected transient Object _from; 52 53 /** 54 * Name of field (for beans) or key (for Maps) that is part 55 * of the reference. May be null for Collection types (which 56 * generally have {@link #_index} defined), or when resolving 57 * Map classes without (yet) having an instance to operate on. 58 */ 59 protected String _fieldName; 60 61 /** 62 * Index within a {@link Collection} instance that contained 63 * the reference; used if index is relevant and available. 64 * If either not applicable, or not available, -1 is used to 65 * denote "not known" (or not relevant). 66 */ 67 protected int _index = -1; 68 69 /** 70 * Lazily-constructed description of this instance; needed mostly to 71 * allow JDK serialization to work in case where {@link #_from} is 72 * non-serializable (and has to be dropped) but we still want to pass 73 * actual description along. 74 * 75 * @since 2.8 76 */ 77 protected String _desc; 78 79 /** 80 * Default constructor for deserialization/sub-classing purposes 81 */ Reference()82 protected Reference() { } 83 Reference(Object from)84 public Reference(Object from) { _from = from; } 85 Reference(Object from, String fieldName)86 public Reference(Object from, String fieldName) { 87 _from = from; 88 if (fieldName == null) { 89 throw new NullPointerException("Cannot pass null fieldName"); 90 } 91 _fieldName = fieldName; 92 } 93 Reference(Object from, int index)94 public Reference(Object from, int index) { 95 _from = from; 96 _index = index; 97 } 98 99 // Setters to let Jackson deserialize instances, but not to be called from outside setFieldName(String n)100 void setFieldName(String n) { _fieldName = n; } setIndex(int ix)101 void setIndex(int ix) { _index = ix; } setDescription(String d)102 void setDescription(String d) { _desc = d; } 103 104 /** 105 * Object through which reference was resolved. Can be either 106 * actual instance (usually the case for serialization), or 107 * Class (usually the case for deserialization). 108 *<p> 109 * Note that this value must be `transient` to allow serializability (as 110 * often such Object is NOT serializable; or, in case of `Class`, may 111 * not available at the point of deserialization). As such will return 112 * `null` if instance has been passed using JDK serialization. 113 */ 114 @JsonIgnore getFrom()115 public Object getFrom() { return _from; } 116 getFieldName()117 public String getFieldName() { return _fieldName; } getIndex()118 public int getIndex() { return _index; } getDescription()119 public String getDescription() { 120 if (_desc == null) { 121 StringBuilder sb = new StringBuilder(); 122 123 if (_from == null) { // can this ever occur? 124 sb.append("UNKNOWN"); 125 } else { 126 Class<?> cls = (_from instanceof Class<?>) ? (Class<?>)_from : _from.getClass(); 127 // Hmmh. Although Class.getName() is mostly ok, it does look 128 // butt-ugly for arrays. 129 // 06-Oct-2016, tatu: as per [databind#1403], `getSimpleName()` not so good 130 // as it drops enclosing class. So let's try bit different approach 131 int arrays = 0; 132 while (cls.isArray()) { 133 cls = cls.getComponentType(); 134 ++arrays; 135 } 136 sb.append(cls.getName()); 137 while (--arrays >= 0) { 138 sb.append("[]"); 139 } 140 /* was: 141 String pkgName = ClassUtil.getPackageName(cls); 142 if (pkgName != null) { 143 sb.append(pkgName); 144 sb.append('.'); 145 } 146 */ 147 } 148 sb.append('['); 149 if (_fieldName != null) { 150 sb.append('"'); 151 sb.append(_fieldName); 152 sb.append('"'); 153 } else if (_index >= 0) { 154 sb.append(_index); 155 } else { 156 sb.append('?'); 157 } 158 sb.append(']'); 159 _desc = sb.toString(); 160 } 161 return _desc; 162 } 163 164 @Override toString()165 public String toString() { 166 return getDescription(); 167 } 168 169 /** 170 * May need some cleaning here, given that `from` may or may not be serializable. 171 * 172 * since 2.8 173 */ writeReplace()174 Object writeReplace() { 175 // as per [databind#1195], need to ensure description is not null, since 176 // `_from` is transient 177 getDescription(); 178 return this; 179 } 180 } 181 182 /* 183 /********************************************************** 184 /* State/configuration 185 /********************************************************** 186 */ 187 188 /** 189 * Path through which problem that triggering throwing of 190 * this exception was reached. 191 */ 192 protected LinkedList<Reference> _path; 193 194 /** 195 * Underlying processor ({@link JsonParser} or {@link JsonGenerator}), 196 * if known. 197 *<p> 198 * NOTE: typically not serializable hence <code>transient</code> 199 * 200 * @since 2.7 201 */ 202 protected transient Closeable _processor; 203 204 /* 205 /********************************************************** 206 /* Life-cycle 207 /********************************************************** 208 */ 209 210 /** 211 * @deprecated Since 2.7 Use variant that takes {@link JsonParser} instead 212 */ 213 @Deprecated // since 2.7 JsonMappingException(String msg)214 public JsonMappingException(String msg) { super(msg); } 215 216 /** 217 * @deprecated Since 2.7 Use variant that takes {@link JsonParser} instead 218 */ 219 @Deprecated // since 2.7 JsonMappingException(String msg, Throwable rootCause)220 public JsonMappingException(String msg, Throwable rootCause) { super(msg, rootCause); } 221 222 /** 223 * @deprecated Since 2.7 Use variant that takes {@link JsonParser} instead 224 */ 225 @Deprecated // since 2.7 JsonMappingException(String msg, JsonLocation loc)226 public JsonMappingException(String msg, JsonLocation loc) { super(msg, loc); } 227 228 /** 229 * @deprecated Since 2.7 Use variant that takes {@link JsonParser} instead 230 */ 231 @Deprecated // since 2.7 JsonMappingException(String msg, JsonLocation loc, Throwable rootCause)232 public JsonMappingException(String msg, JsonLocation loc, Throwable rootCause) { super(msg, loc, rootCause); } 233 234 /** 235 * @since 2.7 236 */ JsonMappingException(Closeable processor, String msg)237 public JsonMappingException(Closeable processor, String msg) { 238 super(msg); 239 _processor = processor; 240 if (processor instanceof JsonParser) { 241 // 17-Aug-2015, tatu: Use of token location makes some sense from databinding, 242 // since actual parsing (current) location is typically only needed for low-level 243 // parsing exceptions. 244 _location = ((JsonParser) processor).getTokenLocation(); 245 } 246 } 247 248 /** 249 * @since 2.7 250 */ JsonMappingException(Closeable processor, String msg, Throwable problem)251 public JsonMappingException(Closeable processor, String msg, Throwable problem) { 252 super(msg, problem); 253 _processor = processor; 254 // 31-Jan-2020: [databind#2482] Retain original location 255 if (problem instanceof JsonProcessingException) { 256 _location = ((JsonProcessingException) problem).getLocation(); 257 } else if (processor instanceof JsonParser) { 258 _location = ((JsonParser) processor).getTokenLocation(); 259 } 260 } 261 262 /** 263 * @since 2.7 264 */ JsonMappingException(Closeable processor, String msg, JsonLocation loc)265 public JsonMappingException(Closeable processor, String msg, JsonLocation loc) { 266 super(msg, loc); 267 _processor = processor; 268 } 269 270 /** 271 * @since 2.7 272 */ from(JsonParser p, String msg)273 public static JsonMappingException from(JsonParser p, String msg) { 274 return new JsonMappingException(p, msg); 275 } 276 277 /** 278 * @since 2.7 279 */ from(JsonParser p, String msg, Throwable problem)280 public static JsonMappingException from(JsonParser p, String msg, Throwable problem) { 281 return new JsonMappingException(p, msg, problem); 282 } 283 284 /** 285 * @since 2.7 286 */ from(JsonGenerator g, String msg)287 public static JsonMappingException from(JsonGenerator g, String msg) { 288 return new JsonMappingException(g, msg, (Throwable) null); 289 } 290 291 /** 292 * @since 2.7 293 */ from(JsonGenerator g, String msg, Throwable problem)294 public static JsonMappingException from(JsonGenerator g, String msg, Throwable problem) { 295 return new JsonMappingException(g, msg, problem); 296 } 297 298 /** 299 * @since 2.7 300 */ from(DeserializationContext ctxt, String msg)301 public static JsonMappingException from(DeserializationContext ctxt, String msg) { 302 return new JsonMappingException(ctxt.getParser(), msg); 303 } 304 305 /** 306 * @since 2.7 307 */ from(DeserializationContext ctxt, String msg, Throwable t)308 public static JsonMappingException from(DeserializationContext ctxt, String msg, Throwable t) { 309 return new JsonMappingException(ctxt.getParser(), msg, t); 310 } 311 312 /** 313 * @since 2.7 314 */ from(SerializerProvider ctxt, String msg)315 public static JsonMappingException from(SerializerProvider ctxt, String msg) { 316 return new JsonMappingException(ctxt.getGenerator(), msg); 317 } 318 319 /** 320 * @since 2.7 321 */ from(SerializerProvider ctxt, String msg, Throwable problem)322 public static JsonMappingException from(SerializerProvider ctxt, String msg, Throwable problem) { 323 /* 17-Aug-2015, tatu: As per [databind#903] this is bit problematic as 324 * SerializerProvider instance does not currently hold on to generator... 325 */ 326 return new JsonMappingException(ctxt.getGenerator(), msg, problem); 327 } 328 329 /** 330 * Factory method used when "upgrading" an {@link IOException} into 331 * {@link JsonMappingException}: usually only needed to comply with 332 * a signature. 333 *<p> 334 * NOTE: since 2.9 should usually NOT be used on input-side (deserialization) 335 * exceptions; instead use method(s) of <code>InputMismatchException</code> 336 * 337 * @since 2.1 338 */ fromUnexpectedIOE(IOException src)339 public static JsonMappingException fromUnexpectedIOE(IOException src) { 340 return new JsonMappingException(null, 341 String.format("Unexpected IOException (of type %s): %s", 342 src.getClass().getName(), 343 ClassUtil.exceptionMessage(src))); 344 } 345 346 /** 347 * Method that can be called to either create a new JsonMappingException 348 * (if underlying exception is not a JsonMappingException), or augment 349 * given exception with given path/reference information. 350 * 351 * This version of method is called when the reference is through a 352 * non-indexed object, such as a Map or POJO/bean. 353 */ wrapWithPath(Throwable src, Object refFrom, String refFieldName)354 public static JsonMappingException wrapWithPath(Throwable src, Object refFrom, 355 String refFieldName) { 356 return wrapWithPath(src, new Reference(refFrom, refFieldName)); 357 } 358 359 /** 360 * Method that can be called to either create a new JsonMappingException 361 * (if underlying exception is not a JsonMappingException), or augment 362 * given exception with given path/reference information. 363 * 364 * This version of method is called when the reference is through an 365 * index, which happens with arrays and Collections. 366 */ wrapWithPath(Throwable src, Object refFrom, int index)367 public static JsonMappingException wrapWithPath(Throwable src, Object refFrom, int index) { 368 return wrapWithPath(src, new Reference(refFrom, index)); 369 } 370 371 /** 372 * Method that can be called to either create a new JsonMappingException 373 * (if underlying exception is not a JsonMappingException), or augment 374 * given exception with given path/reference information. 375 */ 376 @SuppressWarnings("resource") wrapWithPath(Throwable src, Reference ref)377 public static JsonMappingException wrapWithPath(Throwable src, Reference ref) 378 { 379 JsonMappingException jme; 380 if (src instanceof JsonMappingException) { 381 jme = (JsonMappingException) src; 382 } else { 383 // [databind#2128]: try to avoid duplication 384 String msg = ClassUtil.exceptionMessage(src); 385 // Let's use a more meaningful placeholder if all we have is null 386 if (msg == null || msg.length() == 0) { 387 msg = "(was "+src.getClass().getName()+")"; 388 } 389 // 17-Aug-2015, tatu: Let's also pass the processor (parser/generator) along 390 Closeable proc = null; 391 if (src instanceof JsonProcessingException) { 392 Object proc0 = ((JsonProcessingException) src).getProcessor(); 393 if (proc0 instanceof Closeable) { 394 proc = (Closeable) proc0; 395 } 396 } 397 jme = new JsonMappingException(proc, msg, src); 398 } 399 jme.prependPath(ref); 400 return jme; 401 } 402 403 /* 404 /********************************************************** 405 /* Accessors/mutators 406 /********************************************************** 407 */ 408 409 /** 410 * Method for accessing full structural path within type hierarchy 411 * down to problematic property. 412 */ getPath()413 public List<Reference> getPath() 414 { 415 if (_path == null) { 416 return Collections.emptyList(); 417 } 418 return Collections.unmodifiableList(_path); 419 } 420 421 /** 422 * Method for accessing description of path that lead to the 423 * problem that triggered this exception 424 */ getPathReference()425 public String getPathReference() 426 { 427 return getPathReference(new StringBuilder()).toString(); 428 } 429 getPathReference(StringBuilder sb)430 public StringBuilder getPathReference(StringBuilder sb) 431 { 432 _appendPathDesc(sb); 433 return sb; 434 } 435 436 /** 437 * Method called to prepend a reference information in front of 438 * current path 439 */ prependPath(Object referrer, String fieldName)440 public void prependPath(Object referrer, String fieldName) 441 { 442 Reference ref = new Reference(referrer, fieldName); 443 prependPath(ref); 444 } 445 /** 446 * Method called to prepend a reference information in front of 447 * current path 448 */ prependPath(Object referrer, int index)449 public void prependPath(Object referrer, int index) 450 { 451 Reference ref = new Reference(referrer, index); 452 prependPath(ref); 453 } 454 prependPath(Reference r)455 public void prependPath(Reference r) 456 { 457 if (_path == null) { 458 _path = new LinkedList<Reference>(); 459 } 460 /* Also: let's not increase without bounds. Could choose either 461 * head or tail; tail is easier (no need to ever remove), as 462 * well as potentially more useful so let's use it: 463 */ 464 if (_path.size() < MAX_REFS_TO_LIST) { 465 _path.addFirst(r); 466 } 467 } 468 469 /* 470 /********************************************************** 471 /* Overridden methods 472 /********************************************************** 473 */ 474 475 @Override // since 2.7.5 476 @JsonIgnore // as per [databind#1368] getProcessor()477 public Object getProcessor() { return _processor; } 478 479 @Override getLocalizedMessage()480 public String getLocalizedMessage() { 481 return _buildMessage(); 482 } 483 484 /** 485 * Method is overridden so that we can properly inject description 486 * of problem path, if such is defined. 487 */ 488 @Override getMessage()489 public String getMessage() { 490 return _buildMessage(); 491 } 492 _buildMessage()493 protected String _buildMessage() 494 { 495 // First: if we have no path info, let's just use parent's definition as is 496 String msg = super.getMessage(); 497 if (_path == null) { 498 return msg; 499 } 500 StringBuilder sb = (msg == null) ? new StringBuilder() : new StringBuilder(msg); 501 /* 18-Feb-2009, tatu: initially there was a linefeed between 502 * message and path reference; but unfortunately many systems 503 * (loggers, junit) seem to assume linefeeds are only added to 504 * separate stack trace. 505 */ 506 sb.append(" (through reference chain: "); 507 sb = getPathReference(sb); 508 sb.append(')'); 509 return sb.toString(); 510 } 511 512 @Override toString()513 public String toString() 514 { 515 return getClass().getName()+": "+getMessage(); 516 } 517 518 /* 519 /********************************************************** 520 /* Internal methods 521 /********************************************************** 522 */ 523 _appendPathDesc(StringBuilder sb)524 protected void _appendPathDesc(StringBuilder sb) 525 { 526 if (_path == null) { 527 return; 528 } 529 Iterator<Reference> it = _path.iterator(); 530 while (it.hasNext()) { 531 sb.append(it.next().toString()); 532 if (it.hasNext()) { 533 sb.append("->"); 534 } 535 } 536 } 537 } 538