Skip to content

Commit 61ffa3a

Browse files
committed
Complete tests and initial partial implementation of #2195
1 parent 21c1103 commit 61ffa3a

File tree

7 files changed

+200
-20
lines changed

7 files changed

+200
-20
lines changed

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

+97-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import com.fasterxml.jackson.databind.cfg.MapperConfig;
1111
import com.fasterxml.jackson.databind.introspect.Annotated;
1212
import com.fasterxml.jackson.databind.introspect.ObjectIdInfo;
13+
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
14+
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator.Validity;
1315
import com.fasterxml.jackson.databind.type.TypeFactory;
1416
import com.fasterxml.jackson.databind.util.ClassUtil;
1517
import com.fasterxml.jackson.databind.util.Converter;
@@ -163,7 +165,7 @@ public JavaType constructSpecializedType(JavaType baseType, Class<?> subclass) {
163165

164166
/**
165167
* Lookup method called when code needs to resolve class name from input;
166-
* usually simple lookup
168+
* usually simple lookup.
167169
*
168170
* @since 2.9
169171
*/
@@ -201,6 +203,100 @@ public JavaType resolveSubType(JavaType baseType, String subClass)
201203
throw invalidTypeIdException(baseType, subClass, "Not a subtype");
202204
}
203205

206+
/**
207+
* Lookup method similar to {@link #resolveSubType} but one that also validates
208+
* that resulting subtype is valid according to the default {@link PolymorphicTypeValidator}
209+
* for the originating {@link ObjectMapper}.
210+
*
211+
* @since 2.10
212+
*/
213+
public abstract JavaType resolveAndValidateSubType(JavaType baseType, String subClass)
214+
throws JsonMappingException;
215+
216+
/**
217+
* Lookup method similar to {@link #resolveSubType} but one that also validates
218+
* that resulting subtype is valid according to given {@link PolymorphicTypeValidator}.
219+
*
220+
* @since 2.10
221+
*/
222+
public JavaType resolveAndValidateSubType(JavaType baseType, String subClass,
223+
PolymorphicTypeValidator ptv)
224+
throws JsonMappingException
225+
{
226+
// Off-line the special case of generic (parameterized) type:
227+
final int ltIndex = subClass.indexOf('<');
228+
if (ltIndex > 0) {
229+
return _resolveAndValidateGeneric(baseType, subClass, ptv, ltIndex);
230+
}
231+
final MapperConfig<?> config = getConfig();
232+
PolymorphicTypeValidator.Validity vld = ptv.validateSubClassName(config, baseType, subClass);
233+
if (vld == Validity.DENIED) {
234+
return _throwSubtypeNameNotAllowed(baseType, subClass, ptv);
235+
}
236+
final Class<?> cls;
237+
try {
238+
cls = getTypeFactory().findClass(subClass);
239+
} catch (ClassNotFoundException e) { // let caller handle this problem
240+
return null;
241+
} catch (Exception e) {
242+
throw invalidTypeIdException(baseType, subClass, String.format(
243+
"problem: (%s) %s",
244+
e.getClass().getName(),
245+
ClassUtil.exceptionMessage(e)));
246+
}
247+
if (!baseType.isTypeOrSuperTypeOf(cls)) {
248+
return _throwNotASubtype(baseType, subClass);
249+
}
250+
final JavaType subType = config.getTypeFactory().constructSpecializedType(baseType, cls);
251+
if (vld != Validity.ALLOWED) {
252+
if (ptv.validateSubType(config, baseType, subType) != Validity.ALLOWED) {
253+
return _throwSubtypeClassNotAllowed(baseType, subClass, ptv);
254+
}
255+
}
256+
return subType;
257+
}
258+
259+
private JavaType _resolveAndValidateGeneric(JavaType baseType, String subClass,
260+
PolymorphicTypeValidator ptv, int ltIndex)
261+
throws JsonMappingException
262+
{
263+
final MapperConfig<?> config = getConfig();
264+
// 24-Apr-2019, tatu: Not 100% sure if we should pass name with type parameters
265+
// or not, but guessing it's more convenient not to have to worry about it so
266+
// strip out
267+
PolymorphicTypeValidator.Validity vld = ptv.validateSubClassName(config, baseType, subClass.substring(0, ltIndex));
268+
if (vld == Validity.DENIED) {
269+
return _throwSubtypeNameNotAllowed(baseType, subClass, ptv);
270+
}
271+
JavaType subType = getTypeFactory().constructFromCanonical(subClass);
272+
if (!subType.isTypeOrSubTypeOf(baseType.getRawClass())) {
273+
return _throwNotASubtype(baseType, subClass);
274+
}
275+
// Unless we were approved already by name, check that actual sub-class acceptable:
276+
if (vld != Validity.ALLOWED) {
277+
if (ptv.validateSubType(config, baseType, subType) != Validity.ALLOWED) {
278+
return _throwSubtypeClassNotAllowed(baseType, subClass, ptv);
279+
}
280+
}
281+
return subType;
282+
}
283+
284+
protected <T> T _throwNotASubtype(JavaType baseType, String subType) throws JsonMappingException {
285+
throw invalidTypeIdException(baseType, subType, "Not a subtype");
286+
}
287+
288+
protected <T> T _throwSubtypeNameNotAllowed(JavaType baseType, String subType,
289+
PolymorphicTypeValidator ptv) throws JsonMappingException {
290+
throw invalidTypeIdException(baseType, subType,
291+
"Configured `PolymorphicTypeValidator` (of type "+ClassUtil.classNameOf(ptv)+") denied resolution");
292+
}
293+
294+
protected <T> T _throwSubtypeClassNotAllowed(JavaType baseType, String subType,
295+
PolymorphicTypeValidator ptv) throws JsonMappingException {
296+
throw invalidTypeIdException(baseType, subType,
297+
"Configured `PolymorphicTypeValidator` (of type "+ClassUtil.classNameOf(ptv)+") denied resolution");
298+
}
299+
204300
/**
205301
* Helper method for constructing exception to indicate that given type id
206302
* could not be resolved to a valid subtype of specified base type.

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

+5
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,11 @@ public TimeZone getTimeZone() {
273273
return _config.getTimeZone();
274274
}
275275

276+
@Override // since 2.10
277+
public JavaType resolveAndValidateSubType(JavaType baseType, String subClass) throws JsonMappingException {
278+
return resolveAndValidateSubType(baseType, subClass, _config.getPolymorphicTypeValidator());
279+
}
280+
276281
/*
277282
/**********************************************************
278283
/* Access to per-call state, like generic attributes (2.3+)

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

+7
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,13 @@ public ObjectMapper setPolymorphicTypeValidator(PolymorphicTypeValidator ptv) {
14191419
return this;
14201420
}
14211421

1422+
/**
1423+
* @since 2.10
1424+
*/
1425+
public PolymorphicTypeValidator getPolymorphicTypeValidator() {
1426+
return _deserializationConfig.getBaseSettings().getPolymorphicTypeValidator();
1427+
}
1428+
14221429
/*
14231430
/**********************************************************
14241431
/* Configuration: global-default/per-type override settings

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,12 @@ public Locale getLocale() {
386386
public TimeZone getTimeZone() {
387387
return _config.getTimeZone();
388388
}
389-
389+
390+
@Override // since 2.10
391+
public JavaType resolveAndValidateSubType(JavaType baseType, String subClass) throws JsonMappingException {
392+
return resolveAndValidateSubType(baseType, subClass, _config.getPolymorphicTypeValidator());
393+
}
394+
390395
/*
391396
/**********************************************************
392397
/* Generic attributes (2.3+)

src/main/java/com/fasterxml/jackson/databind/cfg/MapperConfig.java

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
1616
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
1717
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
18+
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
1819
import com.fasterxml.jackson.databind.jsontype.SubtypeResolver;
1920
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
2021
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
@@ -265,6 +266,13 @@ public final TypeResolverBuilder<?> getDefaultTyper(JavaType baseType) {
265266

266267
public abstract SubtypeResolver getSubtypeResolver();
267268

269+
/**
270+
* @since 2.10
271+
*/
272+
public PolymorphicTypeValidator getPolymorphicTypeValidator() {
273+
return _base.getPolymorphicTypeValidator();
274+
}
275+
268276
public final TypeFactory getTypeFactory() {
269277
return _base.getTypeFactory();
270278
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public JavaType typeFromId(DatabindContext context, String id) throws IOExceptio
4646

4747
protected JavaType _typeFromId(String id, DatabindContext ctxt) throws IOException
4848
{
49-
JavaType t = ctxt.resolveSubType(_baseType, id);
49+
// 24-Apr-2019, tatu: [databind#2195] validate as well as resolve:
50+
JavaType t = ctxt.resolveAndValidateSubType(_baseType, id);
5051
if (t == null) {
5152
if (ctxt instanceof DeserializationContext) {
5253
// First: we may have problem handlers that can deal with it?

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

+75-17
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import com.fasterxml.jackson.annotation.JsonTypeInfo;
44
import com.fasterxml.jackson.databind.*;
55
import com.fasterxml.jackson.databind.cfg.MapperConfig;
6+
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
67
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
78

89
/**
9-
* Tests to verify working of customizable {@PolymorphicTypeValidator}
10+
* Tests to verify working of customizable {@PolymorphicTypeValidator},
11+
* see [databind#2195]
1012
*
1113
* @since 2.10
1214
*/
@@ -28,6 +30,7 @@ public boolean equals(Object other) {
2830

2931
static class BadValue extends BaseValue { }
3032
static class GoodValue extends BaseValue { }
33+
static class MehValue extends BaseValue { }
3134

3235
// // // Wrapper types
3336

@@ -54,13 +57,6 @@ protected DefTypeWrapper() { }
5457
public DefTypeWrapper(BaseValue v) { value = v; }
5558
}
5659

57-
static class DefTypeMinimalWrapper {
58-
public BaseValue value;
59-
60-
protected DefTypeMinimalWrapper() { }
61-
public DefTypeMinimalWrapper(BaseValue v) { value = v; }
62-
}
63-
6460
// // // Validator implementations
6561

6662
static class SimpleNameBasedValidator extends PolymorphicTypeValidator {
@@ -143,12 +139,12 @@ public void testWithDefaultTypingNameAccept() throws Exception
143139

144140
public void testWithDefaultTypingNameDenyExplicit() throws Exception
145141
{
146-
142+
_verifyBadDefaultValue(MAPPER_DEF_TYPING_NAME_CHECK);
147143
}
148144

149145
public void testWithDefaultTypingNameDenyDefault() throws Exception
150146
{
151-
147+
_verifyMehDefaultValue(MAPPER_DEF_TYPING_NAME_CHECK);
152148
}
153149

154150
// // With Class check
@@ -162,12 +158,12 @@ public void testWithDefaultTypingClassAccept() throws Exception
162158

163159
public void testWithDefaultTypingClassDenyExplicit() throws Exception
164160
{
165-
161+
_verifyBadDefaultValue(MAPPER_DEF_TYPING_CLASS_CHECK);
166162
}
167163

168164
public void testWithDefaultTypingClassDenyDefault() throws Exception
169165
{
170-
166+
_verifyMehDefaultValue(MAPPER_DEF_TYPING_CLASS_CHECK);
171167
}
172168

173169
/*
@@ -187,10 +183,12 @@ public void testWithAnnotationNameAccept() throws Exception
187183

188184
public void testWithAnnotationNameDenyExplicit() throws Exception
189185
{
186+
_verifyBadAnnotatedValue(MAPPER_EXPLICIT_NAME_CHECK);
190187
}
191188

192189
public void testWithAnnotationNameDenyDefault() throws Exception
193190
{
191+
_verifyMehAnnotatedValue(MAPPER_EXPLICIT_NAME_CHECK);
194192
}
195193

196194
// // With Class
@@ -204,10 +202,12 @@ public void testWithAnnotationClassAccept() throws Exception
204202

205203
public void testWithAnnotationClassDenyExplicit() throws Exception
206204
{
205+
_verifyBadAnnotatedValue(MAPPER_EXPLICIT_CLASS_CHECK);
207206
}
208207

209208
public void testWithAnnotationClassDenyDefault() throws Exception
210209
{
210+
_verifyMehAnnotatedValue(MAPPER_EXPLICIT_CLASS_CHECK);
211211
}
212212

213213
/*
@@ -227,12 +227,12 @@ public void testWithAnnotationMinClassNameAccept() throws Exception
227227

228228
public void testWithAnnotationMinClassNameDenyExplicit() throws Exception
229229
{
230-
230+
_verifyBadAnnotatedMinValue(MAPPER_EXPLICIT_NAME_CHECK);
231231
}
232232

233233
public void testWithAnnotationMinClassNameDenyDefault() throws Exception
234234
{
235-
235+
_verifyMehAnnotatedMinValue(MAPPER_EXPLICIT_NAME_CHECK);
236236
}
237237

238238
// // With Class
@@ -246,17 +246,17 @@ public void testWithAnnotationMinClassClassAccept() throws Exception
246246

247247
public void testWithAnnotationMinClassClassDenyExplicit() throws Exception
248248
{
249-
249+
_verifyBadAnnotatedMinValue(MAPPER_EXPLICIT_CLASS_CHECK);
250250
}
251251

252252
public void testWithAnnotationMinClassClassDenyDefault() throws Exception
253253
{
254-
254+
_verifyMehAnnotatedMinValue(MAPPER_EXPLICIT_CLASS_CHECK);
255255
}
256256

257257
/*
258258
/**********************************************************************
259-
/* Helper methods
259+
/* Helper methods, round-trip (ok case)
260260
/**********************************************************************
261261
*/
262262

@@ -274,4 +274,62 @@ private AnnotatedMinimalWrapper _roundTripAnnotatedMinimal(ObjectMapper mapper,
274274
final String json = mapper.writeValueAsString(new AnnotatedMinimalWrapper(input));
275275
return mapper.readValue(json, AnnotatedMinimalWrapper.class);
276276
}
277+
278+
/*
279+
/**********************************************************************
280+
/* Helper methods, failing deser verification
281+
/**********************************************************************
282+
*/
283+
284+
private void _verifyBadDefaultValue(ObjectMapper mapper) throws Exception {
285+
final String json = mapper.writeValueAsString(new DefTypeWrapper(new BadValue()));
286+
_verifyBadValue(mapper, json, DefTypeWrapper.class);
287+
}
288+
289+
private void _verifyMehDefaultValue(ObjectMapper mapper) throws Exception {
290+
final String json = mapper.writeValueAsString(new DefTypeWrapper(new MehValue()));
291+
_verifyMehValue(mapper, json, DefTypeWrapper.class);
292+
}
293+
294+
private void _verifyBadAnnotatedValue(ObjectMapper mapper) throws Exception {
295+
final String json = mapper.writeValueAsString(new AnnotatedWrapper(new BadValue()));
296+
_verifyBadValue(mapper, json, AnnotatedWrapper.class);
297+
}
298+
299+
private void _verifyMehAnnotatedValue(ObjectMapper mapper) throws Exception {
300+
final String json = mapper.writeValueAsString(new AnnotatedWrapper(new MehValue()));
301+
_verifyMehValue(mapper, json, AnnotatedWrapper.class);
302+
}
303+
304+
private void _verifyBadAnnotatedMinValue(ObjectMapper mapper) throws Exception {
305+
final String json = mapper.writeValueAsString(new AnnotatedMinimalWrapper(new BadValue()));
306+
_verifyBadValue(mapper, json, AnnotatedMinimalWrapper.class);
307+
}
308+
309+
private void _verifyMehAnnotatedMinValue(ObjectMapper mapper) throws Exception {
310+
final String json = mapper.writeValueAsString(new AnnotatedMinimalWrapper(new MehValue()));
311+
_verifyMehValue(mapper, json, AnnotatedMinimalWrapper.class);
312+
}
313+
314+
private void _verifyBadValue(ObjectMapper mapper, String json, Class<?> type) throws Exception {
315+
try {
316+
mapper.readValue(json, type);
317+
fail("Should not pass");
318+
} catch (InvalidTypeIdException e) {
319+
verifyException(e, "Could not resolve type id");
320+
verifyException(e, "`PolymorphicTypeValidator`");
321+
verifyException(e, "denied resolution");
322+
}
323+
}
324+
325+
private void _verifyMehValue(ObjectMapper mapper, String json, Class<?> type) throws Exception {
326+
try {
327+
mapper.readValue(json, type);
328+
fail("Should not pass");
329+
} catch (InvalidTypeIdException e) {
330+
verifyException(e, "Could not resolve type id");
331+
verifyException(e, "`PolymorphicTypeValidator`");
332+
verifyException(e, "denied resolution");
333+
}
334+
}
277335
}

0 commit comments

Comments
 (0)