From 7ee436025e6ec107cd146d7639da364b9fef0e33 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 11 Apr 2025 09:42:51 -0700 Subject: [PATCH 1/3] Initial update --- .../tools/jackson/databind/exc/UnwrapRootCause4603Test.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java b/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java index afa56e5c99..a923d9c976 100644 --- a/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java +++ b/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import tools.jackson.core.JacksonException; +import tools.jackson.core.TokenStreamLocation; import tools.jackson.databind.*; import tools.jackson.databind.testutil.DatabindTestUtil; @@ -85,5 +86,9 @@ public void testWithAnySetter() DatabindException result = assertThrows(DatabindException.class, () -> MAPPER.readValue(a2q("{'a':3}"), AnySetterBean.class)); assertInstanceOf(CustomException.class, result.getCause()); + TokenStreamLocation loc = result.getLocation(); + assertNotSame(TokenStreamLocation.NA, loc); + assertEquals(1, loc.getLineNr()); + assertEquals(2, loc.getColumnNr()); } } From a6c52c4a57b7063ffbab4152445200410cb08047 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 11 Apr 2025 15:20:14 -0700 Subject: [PATCH 2/3] Fix passing of context/parser, to preserve location --- .../jackson/databind/DatabindException.java | 71 ++++++++++++++----- .../databind/deser/SettableAnyProperty.java | 28 +++++--- .../databind/exc/UnwrapRootCause4603Test.java | 21 +++--- 3 files changed, 83 insertions(+), 37 deletions(-) diff --git a/src/main/java/tools/jackson/databind/DatabindException.java b/src/main/java/tools/jackson/databind/DatabindException.java index 55922ccde6..e998e0d7aa 100644 --- a/src/main/java/tools/jackson/databind/DatabindException.java +++ b/src/main/java/tools/jackson/databind/DatabindException.java @@ -35,11 +35,14 @@ protected DatabindException(Closeable processor, String msg, Throwable problem) super(processor, msg, problem); } - protected DatabindException(Closeable processor, String msg, TokenStreamLocation loc) - { - super(msg, loc, null); + protected DatabindException(Closeable processor, String msg, TokenStreamLocation loc) { + super(processor, msg, loc, null); } + protected DatabindException(Closeable processor, String msg, TokenStreamLocation loc, + Throwable rootCause) { + super(processor, msg, loc, rootCause); + } protected DatabindException(String msg, TokenStreamLocation loc, Throwable rootCause) { super(msg, loc, rootCause); } @@ -74,21 +77,61 @@ public static DatabindException from(DeserializationContext ctxt, String msg) { return new DatabindException(_parser(ctxt), msg); } + public static DatabindException from(DeserializationContext ctxt, String msg, Throwable problem) { + return new DatabindException(_parser(ctxt), msg, problem); + } + + public static DatabindException from(SerializationContext ctxt, String msg) { + return new DatabindException(_generator(ctxt), msg); + } + + public static DatabindException from(SerializationContext ctxt, String msg, Throwable problem) { + return new DatabindException(_generator(ctxt), msg, problem); + } + // // "Overrides": methods with name same as ones from JacksonException // // (but for static methods no real overriding, static dispatch) public static JacksonException wrapWithPath(DeserializationContext ctxt, Throwable src, Reference ref) { - // !!! TODO: get `JsonParser`, location from `ctxt` - return JacksonException.wrapWithPath(src, ref, - (msg, cause) -> new DatabindException(msg, null, cause)); + JsonParser p = _parser(ctxt); + + // Copied from JacksonException.wrapWithPath() + JacksonException jme; + if (src instanceof JacksonException) { + jme = (JacksonException) src; + } else { + // [databind#2128]: try to avoid duplication + String msg = _exceptionMessage(src); + // Let's use a more meaningful placeholder if all we have is null + if (msg == null || msg.isEmpty()) { + msg = "(was "+src.getClass().getName()+")"; + } + TokenStreamLocation loc = (p == null) ? null : p.currentLocation(); + jme = new DatabindException(p, msg, loc, src); + } + jme.prependPath(ref); + return jme; } public static JacksonException wrapWithPath(SerializationContext ctxt, Throwable src, Reference ref) { - // !!! TODO: get `JsonGenerator` from `ctxt` - return JacksonException.wrapWithPath(src, ref, - (msg, cause) -> new DatabindException(msg, null, cause)); + JsonGenerator g = _generator(ctxt); + + // Copied from JacksonException.wrapWithPath() + JacksonException jme; + if (src instanceof JacksonException) { + jme = (JacksonException) src; + } else { + String msg = _exceptionMessage(src); + if (msg == null || msg.isEmpty()) { + msg = "(was "+src.getClass().getName()+")"; + } + TokenStreamLocation loc = null; + jme = new DatabindException(g, msg, loc, src); + } + jme.prependPath(ref); + return jme; } /* @@ -101,16 +144,6 @@ private static JsonParser _parser(DeserializationContext ctxt) { return (ctxt == null) ? null : ctxt.getParser(); } - public static DatabindException from(SerializationContext ctxt, String msg) { - return new DatabindException(_generator(ctxt), msg); - } - - public static DatabindException from(SerializationContext ctxt, String msg, Throwable problem) { - // 17-Aug-2015, tatu: As per [databind#903] this is bit problematic as - // SerializationContext instance does not currently hold on to generator... - return new DatabindException(_generator(ctxt), msg, problem); - } - private static JsonGenerator _generator(SerializationContext ctxt) { return (ctxt == null) ? null : ctxt.getGenerator(); } diff --git a/src/main/java/tools/jackson/databind/deser/SettableAnyProperty.java b/src/main/java/tools/jackson/databind/deser/SettableAnyProperty.java index c1d3d3f3c4..da80484c3b 100644 --- a/src/main/java/tools/jackson/databind/deser/SettableAnyProperty.java +++ b/src/main/java/tools/jackson/databind/deser/SettableAnyProperty.java @@ -1,5 +1,6 @@ package tools.jackson.databind.deser; +import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -247,7 +248,7 @@ public void set(DeserializationContext ctxt, Object instance, Object propName, O } catch (JacksonException e) { throw e; } catch (Exception e) { - _throwAsIOE(e, propName, value); + _throwAsIOE(ctxt, e, propName, value); } } @@ -261,31 +262,38 @@ protected abstract void _set(DeserializationContext ctxt, Object instance, Objec */ /** - * @param e Exception to re-throw or wrap + * @param t Exception to re-throw or wrap * @param propName Name of property (from Json input) to set * @param value Value of the property */ - protected void _throwAsIOE(Exception e, Object propName, Object value) + protected void _throwAsIOE(DeserializationContext ctxt, Throwable t, Object propName, Object value) throws JacksonException { - if (e instanceof IllegalArgumentException) { + if (t instanceof IllegalArgumentException) { String actType = ClassUtil.classNameOf(value); StringBuilder msg = new StringBuilder("Problem deserializing \"any-property\" '").append(propName); msg.append("' of class "+getClassName()+" (expected type: ").append(_type); msg.append("; actual type: ").append(actType).append(")"); - String origMsg = ClassUtil.exceptionMessage(e); + String origMsg = ClassUtil.exceptionMessage(t); if (origMsg != null) { msg.append(", problem: ").append(origMsg); } else { msg.append(" (no error message provided)"); } - throw DatabindException.from((JsonParser) null, msg.toString(), e); + throw DatabindException.from(ctxt, msg.toString(), t); } - ClassUtil.throwIfJacksonE(e); - ClassUtil.throwIfRTE(e); + ClassUtil.throwIfJacksonE(t); + ClassUtil.throwIfRTE(t); // let's wrap the innermost problem - Throwable t = ClassUtil.getRootCause(e); - throw DatabindException.from((JsonParser) null, ClassUtil.exceptionMessage(t), t); + // 11-Apr-2025: [databind#4603] no more general unwrapping + // Throwable t = ClassUtil.getRootCause(e); + // ... just one: + if (t instanceof InvocationTargetException ite) { + t = ite.getTargetException(); + ClassUtil.throwIfRTE(t); + ClassUtil.throwIfJacksonE(t); + } + throw DatabindException.from(ctxt, ClassUtil.exceptionMessage(t), t); } private String getClassName() { return ClassUtil.nameOf(_setter.getDeclaringClass()); } diff --git a/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java b/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java index a923d9c976..938ea2606d 100644 --- a/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java +++ b/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java @@ -57,8 +57,6 @@ public String getQux() { } } - private static final String JSON = a2q("{'value':3}"); - private final ObjectMapper MAPPER = newJsonMapper(); // Whether disabled or enabled, should get ArithmeticException @@ -66,16 +64,22 @@ public String getQux() { public void testExceptionWrappingConfiguration() throws Exception { - JacksonException disabledResult = _tryDeserializeWith(MAPPER); - assertInstanceOf(DatabindException.class, disabledResult); + JacksonException result = _tryDeserializeWith(MAPPER); + assertInstanceOf(DatabindException.class, result); // We are throwing exception inside a setter, but InvocationTargetException // will still be unwrapped - assertInstanceOf(CustomException.class, disabledResult.getCause()); + assertInstanceOf(CustomException.class, result.getCause()); + + TokenStreamLocation loc = result.getLocation(); + assertNotSame(TokenStreamLocation.NA, loc); + // happens to point to location after `3` + assertEquals(1, loc.getLineNr()); + assertEquals(11, loc.getColumnNr()); } private JacksonException _tryDeserializeWith(ObjectMapper mapper) { return assertThrows(JacksonException.class, - () -> mapper.readValue(JSON, Feature1347DeserBean.class) + () -> mapper.readValue(a2q("{'value':3}"), Feature1347DeserBean.class) ); } @@ -84,11 +88,12 @@ public void testWithAnySetter() throws Exception { DatabindException result = assertThrows(DatabindException.class, - () -> MAPPER.readValue(a2q("{'a':3}"), AnySetterBean.class)); + () -> MAPPER.readValue(a2q("{'a':72}"), AnySetterBean.class)); assertInstanceOf(CustomException.class, result.getCause()); TokenStreamLocation loc = result.getLocation(); assertNotSame(TokenStreamLocation.NA, loc); + // happens to point to location after `72` assertEquals(1, loc.getLineNr()); - assertEquals(2, loc.getColumnNr()); + assertEquals(8, loc.getColumnNr()); } } From 88e6fa54bb3191c70c6081f0aef1a3853aa8402c Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Fri, 11 Apr 2025 15:23:13 -0700 Subject: [PATCH 3/3] ... --- src/main/java/tools/jackson/databind/DatabindException.java | 1 + .../java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/tools/jackson/databind/DatabindException.java b/src/main/java/tools/jackson/databind/DatabindException.java index e998e0d7aa..73d4377c39 100644 --- a/src/main/java/tools/jackson/databind/DatabindException.java +++ b/src/main/java/tools/jackson/databind/DatabindException.java @@ -127,6 +127,7 @@ public static JacksonException wrapWithPath(SerializationContext ctxt, if (msg == null || msg.isEmpty()) { msg = "(was "+src.getClass().getName()+")"; } + // 2025-04-11, tatu: No location from generator, currently TokenStreamLocation loc = null; jme = new DatabindException(g, msg, loc, src); } diff --git a/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java b/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java index 938ea2606d..edeac6b291 100644 --- a/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java +++ b/src/test/java/tools/jackson/databind/exc/UnwrapRootCause4603Test.java @@ -69,7 +69,6 @@ public void testExceptionWrappingConfiguration() // We are throwing exception inside a setter, but InvocationTargetException // will still be unwrapped assertInstanceOf(CustomException.class, result.getCause()); - TokenStreamLocation loc = result.getLocation(); assertNotSame(TokenStreamLocation.NA, loc); // happens to point to location after `3`