Skip to content

Commit

Permalink
Complete tests and initial partial implementation of #2195
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Apr 25, 2019
1 parent 21c1103 commit 61ffa3a
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.ObjectIdInfo;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator.Validity;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;
Expand Down Expand Up @@ -163,7 +165,7 @@ public JavaType constructSpecializedType(JavaType baseType, Class<?> subclass) {

/**
* Lookup method called when code needs to resolve class name from input;
* usually simple lookup
* usually simple lookup.
*
* @since 2.9
*/
Expand Down Expand Up @@ -201,6 +203,100 @@ public JavaType resolveSubType(JavaType baseType, String subClass)
throw invalidTypeIdException(baseType, subClass, "Not a subtype");
}

/**
* Lookup method similar to {@link #resolveSubType} but one that also validates
* that resulting subtype is valid according to the default {@link PolymorphicTypeValidator}
* for the originating {@link ObjectMapper}.
*
* @since 2.10
*/
public abstract JavaType resolveAndValidateSubType(JavaType baseType, String subClass)
throws JsonMappingException;

/**
* Lookup method similar to {@link #resolveSubType} but one that also validates
* that resulting subtype is valid according to given {@link PolymorphicTypeValidator}.
*
* @since 2.10
*/
public JavaType resolveAndValidateSubType(JavaType baseType, String subClass,
PolymorphicTypeValidator ptv)
throws JsonMappingException
{
// Off-line the special case of generic (parameterized) type:
final int ltIndex = subClass.indexOf('<');
if (ltIndex > 0) {
return _resolveAndValidateGeneric(baseType, subClass, ptv, ltIndex);
}
final MapperConfig<?> config = getConfig();
PolymorphicTypeValidator.Validity vld = ptv.validateSubClassName(config, baseType, subClass);
if (vld == Validity.DENIED) {
return _throwSubtypeNameNotAllowed(baseType, subClass, ptv);
}
final Class<?> cls;
try {
cls = getTypeFactory().findClass(subClass);
} catch (ClassNotFoundException e) { // let caller handle this problem
return null;
} catch (Exception e) {
throw invalidTypeIdException(baseType, subClass, String.format(
"problem: (%s) %s",
e.getClass().getName(),
ClassUtil.exceptionMessage(e)));
}
if (!baseType.isTypeOrSuperTypeOf(cls)) {
return _throwNotASubtype(baseType, subClass);
}
final JavaType subType = config.getTypeFactory().constructSpecializedType(baseType, cls);
if (vld != Validity.ALLOWED) {
if (ptv.validateSubType(config, baseType, subType) != Validity.ALLOWED) {
return _throwSubtypeClassNotAllowed(baseType, subClass, ptv);
}
}
return subType;
}

private JavaType _resolveAndValidateGeneric(JavaType baseType, String subClass,
PolymorphicTypeValidator ptv, int ltIndex)
throws JsonMappingException
{
final MapperConfig<?> config = getConfig();
// 24-Apr-2019, tatu: Not 100% sure if we should pass name with type parameters
// or not, but guessing it's more convenient not to have to worry about it so
// strip out
PolymorphicTypeValidator.Validity vld = ptv.validateSubClassName(config, baseType, subClass.substring(0, ltIndex));
if (vld == Validity.DENIED) {
return _throwSubtypeNameNotAllowed(baseType, subClass, ptv);
}
JavaType subType = getTypeFactory().constructFromCanonical(subClass);
if (!subType.isTypeOrSubTypeOf(baseType.getRawClass())) {
return _throwNotASubtype(baseType, subClass);
}
// Unless we were approved already by name, check that actual sub-class acceptable:
if (vld != Validity.ALLOWED) {
if (ptv.validateSubType(config, baseType, subType) != Validity.ALLOWED) {
return _throwSubtypeClassNotAllowed(baseType, subClass, ptv);
}
}
return subType;
}

protected <T> T _throwNotASubtype(JavaType baseType, String subType) throws JsonMappingException {
throw invalidTypeIdException(baseType, subType, "Not a subtype");
}

