Skip to content

Commit ab9c920

Browse files
committed
Merge branch '2.11'
2 parents eed5960 + 554167c commit ab9c920

File tree

7 files changed

+168
-36
lines changed

7 files changed

+168
-36
lines changed

release-notes/CREDITS-2.x

+5
Original file line numberDiff line numberDiff line change
@@ -1010,3 +1010,8 @@ Fitz (Joongsoo.Park) (joongsoo@github)
10101010
Antonio Petrelli (apetrelli@github)
10111011
* Reported #2049: TreeTraversingParser and UTF8StreamJsonParser create contexts differently
10121012
(2.11.0)
1013+
1014+
Joseph Koshakow (jkosh44@github)
1015+
* Contributed fix for #2515: `ObjectMapper.registerSubtypes(NamedType...)` doesn't allow registering
1016+
the same POJO for two different type ids
1017+
(contributed by Joseph K)

release-notes/VERSION-2.x

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Project: jackson-databind
1313
#2503: Support `@JsonSerialize(keyUsing)` and `@JsonDeserialize(keyUsing)` on Key class
1414
#2511: Add `SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL`
1515
(contributed by Joongsoo P)
16+
#2515: `ObjectMapper.registerSubtypes(NamedType...)` doesn't allow registering the same POJO
17+
for two different type ids
18+
(contributed by Joseph K)
1619
#2522: `DeserializationContext.handleMissingInstantiator()` throws `MismatchedInputException`
1720
for non-static inner classes
1821
#2525: Incorrect `JsonStreamContext` for `TokenBuffer` and `TreeTraversingParser`
@@ -37,6 +40,7 @@ Project: jackson-databind
3740
#2520: Sub-optimal exception message when failing to deserialize non-static inner classes
3841
(reported by Mark S)
3942
#2529: Add tests to ensure `EnumSet` and `EnumMap` work correctly with "null-as-empty"
43+
#2534: Add `BasicPolymorphicTypeValidator.Builder.allowIfSubTypeIsArray()`
4044
4145
2.10.0 (26-Sep-2019)
4246

src/main/java/com/fasterxml/jackson/databind/jsontype/BasicPolymorphicTypeValidator.java

