1 package com.fasterxml.jackson.databind.jsontype.vld; 2 3 import java.util.regex.Pattern; 4 5 import com.fasterxml.jackson.databind.*; 6 import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; 7 import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; 8 import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; 9 import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; 10 import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; 11 12 /** 13 * Tests for the main user-configurable {@code PolymorphicTypeValidator}, 14 * {@link BasicPolymorphicTypeValidator}. 15 */ 16 public class BasicPTVTest extends BaseMapTest 17 { 18 // // // Value types 19 20 static abstract class BaseValue { 21 public int x = 3; 22 } 23 24 static class ValueA extends BaseValue { ValueA()25 protected ValueA() { } ValueA(int x)26 public ValueA(int x) { 27 super(); 28 this.x = x; 29 } 30 } 31 32 static class ValueB extends BaseValue { ValueB()33 protected ValueB() { } ValueB(int x)34 public ValueB(int x) { 35 super(); 36 this.x = x; 37 } 38 } 39 40 // // // Value types 41 42 // make this type `final` to avoid polymorphic handling 43 static final class BaseValueWrapper { 44 public BaseValue value; 45 BaseValueWrapper()46 protected BaseValueWrapper() { } 47 withA(int x)48 public static BaseValueWrapper withA(int x) { 49 BaseValueWrapper w = new BaseValueWrapper(); 50 w.value = new ValueA(x); 51 return w; 52 } 53 withB(int x)54 public static BaseValueWrapper withB(int x) { 55 BaseValueWrapper w = new BaseValueWrapper(); 56 w.value = new ValueB(x); 57 return w; 58 } 59 } 60 61 static final class ObjectWrapper { 62 public Object value; 63 ObjectWrapper()64 protected ObjectWrapper() { } ObjectWrapper(Object v)65 public ObjectWrapper(Object v) { value = v; } 66 } 67 68 static final class NumberWrapper { 69 public Number value; 70 NumberWrapper()71 protected NumberWrapper() { } NumberWrapper(Number v)72 public NumberWrapper(Number v) { value = v; } 73 } 74 75 /* 76 /********************************************************************** 77 /* Test methods: by base type, pass 78 /********************************************************************** 79 */ 80 81 // First: test simple Base-type-as-class allowing testAllowByBaseClass()82 public void testAllowByBaseClass() throws Exception { 83 final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder() 84 .allowIfBaseType(BaseValue.class) 85 .build(); 86 ObjectMapper mapper = jsonMapperBuilder() 87 .activateDefaultTyping(ptv, DefaultTyping.NON_FINAL) 88 .build(); 89 90 // First, test accepted case 91 final String json = mapper.writeValueAsString(BaseValueWrapper.withA(42)); 92 BaseValueWrapper w = mapper.readValue(json, BaseValueWrapper.class); 93 assertEquals(42, w.value.x); 94 95 // then non-accepted 96 final String json2 = mapper.writeValueAsString(new NumberWrapper(Byte.valueOf((byte) 4))); 97 try { 98 mapper.readValue(json2, NumberWrapper.class); 99 fail("Should not pass"); 100 } catch (InvalidTypeIdException e) { 101 verifyException(e, "Could not resolve type id 'java.lang.Byte'"); 102 verifyException(e, "as a subtype of"); 103 } 104 105 // and then yet again accepted one with different config 106 ObjectMapper mapper2 = jsonMapperBuilder() 107 .activateDefaultTyping(BasicPolymorphicTypeValidator.builder() 108 .allowIfBaseType(Number.class) 109 .build(), DefaultTyping.NON_FINAL) 110 .build(); 111 NumberWrapper nw = mapper2.readValue(json2, NumberWrapper.class); 112 assertNotNull(nw); 113 assertEquals(Byte.valueOf((byte) 4), nw.value); 114 } 115 116 // Then subtype-prefix testAllowByBaseClassPrefix()117 public void testAllowByBaseClassPrefix() throws Exception { 118 final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder() 119 .allowIfBaseType("com.fasterxml.") 120 .build(); 121 ObjectMapper mapper = jsonMapperBuilder() 122 .activateDefaultTyping(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 testAllowByBaseClassPattern()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 .activateDefaultTyping(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 testDenyByBaseClass()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 .activateDefaultTyping(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 testAllowBySubClass()194 public void testAllowBySubClass() throws Exception { 195 final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder() 196 .allowIfSubType(ValueB.class) 197 .build(); 198 ObjectMapper mapper = jsonMapperBuilder() 199 .activateDefaultTyping(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 testAllowBySubClassPrefix()218 public void testAllowBySubClassPrefix() throws Exception { 219 final PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder() 220 .allowIfSubType(ValueB.class.getName()) 221 .build(); 222 ObjectMapper mapper = jsonMapperBuilder() 223 .activateDefaultTyping(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 testAllowBySubClassPattern()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 .activateDefaultTyping(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 } 265 } 266 267 268