protected <T> T _throwSubtypeNameNotAllowed(JavaType baseType, String subType,
PolymorphicTypeValidator ptv) throws JsonMappingException {
throw invalidTypeIdException(baseType, subType,
"Configured `PolymorphicTypeValidator` (of type "+ClassUtil.classNameOf(ptv)+") denied resolution");
}

protected <T> T _throwSubtypeClassNotAllowed(JavaType baseType, String subType,
PolymorphicTypeValidator ptv) throws JsonMappingException {
throw invalidTypeIdException(baseType, subType,
"Configured `PolymorphicTypeValidator` (of type "+ClassUtil.classNameOf(ptv)+") denied resolution");
}

/**
* Helper method for constructing exception to indicate that given type id
* could not be resolved to a valid subtype of specified base type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ public TimeZone getTimeZone() {
return _config.getTimeZone();
}

@Override // since 2.10
public JavaType resolveAndValidateSubType(JavaType baseType, String subClass) throws JsonMappingException {
return resolveAndValidateSubType(baseType, subClass, _config.getPolymorphicTypeValidator());
}

/*
/**********************************************************
/* Access to per-call state, like generic attributes (2.3+)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,13 @@ public ObjectMapper setPolymorphicTypeValidator(PolymorphicTypeValidator ptv) {
return this;
}

/**
* @since 2.10
*/
public PolymorphicTypeValidator getPolymorphicTypeValidator() {
return _deserializationConfig.getBaseSettings().getPolymorphicTypeValidator();
}

/*
/**********************************************************
/* Configuration: global-default/per-type override settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,12 @@ public Locale getLocale() {
public TimeZone getTimeZone() {
return _config.getTimeZone();
}


@Override // since 2.10
public JavaType resolveAndValidateSubType(JavaType baseType, String subClass) throws JsonMappingException {
return resolveAndValidateSubType(baseType, subClass, _config.getPolymorphicTypeValidator());
}

/*
/**********************************************************
/* Generic attributes (2.3+)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.introspect.VisibilityChecker;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.SubtypeResolver;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
Expand Down Expand Up @@ -265,6 +266,13 @@ public final TypeResolverBuilder<?> getDefaultTyper(JavaType baseType) {

public abstract SubtypeResolver getSubtypeResolver();

/**
* @since 2.10
*/
public PolymorphicTypeValidator getPolymorphicTypeValidator() {
return _base.getPolymorphicTypeValidator();
}

