Skip to content

Commit 535b1dc

Browse files
committed
Fix #2195 (last changes)
1 parent e0cd19b commit 535b1dc

File tree

4 files changed

+175
-6
lines changed

4 files changed

+175
-6
lines changed

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Project: jackson-databind
2121
#2187: Make `JsonNode.toString()` use shared `ObjectMapper` to produce valid json
2222
#2189: `TreeTraversingParser` does not check int bounds
2323
(reported by Alexander S)
24+
#2195: Add abstraction `PolymorphicTypeValidator`, for limiting subtypes allowed by
25+
default typing, `@JsonTypeInfo`
2426
#2196: Type safety for `readValue()` with `TypeReference`
2527
(suggested by nguyenfilip@github)
2628
#2204: Add `JsonNode.isEmpty()` as convenience alias

src/main/java/com/fasterxml/jackson/databind/DatabindContext.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ private JavaType _resolveAndValidateGeneric(JavaType baseType, String subClass,
274274
}
275275
return subType;
276276
}
277-
277+
278278
protected <T> T _throwNotASubtype(JavaType baseType, String subType) throws JsonMappingException {
279279
throw invalidTypeIdException(baseType, subType, "Not a subtype");
280280
}
@@ -290,7 +290,7 @@ protected <T> T _throwSubtypeClassNotAllowed(JavaType baseType, String subType,
290290
throw invalidTypeIdException(baseType, subType,
291291
"Configured `PolymorphicTypeValidator` (of type "+ClassUtil.classNameOf(ptv)+") denied resolution");
292292
}
293-
293+
294294
/**
295295
* Helper method for constructing exception to indicate that given type id
296296
* could not be resolved to a valid subtype of specified base type.

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,17 @@ public boolean match(Class<?> clazz) {
111111
* nominal base type's class name matches given {@link Pattern}
112112
* For example, call to
113113
*<pre>
114-
* builder.allowIfBaseType(Pattern.compile("com\\.mycompany\\.")
114+
* builder.allowIfBaseType(Pattern.compile("com\\.mycompany\\..*")
115115
*</pre>
116116
* would indicate that any polymorphic properties where declared base type
117117
* is in package {@code com.mycompany} would allow all legal (assignment-compatible)
118118
* subtypes.
119+
*<p>
120+
* NOTE! {@link Pattern} match is applied using
121+
*<code>
122+
* if (patternForBase.matcher(typeId).matches()) { }
123+
*</code>
124+
* that is, it must match the whole class name, not just part.
119125
*/
120126
public Builder allowIfBaseType(final Pattern patternForBase) {
121127
return _appendBaseMatcher(new TypeMatcher() {
@@ -197,6 +203,12 @@ public boolean match(Class<?> clazz) {
197203
*</pre>
198204
* would indicate that any polymorphic values in package {@code com.mycompany}
199205
* would be allowed.
206+
*<p>
207+
* NOTE! {@link Pattern} match is applied using
208+
*<code>
209+
* if (patternForSubType.matcher(typeId).matches()) { }
210+
*</code>
211+
* that is, it must match the whole class name, not just part.
200212
*/
201213
public Builder allowIfSubType(final Pattern patternForSubType) {
202214
return _appendSubNameMatcher(new NameMatcher() {

src/test/java/com/fasterxml/jackson/databind/jsontype/vld/BasicPTVTest.java

+158-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.fasterxml.jackson.databind.jsontype.vld;
22

3+
import java.util.regex.Pattern;
4+
35
import com.fasterxml.jackson.databind.*;
46
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
7+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
58
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
69
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
710
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
@@ -71,10 +74,11 @@ protected NumberWrapper() { }
7174

7275
/*
7376
/**********************************************************************
74-
/* Test methods: annotated
77+
/* Test methods: by base type, pass
7578
/**********************************************************************
7679
*/
7780

81+
// First: test simple Base-type-as-class allowing
7882
public void testAllowByBaseClass() throws Exception {
7983
final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
8084
.allowIfBaseType(BaseValue.class)
@@ -94,7 +98,7 @@ public void testAllowByBaseClass() throws Exception {
9498
mapper.readValue(json2, NumberWrapper.class);
9599
fail("Should not pass");
96100
} catch (InvalidTypeIdException e) {
97-
verifyException(e, "Could not resolve type id `java.lang.Byte`");
101+
verifyException(e, "Could not resolve type id 'java.lang.Byte'");
98102
verifyException(e, "as a subtype of");
99103
}
100104

@@ -104,9 +108,160 @@ public void testAllowByBaseClass() throws Exception {
104108
.allowIfBaseType(Number.class)
105109
.build(), DefaultTyping.NON_FINAL)
106110
.build();
107-
NumberWrapper nw = mapper2.readValue(json, NumberWrapper.class);
111+
NumberWrapper nw = mapper2.readValue(json2, NumberWrapper.class);
108112
assertNotNull(nw);
109113
assertEquals(Byte.valueOf((byte) 4), nw.value);
110114
}
115+
116+
// Then subtype-prefix
117+
public void testAllowByBaseClassPrefix() throws Exception {
118+
final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
119+
.allowIfBaseType("com.fasterxml.")
120+
.build();
121+
ObjectMapper mapper = jsonMapperBuilder()
122+
.enableDefaultTyping(ptv, DefaultTyping.NON_FINAL)
123+
.build();
124+
125+
// First, test accepted case
126+
final String json = mapper.writeValueAsString(BaseValueWrapper.withA(42));
127+
BaseValueWrapper w = mapper.readValue(json, BaseValueWrapper.class);
128+
assertEquals(42, w.value.x);
129+
130+
// then non-accepted
131+
final String json2 = mapper.writeValueAsString(new NumberWrapper(Byte.valueOf((byte) 4)));
132+
try {
133+
mapper.readValue(json2, NumberWrapper.class);
134+
fail("Should not pass");
135+
} catch (InvalidTypeIdException e) {
136+
verifyException(e, "Could not resolve type id 'java.lang.Byte'");
137+
verifyException(e, "as a subtype of");
138+
}
139+
}
140+
141+
// Then subtype-pattern
142+
public void testAllowByBaseClassPattern() throws Exception {
143+
final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
144+
.allowIfBaseType(Pattern.compile("\\w+\\.fasterxml\\..+"))
145+
.build();
146+
ObjectMapper mapper = jsonMapperBuilder()
147+
.enableDefaultTyping(ptv, DefaultTyping.NON_FINAL)
148+
.build();
149+
150+
// First, test accepted case
151+
final String json = mapper.writeValueAsString(BaseValueWrapper.withA(42));
152+
BaseValueWrapper w = mapper.readValue(json, BaseValueWrapper.class);
153+
assertEquals(42, w.value.x);
154+
155+
// then non-accepted
156+
final String json2 = mapper.writeValueAsString(new NumberWrapper(Byte.valueOf((byte) 4)));
157+
try {
158+
mapper.readValue(json2, NumberWrapper.class);
159+
fail("Should not pass");
160+
} catch (InvalidTypeIdException e) {
161+
verifyException(e, "Could not resolve type id 'java.lang.Byte'");
162+
verifyException(e, "as a subtype of");
163+
}
164+
}
165+
166+
// And finally, block by specific direct-match base type
167+
public void testDenyByBaseClass() throws Exception {
168+
final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
169+
// indicate that all subtypes `BaseValue` would be fine
170+
.allowIfBaseType(BaseValue.class)
171+
// but that nominal base type MUST NOT be `Object.class`
172+
.denyForExactBaseType(Object.class)
173+
.build();
174+
ObjectMapper mapper = jsonMapperBuilder()
175+
.enableDefaultTyping(ptv, DefaultTyping.NON_FINAL)
176+
.build();
177+
final String json = mapper.writeValueAsString(new ObjectWrapper(new ValueA(15)));
178+
try {
179+
mapper.readValue(json, ObjectWrapper.class);
180+
fail("Should not pass");
181+
182+
// NOTE: different exception type since denial was for whole property, not just specific values
183+
} catch (InvalidDefinitionException e) {
184+
verifyException(e, "denied resolution of all subtypes of base type `java.lang.Object`");
185+
}
186+
}
187+
188+
/*
189+
/**********************************************************************
190+
/* Test methods: by sub type
191+
/**********************************************************************
192+
*/
193+
194+
public void testAllowBySubClass() throws Exception {
195+
final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
196+
.allowIfSubType(ValueB.class)
197+
.build();
198+
ObjectMapper mapper = jsonMapperBuilder()
199+
.enableDefaultTyping(ptv, DefaultTyping.NON_FINAL)
200+
.build();
201+
202+
// First, test accepted case
203+
final String json = mapper.writeValueAsString(BaseValueWrapper.withB(42));
204+
BaseValueWrapper w = mapper.readValue(json, BaseValueWrapper.class);
205+
assertEquals(42, w.value.x);
206+
207+
// then non-accepted
208+
try {
209+
mapper.readValue(mapper.writeValueAsString(BaseValueWrapper.withA(43)),
210+
BaseValueWrapper.class);
211+
fail("Should not pass");
212+
} catch (InvalidTypeIdException e) {
213+
verifyException(e, "Could not resolve type id 'com.fasterxml.jackson.");
214+
verifyException(e, "as a subtype of");
215+
}
216+
}
217+
218+
public void testAllowBySubClassPrefix() throws Exception {
219+
final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
220+
.allowIfSubType(ValueB.class.getName())
221+
.build();
222+
ObjectMapper mapper = jsonMapperBuilder()
223+
.enableDefaultTyping(ptv, DefaultTyping.NON_FINAL)
224+
.build();
225+
226+
// First, test accepted case
227+
final String json = mapper.writeValueAsString(BaseValueWrapper.withB(42));
228+
BaseValueWrapper w = mapper.readValue(json, BaseValueWrapper.class);
229+
assertEquals(42, w.value.x);
230+
231+
// then non-accepted
232+
try {
233+
mapper.readValue(mapper.writeValueAsString(BaseValueWrapper.withA(43)),
234+
BaseValueWrapper.class);
235+
fail("Should not pass");
236+
} catch (InvalidTypeIdException e) {
237+
verifyException(e, "Could not resolve type id 'com.fasterxml.jackson.");
238+
verifyException(e, "as a subtype of");
239+
}
240+
}
241+
242+
public void testAllowBySubClassPattern() throws Exception {
243+
final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
244+
.allowIfSubType(Pattern.compile(Pattern.quote(ValueB.class.getName())))
245+
.build();
246+
ObjectMapper mapper = jsonMapperBuilder()
247+
.enableDefaultTyping(ptv, DefaultTyping.NON_FINAL)
248+
.build();
249+
250+
// First, test accepted case
251+
final String json = mapper.writeValueAsString(BaseValueWrapper.withB(42));
252+
BaseValueWrapper w = mapper.readValue(json, BaseValueWrapper.class);
253+
assertEquals(42, w.value.x);
254+
255+
// then non-accepted
256+
try {
257+
mapper.readValue(mapper.writeValueAsString(BaseValueWrapper.withA(43)),
258+
BaseValueWrapper.class);
259+
fail("Should not pass");
260+
} catch (InvalidTypeIdException e) {
261+
verifyException(e, "Could not resolve type id 'com.fasterxml.jackson.");
262+
verifyException(e, "as a subtype of");
263+
}
264+
}
111265
}
112266

267+

0 commit comments

Comments
 (0)