• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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