+60-3
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ public class BasicPolymorphicTypeValidator
3737
* General matcher interface (predicate) for validating class values
3838
* (base type or resolved subtype)
3939
*/
40-
protected abstract static class TypeMatcher {
40+
public abstract static class TypeMatcher { // note: public since 2.11
4141
public abstract boolean match(Class<?> clazz);
4242
}
4343

4444
/**
4545
* General matcher interface (predicate) for validating unresolved
4646
* subclass class name.
4747
*/
48-
protected abstract static class NameMatcher {
48+
public abstract static class NameMatcher { // note: public since 2.11
4949
public abstract boolean match(String clazzName);
5050
}
5151

@@ -64,6 +64,11 @@ protected abstract static class NameMatcher {
6464
* rules are checked.
6565
*/
6666
public static class Builder {
67+
/**
68+
* Optional set of base types (exact match) that are NOT accepted
69+
* as base types for polymorphic properties. May be used to prevent "unsafe"
70+
* base types like {@link java.lang.Object} or {@link java.io.Serializable}.
71+
*/
6772
protected Set<Class<?>> _invalidBaseTypes;
6873

6974
/**
@@ -81,7 +86,6 @@ public static class Builder {
8186
*/
8287
protected List<TypeMatcher> _subTypeClassMatchers;
8388

84-
8589
protected Builder() { }
8690

8791
// // Methods for checking solely by base type (before subtype even considered)
@@ -152,6 +156,21 @@ public boolean match(Class<?> clazz) {
152156
});
153157
}
154158

159+
/**
160+
* Method for appending custom matcher called with base type: if matcher returns
161+
* {@code true}, all possible subtypes will be accepted; if {@code false}, other
162+
* matchers are applied.
163+
*
164+
* @param matcher Custom matcher to apply to base type
165+
*
166+
* @return This Builder to allow call chaining
167+
*
168+
* @since 2.11
169+
*/
170+
public Builder allowIfBaseType(final TypeMatcher matcher) {
171+
return _appendBaseMatcher(matcher);
172+
}
173+
155174
/**
156175
* Method for appending matcher that will mark any polymorphic properties with exact
157176
* specific class to be invalid.
@@ -239,6 +258,44 @@ public boolean match(String clazzName) {
239258
});
240259
}
241260

261+
/**
262+
* Method for appending custom matcher called with resolved subtype: if matcher returns
263+
* {@code true}, type will be accepted; if {@code false}, other
264+
* matchers are applied.
265+
*
266+
* @param matcher Custom matcher to apply to resolved subtype
267+
*
268+
* @return This Builder to allow call chaining
269+
*
270+
* @since 2.11
271+
*/
272+
public Builder allowIfSubType(final TypeMatcher matcher) {
273+
return _appendSubClassMatcher(matcher);
274+
}
275+
276+
/**
277+
* Method for appending matcher that will allow all subtypes that are Java arrays
278+
* (regardless of element type). Note that this does NOT validate element type
279+
* itself as long as Polymorphic Type handling is enabled for element type: this
280+
* is the case with all standard "Default Typing" inclusion criteria as well as for
281+
* annotation ({@code @JsonTypeInfo}) use case (since annotation only applies to element
282+
* types, not container).
283+
*<p>
284+
* NOTE: not used with other Java collection types ({@link java.util.List}s,
285+
* {@link java.util.Collection}s), mostly since use of generic types as polymorphic
286+
* values is not (well) supported.
287+
*
288+
* @since 2.10.1
289+
*/
290+
public Builder allowIfSubTypeIsArray() {
291+
return _appendSubClassMatcher(new TypeMatcher() {
292+
@Override
293+
public boolean match(Class<?> clazz) {
294+
return clazz.isArray();
295+
}
296+
});
297+
}
298+
242299
public BasicPolymorphicTypeValidator build() {
243300
return new BasicPolymorphicTypeValidator(_invalidBaseTypes,
244301
(_baseTypeMatchers == null) ? null : _baseTypeMatchers.toArray(new TypeMatcher[0]),

src/main/java/com/fasterxml/jackson/databind/jsontype/NamedType.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.fasterxml.jackson.databind.jsontype;
22

3+
import java.util.Objects;
4+
35
/**
46
* Simple container class for types with optional logical name, used
57
* as external identifier
@@ -14,10 +16,10 @@ public final class NamedType implements java.io.Serializable
1416
protected String _name;
1517

1618
public NamedType(Class<?> c) { this(c, null); }
17-
19+
1820
public NamedType(Class<?> c, String name) {
1921
_class = c;
20-
_hashCode = c.getName().hashCode();
22+
_hashCode = c.getName().hashCode() + ((name == null) ? 0 : name.hashCode());
2123
setName(name);
2224
}
2325

@@ -28,14 +30,16 @@ public NamedType(Class<?> c, String name) {
2830
public boolean hasName() { return _name != null; }
2931

3032
/**
31-
* Equality is defined based on class only, not on name
33+
* Equality is defined based on class and name
3234
*/
3335
@Override
3436
public boolean equals(Object o) {
3537
if (o == this) return true;
3638
if (o == null) return false;
3739
if (o.getClass() != getClass()) return false;
38-
return _class == ((NamedType) o)._class;
40+
NamedType other = (NamedType)o;
41+
return (_class == other._class)
42+
&& Objects.equals(_name, other._name);
3943
}
4044

4145
@Override

src/main/java/com/fasterxml/jackson/databind/jsontype/impl/StdSubtypeResolver.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ public Collection<NamedType> collectAndResolveSubtypesByClass(MapperConfig<?> co
128128
{
129129
final AnnotationIntrospector ai = config.getAnnotationIntrospector();
130130
HashMap<NamedType, NamedType> subtypes = new HashMap<>();
131+
131132
// then consider registered subtypes (which have precedence over annotations)
132133
if (_registeredSubtypes != null) {
133134
Class<?> rawBase = type.getRawType();
@@ -238,19 +239,23 @@ protected void _collectAndResolve(MapperConfig<?> config, AnnotatedClass annotat
238239
}
239240
}
240241

242+
//For Serialization we only want to return a single NamedType per class so it's
243+
//unambiguous what name we use.
244+
NamedType typeOnlyNamedType = new NamedType(namedType.getType());
245+
241246
// First things first: is base type itself included?
242-
if (collectedSubtypes.containsKey(namedType)) {
247+
if (collectedSubtypes.containsKey(typeOnlyNamedType)) {
243248
// if so, no recursion; however, may need to update name?
244249
if (namedType.hasName()) {
245-
NamedType prev = collectedSubtypes.get(namedType);
250+
NamedType prev = collectedSubtypes.get(typeOnlyNamedType);
246251
if (!prev.hasName()) {
247-
collectedSubtypes.put(namedType, namedType);
252+
collectedSubtypes.put(typeOnlyNamedType, namedType);
248253
}
249254
}
250255
return;
251256
}
252257
// if it wasn't, add and check subtypes recursively
253-
collectedSubtypes.put(namedType, namedType);
258+
collectedSubtypes.put(typeOnlyNamedType, namedType);
254259
Collection<NamedType> st = ai.findSubtypes(config, annotatedType);
255260
if (st != null && !st.isEmpty()) {
256261
for (NamedType subtype : st) {
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,33 @@
1-
package com.fasterxml.jackson.failing;
2-
3-
import java.util.regex.Pattern;
1+
package com.fasterxml.jackson.databind.jsontype.vld;
42

53
import com.fasterxml.jackson.databind.*;
64
import com.fasterxml.jackson.databind.DefaultTyping;
7-
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
85
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
96
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
107
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
118

129
/**
13-
* Tests for the main user-configurable {@code PolymorphicTypeValidator},
14-
* {@link BasicPolymorphicTypeValidator}.
10+
* Tests related to [databind#2534], support for configuring
11+
* {@link BasicPolymorphicTypeValidator} to allow all Array-valued
12+
* polymorphic values.
1513
*/
16-
public class BasicPTV2532Test extends BaseMapTest
14+
public class BasicPTVWithArraysTest extends BaseMapTest
1715
{
18-
// // // Value types
19-
20-
static abstract class Base2532 {
16+
static abstract class Base2534 {
2117
public int x = 3;
2218
}
2319

24-
static class Good2532 extends Base2532 {
25-
protected Good2532() { }
26-
public Good2532(int x) {
20+
static class Good2534 extends Base2534 {
21+
protected Good2534() { }
22+
public Good2534(int x) {
2723
super();
2824
this.x = x;
2925
}
3026
}
3127

32-
static class Bad2532 extends Base2532 {
33-
protected Bad2532() { }
34-
public Bad2532(int x) {
28+
static class Bad2534 extends Base2534 {
29+
protected Bad2534() { }
30+
public Bad2534(int x) {
3531
super();
3632
this.x = x;
3733
}
@@ -50,19 +46,17 @@ protected ObjectWrapper() { }
5046
/**********************************************************************
5147
*/
5248

53-
// [databind#2532]: handle Java array-types appropriately wrt validation
49+
// [databind#2534]: handle Java array-types appropriately wrt validation
5450
public void testAllowBySubClassInArray() throws Exception {
5551
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
56-
.allowIfSubType(Good2532.class)
52+
.allowIfSubType(Good2534.class)
5753
.build();
5854
ObjectMapper mapper = jsonMapperBuilder()
5955
.activateDefaultTyping(ptv, DefaultTyping.NON_FINAL)
6056
.build();
6157

62-
final ObjectWrapper input = new ObjectWrapper(new Base2532[] { new Good2532(42) });
63-
final String json = mapper.writeValueAsString(input);
58+
final String json = mapper.writeValueAsString(new ObjectWrapper(new Base2534[] { new Good2534(42) }));
6459

65-
System.err.println("JSON: "+json);
6660
// First test blocked case:
6761
try {
6862
mapper.readValue(json, ObjectWrapper.class);
@@ -74,7 +68,8 @@ public void testAllowBySubClassInArray() throws Exception {
7468

7569
// and then accepted:
7670
ptv = BasicPolymorphicTypeValidator.builder()
77-
.allowIfSubType(Good2532.class)
71+
.allowIfSubTypeIsArray() // key addition
72+
.allowIfSubType(Good2534.class)
7873
.build();
7974
mapper = jsonMapperBuilder()
8075
.activateDefaultTyping(ptv, DefaultTyping.NON_FINAL)
@@ -83,10 +78,20 @@ public void testAllowBySubClassInArray() throws Exception {
8378
ObjectWrapper w = mapper.readValue(json, ObjectWrapper.class);
8479
assertNotNull(w);
8580
assertNotNull(w.value);
86-
assertEquals(Base2532[].class, w.value.getClass());
87-
Base2532[] arrayOut = (Base2532[]) w.value;
81+
assertEquals(Base2534[].class, w.value.getClass());
82+
Base2534[] arrayOut = (Base2534[]) w.value;
8883
assertEquals(1, arrayOut.length);
89-
9084
assertEquals(42, arrayOut[0].x);
85+
86+
// but ensure array-acceptance does NOT allow non-validated element types!
87+
final String badJson = mapper.writeValueAsString(new ObjectWrapper(new Base2534[] { new Bad2534(13) }));
88+
try {
89+
mapper.readValue(badJson, ObjectWrapper.class);
90+
fail("Should not pass");
91+
} catch (InvalidTypeIdException e) {
92+
verifyException(e, "Could not resolve type id 'com.fasterxml.jackson.");
93+
verifyException(e, "$Bad2534");
94+
verifyException(e, "as a subtype of");
95+
}
9196
}
9297
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.fasterxml.jackson.databind.jsontype.vld;
2+
3+
import com.fasterxml.jackson.databind.BaseMapTest;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.databind.DefaultTyping;
6+
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
7+
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
8+
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
9+
import com.fasterxml.jackson.databind.jsontype.vld.BasicPTVWithArraysTest.Base2534;
10+
import com.fasterxml.jackson.databind.jsontype.vld.BasicPTVWithArraysTest.Good2534;
11+
import com.fasterxml.jackson.databind.jsontype.vld.BasicPTVWithArraysTest.ObjectWrapper;
12+
13+
public class CustomPTVMatchersTest extends BaseMapTest
14+
{
15+
static abstract class CustomBase {
16+
public int x = 3;
17+
}
18+
19+
static class CustomGood extends CustomBase {
20+
protected CustomGood() { }
21+
public CustomGood(int x) {
22+
super();
23+
this.x = x;
24+
}
25+
}
26+
27+
static class CustomBad extends CustomBase {
28+
protected CustomBad() { }
29+
public CustomBad(int x) {
30+
super();
31+
this.x = x;
32+
}
33+
}
34+
35+
static final class ObjectWrapper {
36+
public Object value;
37+
38+
protected ObjectWrapper() { }
39+
public ObjectWrapper(Object v) { value = v; }
40+
}
41+
42+
/*
43+
/**********************************************************************
44+
/* Test methods
45+
/**********************************************************************
46+
*/
47+
48+
public void testCustomBaseMatchers() throws Exception
49+
{
50+
// TO BE COMPLETED
51+
}
52+
}

0 commit comments

Comments
 (0)