public final TypeFactory getTypeFactory() {
return _base.getTypeFactory();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public JavaType typeFromId(DatabindContext context, String id) throws IOExceptio

protected JavaType _typeFromId(String id, DatabindContext ctxt) throws IOException
{
JavaType t = ctxt.resolveSubType(_baseType, id);
// 24-Apr-2019, tatu: [databind#2195] validate as well as resolve:
JavaType t = ctxt.resolveAndValidateSubType(_baseType, id);
if (t == null) {
if (ctxt instanceof DeserializationContext) {
// First: we may have problem handlers that can deal with it?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;

/**
* Tests to verify working of customizable {@PolymorphicTypeValidator}
* Tests to verify working of customizable {@PolymorphicTypeValidator},
* see [databind#2195]
*
* @since 2.10
*/
Expand All @@ -28,6 +30,7 @@ public boolean equals(Object other) {

static class BadValue extends BaseValue { }
static class GoodValue extends BaseValue { }
static class MehValue extends BaseValue { }

// // // Wrapper types

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

static class DefTypeMinimalWrapper {
public BaseValue value;

protected DefTypeMinimalWrapper() { }
public DefTypeMinimalWrapper(BaseValue v) { value = v; }
}

// // // Validator implementations

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

public void testWithDefaultTypingNameDenyExplicit() throws Exception
{

_verifyBadDefaultValue(MAPPER_DEF_TYPING_NAME_CHECK);
}

public void testWithDefaultTypingNameDenyDefault() throws Exception
{

_verifyMehDefaultValue(MAPPER_DEF_TYPING_NAME_CHECK);
}

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

public void testWithDefaultTypingClassDenyExplicit() throws Exception
{

_verifyBadDefaultValue(MAPPER_DEF_TYPING_CLASS_CHECK);
}

public void testWithDefaultTypingClassDenyDefault() throws Exception
{

_verifyMehDefaultValue(MAPPER_DEF_TYPING_CLASS_CHECK);
}

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

public void testWithAnnotationNameDenyExplicit() throws Exception
{
_verifyBadAnnotatedValue(MAPPER_EXPLICIT_NAME_CHECK);
}

public void testWithAnnotationNameDenyDefault() throws Exception
{
_verifyMehAnnotatedValue(MAPPER_EXPLICIT_NAME_CHECK);
}

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

public void testWithAnnotationClassDenyExplicit() throws Exception
{
_verifyBadAnnotatedValue(MAPPER_EXPLICIT_CLASS_CHECK);
}

public void testWithAnnotationClassDenyDefault() throws Exception
{
_verifyMehAnnotatedValue(MAPPER_EXPLICIT_CLASS_CHECK);
}

/*
Expand All @@ -227,12 +227,12 @@ public void testWithAnnotationMinClassNameAccept() throws Exception

public void testWithAnnotationMinClassNameDenyExplicit() throws Exception
{

_verifyBadAnnotatedMinValue(MAPPER_EXPLICIT_NAME_CHECK);
}

public void testWithAnnotationMinClassNameDenyDefault() throws Exception
{

_verifyMehAnnotatedMinValue(MAPPER_EXPLICIT_NAME_CHECK);
}

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

public void testWithAnnotationMinClassClassDenyExplicit() throws Exception
{

_verifyBadAnnotatedMinValue(MAPPER_EXPLICIT_CLASS_CHECK);
}

public void testWithAnnotationMinClassClassDenyDefault() throws Exception
{

_verifyMehAnnotatedMinValue(MAPPER_EXPLICIT_CLASS_CHECK);
}

/*
/**********************************************************************
/* Helper methods
/* Helper methods, round-trip (ok case)
/**********************************************************************
*/

Expand All @@ -274,4 +274,62 @@ private AnnotatedMinimalWrapper _roundTripAnnotatedMinimal(ObjectMapper mapper,
final String json = mapper.writeValueAsString(new AnnotatedMinimalWrapper(input));
return mapper.readValue(json, AnnotatedMinimalWrapper.class);
}

/*
/**********************************************************************
/* Helper methods, failing deser verification
/**********************************************************************
*/

private void _verifyBadDefaultValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new DefTypeWrapper(new BadValue()));
_verifyBadValue(mapper, json, DefTypeWrapper.class);
}

private void _verifyMehDefaultValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new DefTypeWrapper(new MehValue()));
_verifyMehValue(mapper, json, DefTypeWrapper.class);
}

private void _verifyBadAnnotatedValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedWrapper(new BadValue()));
_verifyBadValue(mapper, json, AnnotatedWrapper.class);
}

private void _verifyMehAnnotatedValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedWrapper(new MehValue()));
_verifyMehValue(mapper, json, AnnotatedWrapper.class);
}

private void _verifyBadAnnotatedMinValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedMinimalWrapper(new BadValue()));
_verifyBadValue(mapper, json, AnnotatedMinimalWrapper.class);
}

private void _verifyMehAnnotatedMinValue(ObjectMapper mapper) throws Exception {
final String json = mapper.writeValueAsString(new AnnotatedMinimalWrapper(new MehValue()));
_verifyMehValue(mapper, json, AnnotatedMinimalWrapper.class);
}

private void _verifyBadValue(ObjectMapper mapper, String json, Class<?> type) throws Exception {
try {
mapper.readValue(json, type);
fail("Should not pass");
} catch (InvalidTypeIdException e) {
verifyException(e, "Could not resolve type id");
verifyException(e, "`PolymorphicTypeValidator`");
verifyException(e, "denied resolution");
}
}

private void _verifyMehValue(ObjectMapper mapper, String json, Class<?> type) throws Exception {
try {
mapper.readValue(json, type);
fail("Should not pass");
} catch (InvalidTypeIdException e) {
verifyException(e, "Could not resolve type id");
verifyException(e, "`PolymorphicTypeValidator`");
verifyException(e, "denied resolution");
}
}
}

0 comments on commit 61ffa3a

Please sign in to comment.